Linux设备驱动程序工作原理

LinuxUnix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。 

  系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,运行在核心态,它完成以下的功能:

  1.对设备初始化和释放.

  2.把数据从内核传送到硬件和从硬件读取数据.

  3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.

  4.检测和处理设备出现的错误.

  在Linux操作系统下有三类主要的设备文件类型:字符设备、块设备和网络接口。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。

  已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都有其文件属性(c/b),表示是字符设备还是块设备?另 外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用 从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。

  最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行

  由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 openreadwriteclose...., 注意,不是fopen fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:

struct file_operations {

int (*seek) (struct inode * struct file * off_t int);
int (*read) (struct inode *
struct file * char int);
int (*write) (struct inode *
struct file * off_t int);
int (*readdir) (struct inode *
struct file * struct dirent * int);
int (*select) (struct inode *
struct file * int select_table *);
int (*ioctl) (struct inode *
struct file * unsined int unsigned long);
int (*mmap) (struct inode *
struct file * struct vm_area_struct *);
int (*open) (struct inode *
struct file *);
int (*release) (struct inode *
struct file *);
int (*fsync) (struct inode *
struct file *);
int (*fasync) (struct inode *
struct file *int);
int (*check_media_change) (struct inode *
struct file *);
int (*revalidate) (dev_t dev);
}
   
这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。 相当简单!

深入分析:

内核用struct inode结构在内部表示文件,因此它和struct file结构不同,后者用来描述被某个进程打开的文件。而且struct inode struct file的一个成员。对单个文件,可能会有多个表示打开的文件描述结构体struct file,但是它们都指向单个struct inode结构。struct inode结构中包含了大量有关文件的信息,其中有一个重要的成员可以解释清楚驱动程序的工作原理,这个成员为:

dev_c i_rdev;//这个字段包含了正真的设备编号,包括主设备号和次设备号

内核用struct task_struct结构体来描述一个进程,在struct task_struct 结构体中有一个重要成员:

struct files_struct *files;//描述了该进程打开的所有文件的信息

其定义如下:

struct files_struct{

int count;

fd_set close_on_exec;

fd_set open_fds;

struct file * fd[NR_OPEN];

};

fd就是由open系统调用返回的文件描述符,通过fd就可以找到对应的file结构,从而找到inode结构,从而由inode结构找到i_rdev,也就是找到了设备文件的主设备号和次设备号。由于设备文件的主设备号标志的是驱动程序,所以也就找到了已经在系统中注册过的驱动程序。然后读取struct file_operations中相应的标准系统调用的实现函数,从而完成了用户的读写等的请求。调用链为:

struct task_struct > struct files_stuctt > struct file > struct inode > i_rdev

注意:

开发驱动过程中,采用内核引用。/include下有好多头文件,需要的只有/include/linux/include/asm目录。

三个数据结构最重要:/linux/fs.h中定义的file_operationsinodefile结构。

转自:http://blog.163.com/ocean_china@126/blog/static/1851702020084291155083/

原文地址:https://www.cnblogs.com/hnrainll/p/2084227.html