📄 (ldd) ch03-字符设备驱动程序(转载).txt
字号:
尽管这总是操作在设备节点上的第一个操作,然而并不要求驱动程序一定要声明这个方
法。如果该项为NULL,设备的打开操作永远成功,但系统不会通知你的驱动程序。
void (*release)(struct inode *, struct file *);
当节点被关闭时调用这个操作。与open相仿,release也可以没有。在2.0和更早的核心
中,close系统调用从不失败;这种情况在版本2.1.31中有所变化(见第17章)。
int (*fsync)(struct inode *, struct file *);
刷新设备。如果驱动程序不支持,fsync系统调用返回-EINVAL。
int (*fasync)(struct inode *, struct file *, int);
这个操作用来通知设备它的FASYNC标志的变化。异步触发是比较先进的话题,将在第5章
的“异步触发”一节中介绍。如果设备不支持异步触发,该字段可以是NULL。
int (*check_media_change)(kdev_t dev);
check_media_change只用于块设备,尤其是象软盘这类可移动介质。内核调用这个方法
判断设备中的物理介质(如软盘)自最近一次操作以来发生了变化(返回1)或是没有(
0)。字符设备无需实现这个函数。
int (*revalidate)(kdev_t dev);
这是最后一项,与前面提到的那个方法一样,也只适用于块设备。revalidate与缓冲区
高速。缓存有关。我们将在第12章“加载块设备驱动程序”的“可移动设备”中介绍rev
alidate。
scull驱动程序中适用的file_operations结构如下:
(代码)
(代码)
在最新的开发用内核中,某些原型已经发生了变化。该列表是从2.0.x的头文件中提炼出
来的,这里给出的原型对于大多数内核而言都是正确的。内核2.1引入的变化(以及为了
使我们的模块可移植所进行的修改)在针对不同操作的每一节和第17章的“文件操作”
中详细介绍。
file结构
在<linux/fs.h>中定义的struct file是设备驱动程序所适用的又一个最重要的数据结构
。注意,file与用户程序中的FILE没有任何关联。FILE是在C库中定义且从不出现在内核
代码中。而struct file是一个内核结构,从不出现在用户程序中。
file结构代表一个“打开的文件”。它有内核在open时创建而且在close前做为参数传递
给如何操作在设备上的函数。在文件关闭后,内核释放这个数据结构。一个“打开的文
件”与由struct inode表示的“磁盘文件”有所不同。
在内核源码中,指向struct file的指针通常称为file或filp(“文件指针”)。为了与
这个结构相混淆,我将一直称指针为filp-flip是一个指针(同样,它也是设备方法的
参数之一),而file是结构本身。
struct file中的最重要的字段罗列如下。与上节相似,这张列表在首次阅读时可以略过
。在下一节中,我们将看到一些真正的C代码,我将讨论某些字段,到时你可以反过来查
阅这张列表。
阅这张列表。
mode_t f_mode;
文件模式由FMODE_READ和FMODE_WRITE标别。你可能需要在你的ioctl函数中查看这个域
来来检查读/写权限,但由于内核在调用你的驱动程序的read和write前已经检查了权限
,你无需检查在这两个方法中检查权限。例如,一个不允许的写操作在驱动程序还不知
道的情况下就被已经内核拒绝了。
loff_t f_ops;
当然读/写位置。loff_t是一个64位数值(用gcc的术语就是long long)。如果驱动程序
需要知道这个值,可以直接读取这个字段。如果定义了lseek方法,应该更新f_pos的值
。当传输数据时,read和write也应该更新这个值。
unsigned short f_flags;
文件标志,如O_RDONLY,O_NONBLOCK和O_SYNC。驱动程序为了支持非阻塞型操作需要检
查这个标志,而其他标志很少用到。注意,检查读/写权限应该查看f_mode而不是f_flag
s。所有这些标志都定义在<linux/fcntl.h>中。
struct inode *f_inode;
打开文件所对应的i节点。inode指针是内核传递给所有文件操作的第一个参数,所以你
一般不需要访问file结构的这个字段。在某些特殊情况下你只能访问struct file时,你
可以通过这个字段找到相应的i节点。
struct file_operations *f_op;
与文件对应的操作。内核在完成open时对这个指针赋值,以后需要分派操作时就读这些
数据。filp->f_op中的值从不保存供以后引用;这也就是说你可以在需要的事后修改你
的文件所对应的操作,下一次再操作那个打开文件的相应操作时就会调用新方法。例如
,主设备号为1的设备(/dev/null,/dev/zero等等)的open代码根据要打开的次设备号
替换filp->f_op中的操作。这种技巧有助于在不增加系统调用负担的情况下方便识别主
设备号相同的设备。能够替换文件操作的能力在面向对象编程技术中称为“方法重载”
。
void *private_data;
系统调用open在调用驱动程序的open方法前将这个指针置为NULL。驱动程序可以将这个
字段用于任意目的或者忽略简单忽略这个字段。驱动程序可以用这个字段指向已分配的
数据,但是一定要在内核释放file结构前的release方法中清除它。private_data是跨系
统调用保存状态信息的非常有用的资源,在我们的大部分样例都使用了这个资源。
实际的结构里还有其他一些字段,但它们对于驱动程序并不是特别有用。由于驱动程序
实际的结构里还有其他一些字段,但它们对于驱动程序并不是特别有用。由于驱动程序
从不填写file结构;它们只是简单地访问别处创建的结构,我们可以大胆地忽略这些字
段。
Open和Close
现在让我们已经走马观花地看了一遍这些字段,下面我们将开始在实际的scull函数中使
用这些字段。
Open方法
open方法是驱动程序用来为以后的操作完成初始化准备工作的。此外,open还会增加设
备计数,以便防止文件在关闭前模块被卸载出内核。
在大部分驱动程序中,open完成如下工作:
l 检查设备相关错误(诸如设备未就绪或相似的硬件问题)。
l 如果是首次打开,初始化设备。
l 标别次设备号,如有必要更新f_op指针。
l 分配和填写要放在filp->private_data里的数据结构。
l 增加使用计数。
l 增加使用计数。
在scull中,上面的大部分操作都要依赖于被打开设备的次设备号。因此,首先要做的事
就是标别要操作的是哪个设备。我们可以通过查看inode->i_rdev完成。
我们已经谈到内核是如何不使用次设备号的了,因此驱动程序可以随意使用次设备号。
事实上,利用不同的次设备号访问不同的设备,或以不同的方式打开同一个设备。例如
,/dev/ttyS0和/dev/ttyS1是两个不同的串口,而/dev/cua0的物理设备与/dev/ttyS0相
同,仅仅是操作行为不同。cua是“调出”设备;它们不是终端,而且它们也没有终端所
需要的所有软件支持(即,它们没有加入行律*)。所有的串口设备都有许多不同的次设
备号,这样驱动程序就区分它们了:ttyS与cua不一样。
驱动程序从来都不知道被打开的设备的名字,它仅仅知道设备号――而且用户可以按照
自己的规范给用设备起别名,而完全不用原有的名字。如果你看看/dev目录就会知道,
你将发现对应相同主/次设备号的不同名字;设备只有一个而且是相同的,而且没有方法
区分它们。例如,在许多系统中,/dev/psaux和/dev/bmouseps2都存在,而且它们有同
样的设备号;它们可以互换使用。后者是“历史遗迹”,你的系统里可以没有。
scull驱动程序是这样使用次设备号的:最高4位标别设备类型个体(personality),如
果该类型可以支持多实例(scull0-3和scullpipe0-3),低4位可以供你标别这些设备。
因此,scull0的高4位与scullpipe0不同,而scull0的低4位与scull1不同*。源码中定义
了两个宏(TYPE和NUM)从设备号中分解出这些位,我们马上就看到这些宏。
对于每一设备类型,scull定义了一个相关的file_operations结构,并在open时替换fil
p->f_op。下面的代码就是位切分和多fops是如何实现的:
(代码)
内核根据主设备号调用open;scull用上面给出的宏处理次设备号。接着用TYPE索引scul
l_fop_array数组,从中分解出被打开设备的方法集。
我在scull中所做的就是根据次设备号的类型给filp->f_op赋上正确的值。然后调用新的
fops中定义的open方法。通常,驱动程序不必调用自己的fops,它只有内核分配正确的
驱动程序方法时调用。但当你的open方法不得不处理不同设备类型时,在根据被打开设
备次设备号修改fops指针后就需要调用fops->open了。
scull_open的实际代码如下。它使用了前面那段代码中定义的TYPE和NUM两个宏来切分次
设备号:
(代码)
这里给一点解释。用来保存内存区的数据结构是Scull_Dev,这里简要介绍一下。Scull_
Dev和scull_trim(“Scull的内存使用”一节中讨论)的内部结构这里并没有使用。全
局变量scull_nr_devs和scull_devices[](全部小写)分别是可用设备数和指向Scull_D
ev的指针数组。
ev的指针数组。
这段代码看起来工作很少,这是因为当调用open时它没做任何针对某个设备的处理。由
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -