📄 (ldd) ch12-加载块设备驱动程序(转载).txt
字号:
享很多代码。
在字符设备驱动程序scull中,不同的次设备号可以实现不同的行为,因此一个驱动程序
可以显示几种不同的实现。而按照次设备号区分块设备是不可行的,这就是为什么sbull
可以显示几种不同的实现。而按照次设备号区分块设备是不可行的,这就是为什么sbull
和spull被分离开。这种无能为力是块设备驱动程序的一个基本特征,因为几个数据结构
和宏只是作为主设备号的函数定义的。
关于移植,需要注意的是可分区模块不能被加载到核心的1.2版,因为符号resetup_one_
dev(在本节后面介绍)没有被引出到模块。在对SCSI盘的支持模块化之前,没有人会考
虑可分区的模块。
我要介绍的设备结点被称做pd,表示“可分区磁盘(partitionable disk)”。四个完
整的设备(又称“单元”)被称做/dev/pda直到/dev/pdd;每个设备最多支持15个分区
。次设备号有下面的含义:低四位表示分区号(0为完整的设备),高四位表示单元号。
这个规则在源文件中由下面的宏表达:
(代码255)
普通硬盘
每个可分区设备需要知道它是如何分区的。这个信息可以从分区表中得到。初始化进程
的一部分包括解码分区表,并更新内部数据结构以反映分区信息。
这个解码并不容易。不过幸运的是,核心提供可被所有块设备驱动程序使用的“普通硬
盘”支持,它显著地减少了处理分区驱动程序的代码。这个普通支持的另一个好处是驱
动程序的作者不必理解分区是如何完成的,而不需要修改驱动程序的代码就可以在核心
动程序的作者不必理解分区是如何完成的,而不需要修改驱动程序的代码就可以在核心
中支持新的分区方式。
想要支持分区的块设备驱动程序要包含<linux/genhd.h>,并声明结构gendisk。所有这
样的结构被组织在一个链表中,它的头是全局指针gendisk_head。
在我们进行下一步之前,让我们先看看结构gendisk的域。你为了利用普通设备支持就需
要理解它们。
int major
确定这个结构所指的设备驱动程序的主设备号。
const char*major_name
属于这个主设备号的设备的基本名。每个设备名是通过在这个名字后为每个单元加一个
字母并为每个分区加一个数字得到。例如,“hd”是用来构成/dev/hda1和/dev/hda3的
基本名。基本名最多5个字符长,因为add_partition在一个8字节的缓冲区中构造全名,
它要附加上一个确定单元的字母,分区号和一个终止符‘\0’。spull所用的名字是pd(
“可分区磁盘(partitionable disk)”)。
int minor_shift
从设备的次设备号中获取驱动器号要进行移位的次数。在spull中这个数是4。这个域中
的值应与宏DEVICE_NR(device)中的定义一致(见本章前面的“头文件blk.h”)。spull
中的宏扩展为device>>4。
int max_p
分区的最大数目。在我们的例子中,max_p1是16,或更一般地,是<<minor_shift。
int max-_nr
单元的最大数目。在spull中,这个数字是4。单元最大数目在移位minor_shift次后的结
果应匹配次设备号的可能的范围,目前是0-255。IDE驱动程序可以同时支持很多驱动器
和每一个驱动器很多分区,因为它注册了几个主设备号,从而绕过了次设备号范围小的
问题。
void(*init)(struct gendisk*)
驱动程序的初始化函数,它在初始化设备后和分区检查执行前被调用。我将在下面介绍
这个函数更多的细节。
struct hd_struct *part
设备的解码后的分区表。驱动程序用这一项确定通过每个次设备号哪些范围的磁盘扇区
是可以访问的。大多数驱动程序实现max_nr<<minor_shift个结构的静态数值,并负责数
组的分配和释放。在核心解码分区表之前驱动程序应将数组初始化为零。
int *sizes
这个域指向一个整数数组。这个数组保持着与blk_size同样的信息。驱动程序负责分配
和释放该数据区域。注意设备的分区检查把这个指针拷贝到blk_size,因此处理可分区
设备的驱动程序不必分配这后一个数组。
int nr_real
存在的真实设备(单元)的个数。这个数字必须小于等于max_nr。
void *real_devices
这个指针被那些需要保存一些额外私有信息的驱动程序内部使用(这与filp->private_d
ata类似)。
void struct gendisk *next
在普通硬盘列表中的一根链。
在普通硬盘列表中的一根链。
分区检查的设计最适合那些直接链入核心映象的驱动程序,因此我将从介绍核心代码的
基本结构开始。以后我将介绍spull模块处理它的分区的方法。
核心中的分区检测
在引导时,init/main.c调用了各种各样的初始化函数。其中一个是start_kernel,它通
过调用device_setup来初始化所有的驱动程序。这个函数又调用blk_dev_init,接着检
查所有注册的普通硬盘的分区信息。任何一个块设备驱动程序,如果它找到至少一个它
的设备,就将这个驱动程序的genhd结构注册到核心列表中,这样它的分区便可以被正确
地检测出来。
因此,一个可分区的驱动程序应该声明它自己的结构genhd。这个结构看起来如下:
(代码258)
于是,在这个驱动程序的初始化函数中,这个结构被排队在可分区设备的主列表中。
被链入核心的驱动程序的初始化函数与init_module等价,即使它被调用的方式不同。这
个函数一定包含如下两行,它们用来将结构排队:
my_gendisk.next=gendisk_head;
my_gendisk.next=gendisk_head;
gendisk_head=my_gendisk;
通过将结构插入链表,这简单的两行便是驱动程序入口点为所有的分区正确地识别和配
置所需要的所有内容。
额外的设置通过my_geninit完成。在上面的例子中,这个函数填充“单元数”域来反映
计算机系统的实际硬件设置。在my_geninit结束后,gendisk.c为所有的盘(单元)执行
实际的分区检测。你可以看到系统启动时被检测的分区,因为gendisk.c在系统控制台上
打印分区检查Partition check:,后面跟随它在可得的普通硬盘上找到的所有分区。
你可以修改前面的代码,推迟my_sizes和my_partitions的分配直到my_geninit函数。这
可以节省少量的核心内存,因为这些数组可以小到nr_real<<minor_shift,而竟态数组
则必须为max_nr<<minor_shift字节长。不过,典型的数值是每个物理单元节省几百个字
节。
模块中的分区检测
一个模块化的驱动程序和链接到核心的驱动程序的区别在于它无法受益于集体中的初始
化。相反,它需要处理它自己的设置。由于没有为模块的两步初始化,所以spull的gend
isk结构在它的init函数指针中有一个NULL指针:
(代码259 1#)
同时也不必在普通硬盘的全局链表里注册gendisk结构。
通过引出函数resetup_one_dev,文件gendisk.c被准备用来处理象模块需要一类“晚的
”初始化。resetup_one_dev为单个物理设备扫描分区。其原型是:
boid resetup_one_dev(struct gendisk *dev,int drive);
从这个函数名字你可以看出来它是要改变一个设备的设置信息。这个函数被设计为由ioc
tl里BLKRRPART实现调用,但他也可以被用来完成一个模块的初始设置。
当一个模块被初始化后,它应该为每个它将要访问的物理设备调用resetup_one_dev,从
而将分区信息贮存my_gendisk->part中。分区信息会被设备的request_fn函数使用。
在spull中,init_module函数除了通常的指令外还包含了下面的代码。它分配分区检测
所需的数组并初始化数组中完整磁盘的项目。
(代码259 2#)
(代码260 1#)
有趣的是注意到resetup_one_dev通过重复调用下面函数打印分区信息:
printk(“%s:”,disk_name(hd,minor,buf));
这就是为什么spull要打印一个引导串。它意味着要为塞进系统日志的信息增加一些上下
文。
当一个可分区的模块被卸载时,驱动程序应该通过为每个支持的主/次对调用fsync_dev
来安排所有的分区刷新。而且,如果结构gendisk被插在全局链表中,它应该被删除——
注意spull并未自己插入它,原因上面提到过。
spull的清除函数是:
(代码260 2#)
(代码261)
使用Initrd进行分区检测
如果你想从一个设备上安装你的根文件系统,而这个设备的驱动程序只有模块化的形式
,你就必须使用由现代Linux核心提供的Initrd工具。我不想在这里介绍Initrd,这一小
节是针对那些了解Initrd并想知道它是如何影响块设备驱动程序的读者的。
使用Initrd进行分区检测
如果你想从一个设备上安装你的根文件系统,而这个设备的驱动程序只有模块化的形式
,你就必须使用由现代Linux核心提供的Initrd工具。我不想在这里介绍Initrd,这一小
节是针对那些了解Initrd并想知道它是如何影响块设备驱动程序的读者的。
当你用Initrd引导一个核心时,它会在安装真正的根文件系统之前建立一个暂时的运行
环境。模块通常是从被用作临时根文件系统的ramdisk中装载。
--
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -