📄 (ldd) ch12-加载块设备驱动程序(转载).txt
字号:
(LDD) Ch12-加载块设备驱动程序(转载)
第十二章 加载块设备驱动程序
正如在第一章“Linux核心简介”中“设备与模块的分类”中所概述的一样,Unix的设备
驱动程序并不仅限于字符设备。本章就来介绍一下第二大类的设备驱动程序——块设备
驱动程序。所谓面向块的设备是指数据传输是以块为单位的(例如软盘和硬盘),这里
硬件的块一般被称作“扇区(Sector)”。而名词“块”常用来指软件上的概念:驱动
程序常常使用1KB大小的块,即使扇区大小为512字节。
在这一章,我们将来构造一个全特征的块设备驱动程序sbull(Simple Block Utility
for Loading Localities)。这个驱动程序与scull类似,也是使用计算机的内存作为硬
件设备。换句话说,它是一个RAM-disk的驱动程序。sbull可以在任何Linux计算机上执
行(不过我只在有限的几个平台上作过测试)。
行(不过我只在有限的几个平台上作过测试)。
注册驱动程序
和字符设备驱动程序类似,核心里的块设备驱动程序也是由一个主设备号来标识。用来
对其进行注册和取消注册的函数是:
int register_blkdev(unsigned int major, const char*name, struct
file_operations *fops)
int unregister_blkdev(unsigned int major, const char*name);
参数的含义与字符设备驱动程序一样,对主设备号的动态赋值也类似。因此,一个sbull
设备与scull一样将自己注册:
result=register_blkdev(sbull_major,“sbull”,$sbull_fops);
if(result<0){
printk(KERN_WARNING“sbull:can’t get major %d\n”,sbull_major);
return result;
}
if (sbull_major==0) sbull_major=result; /*dynamic*/
major=sbull_major; /*Use “major”later on to save
typing*/
register_blkdev 的fops参数与我们在字符设备驱动程序中使用的类似,为read,write
以及fsync的操作并不要求针对某个驱动程序。通用函数block_read, block_write及blo
ck_fsync被用来代替任何针对某个驱动程序的函数。另外,check_media_change和reval
idate对块设备驱动程序也有意义,二者都在sbull_fops中定义。
在sbull中使用的fops结构如下:
(代码236)
通用的读写操作被用来获得较高的性能。通过数据缓冲获得加速,这在字符设备驱动程
序重中是没有的。块设备驱动程序可以被缓冲是因为它们的数据服从于计算机的文件层
次结构,任何应用程序都无法直接访问,而字符设备驱动程序则不是这样。
不过,当缓冲的高速缓存不能满足一个读请求或当一个待处理的写操作要刷新到物理磁
盘上时,驱动程序必须被调用来进行真正的数据传送。fops结构除了read和write外,并
盘上时,驱动程序必须被调用来进行真正的数据传送。fops结构除了read和write外,并
不带有入口点,因此,必须要一个额外的结构blk_dev_struct来发出对实际数据传送的
请求。
这个结构在<linux/blkdev.h>定义,它有几个域,但只有第一个域需被驱动程序设置。
下面是这个结构在核心2.0中的定义。
(代码237)
当核心需要为sbull设备产生一个I/O操作时,它便调用函数blk_dev[sbull_major].requ
est_fn。因此这个模块的初始化函数须设置这个域使其指向它自己的请求函数。这个结
构中的其它域只供核心函数或宏进行内部使用;你不必在你的代码段中显式地使用它们
。
一个块设备驱动程序模块与核心的关系见图12-1。
除了blk_dev还有几个数组带有块设备驱动程序的信息。这些数组一般由主设备号(有时
也用次设备号)进行索引。它们在drivers/block/ll_rw_block.c中被声明和描述。
int blk_size[][];
这个数组由主设备号和次设备号索引。它以KB为单位描述了每个设备的大小。如果blk_s
ize[major]是NULL,则不对这个设备的大小进行检查(也就是说,核心可能要求数据传
ize[major]是NULL,则不对这个设备的大小进行检查(也就是说,核心可能要求数据传
送通过end_of_device)。
int blksize_size[][];
被每个设备所使用的块的大小,以字节为单位。与上一个数组类似,这个二维数组也是
由主设备号和次设备号索引。如果blksize_size[major]是一个空指针,那么便假设其块
大小为BLOCK_SIZE(目前是1KB)。块大小必须是2的幂,因为核心使用移位操作将偏移
量转换为块号。
int hardsect_size[][];
与其它的一样,这个数据结构也是由主设备号和次设备号索引。硬件扇区的缺省大小为5
12字节。直到包括2.0.X版本为止,可变扇区大小仍未真正支持,因为一些核心代码仍旧
假设扇区大小为半KB。不过很可能在2.2版本中会真正实现可变扇区大小。
int read_ahead[];
这个数组由主设备号索引,它定义了一个文件被顺序读取时,核心可以提前读取多少扇
区。在进程请求数据之前将其读出可以改善系统的性能及总的吞吐率。慢速设备最好指
定一个较大的提前读的值,而一个快速设备则可以在较小的提前读的值下工作的很好。
这个提前读的值越大缓冲高速缓存则需要越多的内存。每个主设备号有一个提前读的值
,它对所有次设备号有效。这个值可以通过驱动程序的ioctl方法来改变;硬盘驱动程序
,它对所有次设备号有效。这个值可以通过驱动程序的ioctl方法来改变;硬盘驱动程序
一般设为8个扇区,对应着4KB。
sbull设备允许在加载时设置这些值,它们作用于示例驱动程序的所有次设备号。在sbul
l中变量名和它们的缺省值为:
size=2048(KB)
由sbull生成的每个ramdisk占两兆字节,正如系统的缺省值。
hardsect=512(B)
sbull扇区大小是常用的半KB值。改变hardsect的值是不允许的。
如前所述,其它的扇区大小并不被支持。如果你一定要改它,可以将sbull/sbull.c中的
安全检查去掉。不过请做好发生严重的内存崩溃的危险的准备。除非在你尝试时,已经
加上了对可变扇区大小的支持。
rahead=2(扇区)
因为ramdisk是一个快速设备,所以这个缺省提前读的值比较小。
sbull设备也允许你选择一个设备个数进行安装。devs是设备个数,缺省设为2,表明缺
sbull设备也允许你选择一个设备个数进行安装。devs是设备个数,缺省设为2,表明缺
省内存使用量为4兆——2个大小为2MB的盘。
sbull设备的init_module的实现如下(不含主设备号的注册和错误恢复):
(代码239)
相应的清除函数如下所示:
(代码240)
这里,调用fsync_dev是必须的,用以清除核心保存在不同高速缓存中的对设备的所有引
用。事实上,fsync_dev是运行在block--_fsync之后的引擎,它是块设备的fsync“方法
”。
头文件 blk.h
由于块设备驱动程序的绝大部分是设备无关的,核心的开发者通过把大部分相同的代码放
在一个头文件<linux/blk.h>中,来试图简化驱动程序的代码。因此,每个块设备驱动程
序都必须包含这个头文件,在<linux/blk.h>中定义的最重要的函数是end_request,它
被声明为static(静态)的。让它成为静态的,使得不同驱动程序可有一个正确定义的e
nd_request,而不需要每个都写自己的实现。
在Linux1.2中,这个头文件应该用<linux/../../drivers/block/blk.h>来包含。原因在
于当时还不支持自定义的块设备驱动程序,而这个头文件最初位于drivers/block源码目
录下。
实际上,blk.h相当不寻常,比如它定义了几个基于符号MAJOR_NR的符号,而MAJOR_NR必
须由驱动程序在它包含这个头文件之前声明。这里,我们再次看到blk.h在设计时并没有
真正考虑自定义驱动程序。
看看blk.h,你会发现几个设备相关的符号是按照MAJOR_NR的值声明的,也就是说MAJOR_
NR应该提前知道。然而,如果主设备号是动态赋值的,驱动程序无法预知其值,因此也
就不能正确定义MAJOR_NR。如果MAJOR_NR未定义,blk.h就不能设定一些在end_request
中使用的宏。因此,为了让自定义驱动程序从通用的end_request函数受益,从而避免重
新实现它,驱动程序必须在包含blk.h之前定义MAJOR_NR和其它几个符号。
下面的列表描述了一些必须提前定义的<linux/blk.h>中的符号。列表结尾给出了sbull
中使用的代码。
MAJOR_NR
这个符号用来访问一些数组,特别是blk-_dev和blksize-_size。自定义驱动程序(如sb
ull)不能给这个符号赋一个常量值,可以将其定义(#define)为一个存有主设备号的
变量。对sbull而言,它是sbull-_major。
变量。对sbull而言,它是sbull-_major。
DEVICE_NAME
被生成的设备名。这个字符串用来从end_request中打印错误信息。
DEVICE_NR(kdev_t device)
这个符号用来从kdev_t设备号中抽取物理设备的序号。这个宏的值可以是MINOR(device)
或别的表达式。这要依据给设备或分区分配次设备号的常规方式而定。对同一个物理设
备上的所有分区,这个宏应返回同一个设备号——也就是说,DEVICE_NR表达的是磁盘号
,而不是分区号。这个符号被用来声明CURRENT_DEV,它在request_fn中用来确定被一个
传送请求访问的硬件设备的次设备号,可分区设备将在后面“可分区设备”一节中介绍
。
DEVICE_INTR
这个符号用来声明一个指向当前下半部处理程序的指针变量。宏SET_INTR(intr)和CLEAR
_INTR用来给这个变量赋值。当设备可以发出具有不同含义的中断时,使用多个处理程序
是很方便的。这个主题将在后面“中断驱动的块设备驱动程序”一节中讨论。
TIMEOUT_VALUE
DEBICE_TIMEOUT
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -