2019.05.08 《Linux驱动开发入门与实战》
第六章:字符设备
申请设备号---注册设备
1、字符设备的框架:
2、结构体,struct cdev:
3、字符设备的组成:
4、例子:
5、申请和释放设备号:
设备号和设备节点是什么关系。?
设备驱动中,很多功能是通过设备号完成的。
步骤:
构建字符设备前,应该申请设备号:所用到的函数是下面两个:
该函数在<fs/char_dev.c>中定义
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor,unsigned count, const char *name) ;
释放设备号,统一使用下面的函数,通常在模块的卸载函数中会调用这个。
void unregister_chrdev_region(dev_t from, unsigned count);
申请字符设备的设备号后,需要将字符设备注册到系统中。这样才能使用字符设备。
为了实现这个过程,需要先解释cdev结构体。cdev结构体描述字符设备,该结构体是
所有字符设备的抽象,其包含了大量字符设备所共有的特性。
cdev中kobj成员,用于内核管理字符设备,驱动开发基本不用。
cdev中ops指向的file_operations结构的指针,该结构定义了操作字符设备的函数。
cdev中dev是用来存储字符设备所申请的设备号。
cdev中count表示目前有多少个字符设备在使用改启动程序。
cdev中list是一个双向链表,用于将其他结构体链接成一个双向链表。这种结构在linux中常用,需要掌握。
每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应的字
符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点。这样可以通过
inode结点的i_cdev字段找到cdev字符结构体。通过cdev的ops指针,就能找到设备A的
操作函数。对操作函数的讲解,将放在后面的内容中。
file_operations是一个对设备进行操作的抽象结构体。Linux内核的设计非常巧妙。内
核允许为设备建立一个设备文件,对设备文件的所有操作,就相当于对设备的操作。这
样的好处是,用户程序可以使用访问普通文件的方法访问设备文件,进而访问设备。这
样的方法,极大地减轻了程序员的编程负担,程序员不必去熟悉新的驱动接口,就能够
访问设备。
对普通文件的访问常常使用open()、read()、write()、close()、ioctl()等方法。同
样对设备文件的访问,也可以使用这些方法。这些调用最终会引起对
file_operations结构体中对应函数的调用。对于程序员来说,只要为不同的设备编写
不同的操作函数就可以了。
为了使file_operations结构体具有通用性,file_operations会不断地扩充,现在已经非常
庞大,只需要对file_operations结构体中几个重要的成员进行分析:
owner成员根本不是一个函数;它是一个指向拥有这个结构模块的指针。这个成员用来
维持模块的引用计数,当模块还在使用时,不能用rmmod卸载模块。几乎所有时刻,它
被简单初始化为 THIS_MODULE,一个在<linux/module.h>中定义的宏。
llseek()函数用来改变文件中的当前读/写位置,并将新位置返回。loff_t参数是一个
"long long"类型,"long long"类型即使在32位机上也是64位宽。这是为了与64位机兼
容而定的,因为64位机的文件大小完全可以突破4G。
read()函数用来从设备中获取数据,成功时函数返回读取的字节数,失败时返回一个负
的错误码。
write()函数用来写数据到设备中。成功时该函数返回写入的字节数,失败时返回一个
负的错误码。
ioctl()函数提供了一种执行设备特定命令的方法。例如使设备复位,这既不是读操作
也不是写操作,不适合用read()和write()方法来实现。如果在应用程序中给ioctl传入
没有定义的命令,那么将返回-ENOTTY的错误,表示该设备不支持这个命令。
open()函数用来打开一个设备,在该函数中可以对设备进行初始化。如果这个函数被复
制NULL,那么设备打开永远成功,并不会对设备产生影响。
release()函数用来释放open()函数中申请的资源,将在文件引用计数为0时,被系统调
用。其对应应用程序的close()方法,但并不是每一次调用close()方法,都会触发
release()函数,在对设备文件的所有打开都释放后,才会被调用。
一般来说,驱动开发人员会将特定设备的特定数据放到cdev结构体后,组成一个新的结
构体。如图6.3所示,"自定义字符设备"中就包含特定设备的数据。该"自定义设备"中
有一个cdev结构体。cdev结构体中有一个指向file_operations的指针。这里
,file_operations中的函数就可以用来操作硬件,或者"自定义字符设备"中的其他数
据,从而起到控制设备的作用。
inode结构体 【这个可以理解为设备节点?】
内核使用inode结构在系统内部表示文件。inode一般作为file_operations结构中函数的参
数传递过来。例如,open()函数将传递一个inode指针进来,表示目前打开的文件结点
。需要注意的是,inode的成员已经被系统赋予了合适的值,驱动程序只需要使用该结
点中的信息,而不用更改。Oepn()函数为:int (*open) (struct inode *, struct file *);
inode结构中包含大量的有关文件的信息。这里,只对编写驱动程序有用的字段进行介
绍,对于该结构更多的信息,可以参看内核源码。
dev_t i_rdev,表示设备文件对应的设备号。
struct list_head i_devices,如图6.2所示,该成员使设备文件连接到对应的cdev结
构,从而对应到自己的驱动程序。
struct cdev *i_cdev,如图6.2所示,该成员也指向cdev设备。
除了从dev_t得到主设备号和次设备号外,这里还可以使用imajor()和iminor()函数从
i_rdev中得到主设备号和次设备号。
在Linux系统中,字符设备驱动程序由以下几个部分组成。
字符设备加载和卸载函数
在字符设备的加载函数中,应该实现字符设备号的申请和cdev的注册。相反,在字符设
备的卸载函数中应该实现字符设备号的释放和cdev的注销。
cdev是内核开发者对字符设备的一个抽象。除了cdev中的信息外,特定的字符设备还需
要特定的信息,常常将特定的信息放在cdev之后,形成一个设备结构体,如代码中的
xxx_dev。
file_operations结构体和其成员函数
驱动程序与应用程序的数据交换
字符设备是3大类设备(字符设备、块设备、网络设备)中较简单的一类设备,其驱动
程序中完成的主要工作是初始化、添加和删除cdev结构体,申请和释放设备号,以及填
充file_operation结构体中操作函数,并实现file_operations结构体中的read()、
write()、ioctl()等重要函数。如图6.4所示为cdev结构体、file_operations和用户空
间调用驱动的关系。