[注意:自从块设备接口为了支持块设备可加载模块而进行更改后,本文档就未更新。这些更改不应使您无法应用本文档中的任何内容...]
要在设备上挂载文件系统,它必须是由块设备驱动程序驱动的块设备。这意味着该设备必须是随机访问设备,而不是流设备。换句话说,您必须能够随时寻址到物理设备上的任何位置。
你不提供read()和write()块设备的例程。相反,你的驱动程序使用block_read()和block_write(),它们是由 VFS 提供的通用函数,VFS 将调用 strategy 例程,或者request()函数,你编写它来代替read()和write()用于你的驱动程序。这个 strategy 例程也由 buffer cache 调用,buffer cache 由 VFS 例程调用,这就是普通文件系统上的普通文件被读取和写入的方式。
I/O 请求由缓冲区缓存提供给一个名为ll_rw_block(),它构建请求列表,这些列表由 elevator algorithm 排序,该算法对列表进行排序,以使访问更快更有效。反过来,它调用你的request()函数来实际执行 I/O。
请注意,尽管 SCSI 磁盘和 CD-ROM 被认为是块设备,但它们是经过特殊处理的(所有 SCSI 设备都是如此)。有关详细信息,请参阅 编写 SCSI 驱动程序。(尽管 SCSI 磁盘和 CD-ROM 是块设备,但 SCSI 磁带,像其他磁带一样,通常是字符设备。)
块设备的初始化比字符设备的初始化稍微复杂一些,特别是某些“初始化”必须在编译时完成。还有一个register_blkdev()调用,它对应于字符设备register_chrdev()调用,驱动程序必须调用它来说明它存在、正在工作并且处于活动状态。
在你的驱动程序代码的顶部,在所有其他包含的头文件之后,你需要编写两行代码
#define MAJOR_NR DEVICE_MAJOR #include "blk.h"其中DEVICE_MAJOR是你设备的主设备号。drivers/block/blk.h 需要使用MAJOR_NR定义来为你的驱动程序设置许多其他定义和宏。
现在你需要编辑 blk.h。在#ifdef MAJOR_NR,有一个定义部分,这些定义是为某些主设备号有条件地包含的,受以下保护#elif (MAJOR_NR == DEVICE_MAJOR)。在此列表的末尾,你将为你的驱动程序添加另一个部分。在该部分中,需要以下行
#define DEVICE_NAME "device" #define DEVICE_REQUEST do_dev_request #define DEVICE_ON(device) /* usually blank, see below */ #define DEVICE_OFF(device) /* usually blank, see below */ #define DEVICE_NR(device) (MINOR(device))
DEVICE_NAME只是设备名称。有关示例,请参阅 blk.h 中的其他条目。
DEVICE_REQUEST是你的 strategy 例程,它将处理设备上的所有 I/O。有关 strategy 例程的更多详细信息,请参阅 策略例程。
DEVICE_ON和DEVICE_OFF用于需要打开和关闭的设备,例如软盘。事实上,软盘驱动程序是目前唯一使用这些定义的设备驱动程序。
DEVICE_NR(device)用于从次设备号确定物理设备的编号。例如,在hd驱动程序中,由于第二个硬盘驱动器从次设备号 64 开始,DEVICE_NR(device)定义为(MINOR(device)>>6).
如果你的驱动程序是中断驱动的,你还需要设置
#define DEVICE_INTR do_dev它将成为一个变量,该变量由 blk.h 的其余部分自动定义和使用,特别是通过SET_INTR()和CLEAR_INTR宏。
你可能还会考虑设置这些定义
#define DEVICE_TIMEOUT DEV_TIMER #define TIMEOUT_VALUE n其中n是 jiffies 的数量(时钟节拍;Linux/386 上为百分之一秒;Linux/Alpha 上约为千分之一秒),如果在没有收到中断的情况下超时。如果你的设备可能“卡住”,则使用这些:驱动程序无限期等待永远不会到达的中断的情况。如果你定义了这些,它们将自动用于SET_INTR使你的驱动程序超时。当然,你的驱动程序必须能够处理被定时器超时的可能性。
[检查 genhd.c 中的例程,并包含详细、正确的说明,说明如何使用它们来允许你的设备使用标准的 dos 分区方案。到现在为止,也支持 bsd disklabel 和 sun 的 SMD 标签,但我仍然没有抽出时间来记录这一点。我很惭愧——但人们似乎已经能够弄清楚了:-)]
[这里应该简要解释一下 ll_rw_block() 是如何被调用的,关于 getblk() 和 bread() 和 breada() 和 bwrite() 等等。缓冲区缓存的真正解释保留在 VFS 参考部分。Jean-Marc Lugrin 写了一个,但我现在找不到他了。]
所有块的读取和写入都是通过 strategy 例程 完成的。此例程不接受任何参数,也不返回任何内容,但它知道在哪里可以找到 I/O 请求列表 (CURRENT,默认定义为blk_dev[MAJOR_NR].current_request),并且知道如何将数据从设备获取到块中。它在中断 禁用 的情况下被调用,以避免竞争条件,并负责通过调用以下命令来启用中断sti()然后返回。
strategy 例程首先调用INIT_REQUEST宏,它确保请求确实在请求列表中,并进行其他一些健全性检查。add_request()将已经根据电梯算法以正确的顺序对请求进行了排序(使用插入排序,因为它为每个请求调用一次),因此 strategy 例程“仅仅”必须满足请求,调用end_request(1),它将从列表中删除请求,然后如果列表上仍然有另一个请求,则满足它并调用end_request(1), 直到列表上没有更多请求为止,此时它返回。
如果驱动程序是中断驱动的,strategy 例程只需要调度第一个请求发生,并让中断处理程序调用end_request(1)并再次调用 strategy 例程,以便调度下一个请求。如果驱动程序不是中断驱动的,则 strategy 例程可能要等到所有 I/O 完成后才能返回。
如果由于某种原因,I/O 在当前请求上永久失败,end_request(0)必须调用它来销毁请求。
请求可以是读取或写入。驱动程序通过检查以下内容来确定请求是读取还是写入CURRENT->cmd。如果CURRENT->cmd == READ,则请求是读取,如果CURRENT->cmd == WRITE,则请求是写入。如果设备有单独的中断例程来处理读取和写入,SET_INTR(n)必须调用它以确保将调用正确的中断例程。
[在这里,我需要包含轮询 strategy 例程和中断驱动例程的示例。中断驱动的例程应提供单独的读取和写入中断例程,以显示SET_INTR.]
版权 (C) 1992, 1993, 1994, 1996 Michael K. Johnson, johnsonm@redhat.com。