linux驱动程序结构框架及工作原理分别是什么?

linux驱动程序结构框架及工作原理分别是什么?,第1张

一、Linux device driver 的概念\x0d\x0a\x0d\x0a系统调用是 *** 作系统内核和应用程序之间的接口,设备驱动程序是 *** 作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象 *** 作普通文件一样对硬件设备进行 *** 作。设备驱动程序是内核的一部分,它完成以下的功能:\x0d\x0a\x0d\x0a1、对设备初始化和释放;\x0d\x0a\x0d\x0a2、把数据从内核传送到硬件和从硬件读取数据;\x0d\x0a\x0d\x0a3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;\x0d\x0a\x0d\x0a4、检测和处理设备出现的错误。\x0d\x0a\x0d\x0a在Linux *** 作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O *** 作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。\x0d\x0a\x0d\x0a已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。\x0d\x0a\x0d\x0a最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。\x0d\x0a\x0d\x0a二、实例剖析\x0d\x0a\x0d\x0a我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。\x0d\x0a\x0d\x0a由于用户进程是通过设备文件同硬件打交道,对设备文件的 *** 作方式不外乎就是一些系统调用,如 open,read,write,close?, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:\x0d\x0a\x0d\x0aSTruct file_operatiONs {\x0d\x0a\x0d\x0aint (*seek) (struct inode * ,struct file *, off_t ,int)\x0d\x0a\x0d\x0aint (*read) (struct inode * ,struct file *, char ,int)\x0d\x0a\x0d\x0aint (*write) (struct inode * ,struct file *, off_t ,int)\x0d\x0a\x0d\x0aint (*readdir) (struct inode * ,struct file *, struct dirent * ,int)\x0d\x0a\x0d\x0aint (*select) (struct inode * ,struct file *, int ,select_table *)\x0d\x0a\x0d\x0aint (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long)\x0d\x0a\x0d\x0aint (*mmap) (struct inode * ,struct file *, struct vm_area_struct *)\x0d\x0a\x0d\x0aint (*open) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*release) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*fsync) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*fasync) (struct inode * ,struct file *,int)\x0d\x0a\x0d\x0aint (*check_media_change) (struct inode * ,struct file *)\x0d\x0a\x0d\x0aint (*revalidate) (dev_t dev)\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write *** 作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。\x0d\x0a\x0d\x0a下面就开始写子程序。\x0d\x0a\x0d\x0a#include 基本的类型定义\x0d\x0a\x0d\x0a#include 文件系统使用相关的头文件\x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0aunsigned int test_major = 0\x0d\x0a\x0d\x0astatic int read_test(struct inode *inode,struct file *file,char *buf,int count)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint left用户空间和内核空间\x0d\x0a\x0d\x0aif (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )\x0d\x0a\x0d\x0areturn -EFAULT\x0d\x0a\x0d\x0afor(left = count left >0 left--)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0a__put_user(1,buf,1)\x0d\x0a\x0d\x0abuf++\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0areturn count\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。\x0d\x0a\x0d\x0astatic int write_test(struct inode *inode,struct file *file,const char *buf,int count)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0areturn count\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0astatic int open_test(struct inode *inode,struct file *file )\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aMOD_INC_USE_COUNT模块计数加以,表示当前内核有个设备加载内核当中去\x0d\x0a\x0d\x0areturn 0\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0astatic void release_test(struct inode *inode,struct file *file )\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aMOD_DEC_USE_COUNT\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a这几个函数都是空 *** 作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。\x0d\x0a\x0d\x0astruct file_operations test_fops = {?\x0d\x0a\x0d\x0aread_test,\x0d\x0a\x0d\x0awrite_test,\x0d\x0a\x0d\x0aopen_test,\x0d\x0a\x0d\x0arelease_test,\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0a设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。\x0d\x0a\x0d\x0aint init_module(void)\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint result\x0d\x0a\x0d\x0aresult = register_chrdev(0, "test", &test_fops)对设备 *** 作的整个接口\x0d\x0a\x0d\x0aif (result \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0a#include \x0d\x0a\x0d\x0amain()\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aint testdev\x0d\x0a\x0d\x0aint i\x0d\x0a\x0d\x0achar buf[10]\x0d\x0a\x0d\x0atestdev = open("/dev/test",O_RDWR)\x0d\x0a\x0d\x0aif ( testdev == -1 )\x0d\x0a\x0d\x0a{\x0d\x0a\x0d\x0aprintf("Cann't open file \n")\x0d\x0a\x0d\x0aexit(0)\x0d\x0a\x0d\x0a}\x0d\x0a\x0d\x0aread(testdev,buf,10)\x0d\x0a\x0d\x0afor (i = 0i 回答于 2022-11-18

以装载和卸载模块为例:

1、首先输入代码

#include <linux/init.h>

#include <linux/module.h>

2、然后输入下方的代码:

static int my_init(void)

{

                    return  0

}

static void my_exit(void)

3、然后在输入下方的代码:

{

                    return

}

module_init(my_init)

module_exit(my_exit)这样就完成了。

对于Linux底层驱动的简单认知

底层驱动是让设备工作的基本程序,它给用户提供了一个使用这个设备的接口。就拿树莓派来说,如果我们想要用它的那40Pin中的某个GPIO口,但是那个IO口没有相应的驱动程序给我们 *** 作,这时,无论如何我们都无法 *** 作IO口,wiringPi库到了最后也是要通过相应的驱动程序去 *** 作IO的。因为底层驱动会 *** 作CPU的与该设备相关的寄存器,实现驱动功能,这就是底层驱动。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/6040899.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-12
下一篇 2023-03-12

发表评论

登录后才能评论

评论列表(0条)

保存