15:02-16:47 90m 18p
第3章 字符驱动
模块化的字符驱动
什么是模块化?
scull的设计
编写驱动的第一步是定义驱动将要提供给用户程序的能力(机制)。
全局意味着如果设备被多次打开,设备中含有的数据由所有打开他的文件描述符共享。
永久意味着如果设备关闭又重新打开,数据不会丢失。
主次编号
支付设备通过文件系统的名字来存取。这些名字被称为文件系统的特殊文件,或设备文件。
传统上,主编号标识设备相连的驱动。
次编号被内核用来决定引用那个设备。
分配和释放设备编号
在建立一个字符驱动时需要做的第一件事是:获取一个或多个设备编号来使用。
【可以指定 主编号,也可以动态让内核给你分配】
不管你如何分配你的设备编号,你应当在不再使用它时释放它。
【这通常是在模块的cleanup 函数】
【为了避免冲突,应该使用动态分配来获取你的主设备编号】
动态分配的缺点是你无法提前创建设备节点
什么是设备节点?
作为scull源码的一部分,我们提过了一个相当完整和可配置的init脚本例子,称为scull.init。它接收传统的参数--start,stop和restart以完成 scull_load 和 scull_unload 的角色。
【init 脚本规范 】
安排住编号最好的方式,是缺省使用动态分配,而留给自己在加载时指定主编号的选择权。
【一个全局变量初始化为0,表示动态分配。 可以选择主编号,或者在编译前修改宏定义或者在insmod 或是用脚本load时指定一个编号。】
一些重要数据结构
文件操作
scull设备驱动只实现最重要的设备方法. 它的 file_operations 结构是如下初始化的:
structfile_operations scull_fops = {
.owner =THIS_MODULE,
.llseek =scull_llseek,
.read =scull_read,
.write =scull_write,
.ioctl =scull_ioctl,
.open =scull_open,
.release =scull_release,
};
这个声明使用标准的C标记式结构初始化语法。这种方式是首选的,因为它是驱动在结构定义的改变之间更加可移植,并且有争议地,使代码更加紧凑可读。
文件结构
struct file 和 定义在 C 库的 FILE 没有任何关系。
文件结构代表一个打开的文件。它由内核在open时创建,并传递给在文件上操作的任何函数,直到最后的关闭。在文件的所有实例都关闭后,内核释放这个数据结构。
inode 结构
作为一个更可移植的编程方法,应使用宏来从inode 获取主次编号
字符设备注册
内核在内部使用类型 scructcdev 结构来代表字符设备。
有两种方法来分配并注册一个或几个这样的结构。
分配一个独立的 cdev 结构用 cdev_alloc()
如果 cdev 结构嵌入到你设备的某个结构,则用 cdev_init()函数来初始化。
一旦 cdev 结构建立,最后的步骤是把他告诉内核,调用 cdev_add()
注意两点:
- cdev_add 可能失败
- 一旦成功,则内核可以调用它的操作。所以在你的驱动完全准备好之前,不要调用它。
open 和 release
open 方法
open应当进行下面的操作
- 检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误
- 如果它第一次打开, 初始化设备
- 如果需要, 更新 f_op 指针.
- 分配并填充要放进 filp->private_data 的任何数据结构
【应当使用 宏 container_of 来获得 指针,类型的转换】
release 方法
release 应该执行下面的任务:
- 释放open 分配在filp->private_data 中的任何东西
- 在最后的 close 关闭设备
不是每次close 系统调用都会引起调用release方法。只有真正释放设备数据结构的调用会执行该方法。
内核维持一个文件结构被使用多少次的计数器。fork和dup都不增加它,只有open增加它。每次close,计数器减少,直到为0 时执行release。
LDD3 D03 补
最新推荐文章于 2024-12-19 09:05:05 发布