📄 (ldd) ch12-加载块设备驱动程序(转载).txt
字号:
当你做完了集簇请求,CURRENT->bh必须被更新以指向“已经被处理但未被解锁”的第一
个缓冲区。如果列表中所有的缓冲区都已被处理和解锁,CURRENT->bh可被置为NULL。
此时,驱动程序可以调用end_request。如果CURRENT->bh是有效的,那么这个函数在转
到下一个缓冲之前对其进行解锁——这是非集簇操作所发生的情况,此时由end_request
照管所有的事情。如果指针为空,这个函数直接转到下一个请求。
全功能的集簇实现出现在driver/block/floppy.c,而要求的所有操作出现在blk.h的end
_request中。floppy.c和blk.h都不容易理解,不过建议先从后者开始。
安装(Mounting)是如何工作的
块设备与字符设备及一般文件的不同在于它们可以被安装到计算机的文件系统上。这与
一般的访问方式不同。一般的访问方式通过结构file进行,这个结构与特定的进程相关
联,并且只在open到close之间存在。当一个文件系统被安装后,没有进程拥有一个filp
。
当核心把一个设备安装到文件系统上,它调用一般的open方法来访问驱动程序。然而,
这种情况下open的参数filp是个虚的变量,几乎只是为了占个地方,它唯一有意义的域
是f_mode。其它域含任意值并不使用。f_mode的值是告诉驱动程序设备是以只读(f_mod
e==FMODE_READ)还是读写(f_mode==(FMODE_READ|FMOD_WRITE))方式被安装。使用
一个虚变量而不是file结构的原因是因为实际的结构file在进程结束时将被释放,而被
一个虚变量而不是file结构的原因是因为实际的结构file在进程结束时将被释放,而被
安装的文件系统在mount命令完成后仍然存在。
在安装时,驱动程序唯一调用的是open方法。当磁盘被安装后,核心调用设备中的read
和write 方法(被映射到request_fn)来管理文件系统中的文件。驱动程序并不知道req
uest_fn服务的是一个进程(象fsck)还是核心中的文件系统层。
至于umount,它只是刷新缓冲高速缓存并调用驱动程序的release(close)方法。由于
没有有意义的filp可以传递给fop->realse,核心使用NULL。
因此,当你实现release时,你应将驱动程序设为能处理为NULL的filp指针。不然,如果
你用了filp,你可能运行mkfs和fsck,它们都使用filp来访问设备,你也可能mount这个
设备,但umount将无法运行,原因就是NULL指针。
由于一个块设备驱动程序的release实现不能用filp->private_data来访问设备信息,它
采用inode->i_rdev来区分设备。这里是release的sbull实现:
(代码249)
其它的驱动程序函数并不关心filp问题,因为它们与安装的文件系统无关。例如,一个
显示地open这个设备的进程只发出ioctl。
ioctl方法
ioctl方法
如字符设备一样,块设备也可以通过ioctl系统调用进行操作。两者之间相对不一样的地
方在于块设备驱动程序有大量驱动程序都要支持的ioctl命令。
块设备驱动程序经常要处理的命令如下所示,它们在<linux/fs.h>中被声明。
BLKGETSIZE
获取当前设备的大小,以扇区数表示。由系统调用传递的 数值arg是一个指向long数值
的指针,用来将大小拷贝到一个用户空间的变量中。这个ioctl命令可以被mkfs用来获知
产生的文件系统的大小。
BLKFLSBUF
字面上的意思是“刷新缓冲区”。这个命令的实现对每个设备都是一样的,我们将在后
面整个ioctl方法的示例代码中给出来。
BLKRAGET
用来为设备取得当前提前读的值。当前数值应该用在参数arg中传递给ioctl的指针写进
一个long类型的用户空间变量。
BLKRASET
设置提前读的值。用户进程在arg中传递这个新值。
BLKRRPART
重读分区表。这个命令只对可分区设备有意义,将在后面“可分区设备”中介绍。
BLKROSET
BLKROGET
这些命令用来改变和检查设备的只读标志。因为代码是设备无关的,它们由宏RO_IOCTLS
(kdev_tdev,unsigned long where)来实现。这个宏在blk.h中定义。
HDIO_GETGEO
在<linux/hdreg.h>中定义,用来获得磁盘的几何参数。这个参数应被写入用户空间的结
构hd_geometry中,它也在hdreg.h中定义。sbull显示了这个命令的一般实现。
HDIO_GETGEO是<linux/hdreg.h>中定义的一系列HDIO命令中最常用的一个。感兴趣的读
者可以查看ide.c和hd.c以获得这些命令的更多信息。
者可以查看ide.c和hd.c以获得这些命令的更多信息。
这里列出的这些命令的一个主要缺点是它们是以“老”方法定义的(是第五章“增强的
字符设备驱动程序操作”中“选择ioctl命令”一节),因此无法使用位域的宏来减化代
码——每个命令要实现它自己的verify_area。不过,如果一个驱动程序需要定义它自己
的命令来利用设备的一些特殊特点,你可以自由地使用“新”方法来定义命令。
sbull设备只支持上面的通用命令,因为实现设备特定的命令与实现字符设备驱动程序的
命令没有什么不同。sbull的ioctl实现如下所示,它将有助于你理解上面列出的命令。
(代码250)
(代码251)
函数开始的PDEBUG语句被留出,这样当你编译这个模块时,你可以打开调试(debugging
)来看看设备上调用了哪个ioctl命令。
例如,对于显示的ioctl命令,你可以在sbull上使用fdisk。下面是在我自己系统上的一
个示例执行过程:
(代码252 1#)
在会话过程中下面的消息出现在我的系统日志中:
在会话过程中下面的消息出现在我的系统日志中:
(代码252 2#)
第一个ioctl是HDIO_GETGEO,它在fdisk启动时被调用;第二个是BLKRRPART。对后一个
命令的sbull实现仅仅是调用一下revalidate函数,它则在打印输出中打印最后的消息(
见本章后面的“revalidate”)。
可拆卸的设备
在我们讨论字符设备驱动程序时,我们忽略了fops结构中的最后两个文件操作,因为它
们只是为可拆卸块设备而设的。现在是看看它们的时候了。sbull并不真是可拆卸的,但
它假装是,因此它实现了这些方法。
我所说的操作是check_media-_change和revalidate。前者用来发现设备自上次访问以来
是否改变过,后者则在磁盘变动之后重新初始化驱动程序的状态。
至于sbull,与设备相联的数据区在使用计数下降为零后半分钟要释放。待这个设备处于
未安装状态(或关闭状态)足够长的时间以模拟一次磁盘的改变,下一次对设备的访问
分配一个新的内存区域。
这一类的“时间到期”通过一个核心计数器来实现。
check_media_change
这个检查函数接收到kev_t做为一个确定设备的参数。如果介质被改变了返回值为1,否
则为0。如果一块设备驱动程序不支持可拆卸设备,可以通过置fops->check_media_chan
ge为NULL来避免这个声明函数。
有趣的是要注意,当一个设备是可拆卸的,但却无法判断它是否改变了,这时,返回1是
个安全选择。事实上,IDE驱动程序在处理可 鹦洞排淌 就是这么做的。
sbull的实现是这样的,当由于计数器超时,设备已经从内存中删除时就返回1,如果数
据仍然有效则返回0。如果设置了调试,它同时向系统日志打印一条消息,这样用户就可
以检查核心什么时候调用了这个方法。
(代码253 1#)
revalidate
这个有效化函数是在检测到一个磁盘的改变时被调用。它也被在核心的2.1版中实现的各
种stat系统调用。返回值目前不做使用;为安全起见,返回0表示成功,出错时返回一个
负的错误代码。
revalidate执行的动作是设备特定的,但revalidate通常更新一些内部状态信息以反映
新的设备。
新的设备。
在sbull中,revalidate方法在没有一个有效区域的情况下试图分配一块新的数据区域。
(代码253 2#)
(代码254 1#)
特别注意
当可拆卸设备已经打开时,驱动程序也应该检查是否有磁盘的改变;在mount时核心自动
调用它的check-_disk_change函数,但在open时,并不这样做。
不过,有些程序直接访问磁盘数据而不安装这个设备,fsck,mcopy和fdisk都是这类程
序的例子。如果驱动程序在内存中保存可拆卸设备的状态信息,它应在设备第一次打开
时调用check_disk_change函数。这个核心函数还要依赖驱动程序方法(check_media_ch
ange和revalidate),因此在open里不须实现任何特别的东西。
这里是open的sbull实现,它关注了发生磁盘改变的情况:
(代码254 2#)
在驱动程序中不需对磁盘的改变做任何别的。如果一个磁盘被改变了,而它的打开计数
大于零,那么数据会被破坏。防止这种情况发生的唯一方法是让利用在物理上支持的设
备使用计数控制门锁。open和close可以在合适的时候关闭或打开锁。
可分区设备
如果你想用fdisk生成分区,你会发现它们有一些问题。fdisk程序称这些分区为/dev/sb
ull01,/dev/sbull02以 此类推,但文件系统上并不存在这些名字。的确,基本的sbull
设备是一个字节阵列,不存在提供访问数据区域的子区域的入口点,因此想对sbull进行
分区是行不通的。
为了能对设备分区,我们必须给每个物理设备分配几个次设备号。一个数字用来访问整
个设备(如/dev/hda),其它的用来访问不同的分区(如/dev/hda1)。由于fdisk产生
分区名的办法是在全盘设备名后加一个数字后缀,我们将在后面的块设备驱动程序中遵
循同样的命名规则。
在本节中我将要介绍的设备叫spull,因此它是一个“简单的可分区工具(Simple
Partitionable Utility)”。这个设备位于spull目录,完全与sbull无关,尽管它们共
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -