HyperNews Linux KHG 讨论页面

块设备驱动程序

[注意:自从块设备接口为了支持块设备可加载模块而进行更改后,本文档就未更新。这些更改不应使您无法应用本文档中的任何内容...]

要在设备上挂载文件系统,它必须是由块设备驱动程序驱动的块设备。这意味着该设备必须是随机访问设备,而不是流设备。换句话说,您必须能够随时寻址到物理设备上的任何位置。

你不提供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()调用,驱动程序必须调用它来说明它存在、正在工作并且处于活动状态。

文件 blk.h

在你的驱动程序代码的顶部,在所有其他包含的头文件之后,你需要编写两行代码

#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_ONDEVICE_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使你的驱动程序超时。当然,你的驱动程序必须能够处理被定时器超时的可能性。

识别 PC 标准分区

[检查 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。


消息

1. Idea: 非块缓存块设备? Neal Tucker
2. Idea: 我应该解释电梯算法(+锯齿等)吗? Michael De La Rue