+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email:
[email protected] |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
既然上一章结束时我们已经预告了本章的内容,
那么本章中我们就让这个块设备有能力告知操作系统它的“物理结构”。
当然,对于基于内存的块设备来说,什么样的物理结构并不重要,
这就如同从酒吧带mm回家时不需要打听她的姓名一样。
但如果不幸遇到的是兼职,并且带她去不入流的招待所时,
建议最好还是先串供一下姓名、生日和职业等信息,
以便JJ查房时可以伪装成情侣。
同样,如果要实现的是真实的物理块设备驱动,
那么返回设备的物理结构时大概不能这么随意。
对于块设备驱动程序而言,我们现在需要关注那条目前只有一行的struct block_device_operations simp_blkdev_fops结构。
到目前为止,它存在的目的仅仅是因为它必须存在,但马上我们将发现它存在的另一个目的:为块设备驱动添加获得块设备物理结构的接口。
对于具有极强钻研精神的极品读者来说,大概在第一章中就会自己去看struct block_device_operations结构,然后将发现这个结构其实还挺复杂:
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
在前几章中,我们邂逅过其中的owner成员变量,它用于存储这个结构的所有者,也就是我们的模块,因此我们做了如下的赋值:
.owner = THIS_MODULE,
而这一章中,我们将与它的同胞妹妹------getgeo也亲密接触一下。
我们要做的是:
1:在block_device_operations中增加getgeo成员变量初值的设定,指向我们的“获得块设备物理结构”函数。
2:实现我们的“获得块设备物理结构”函数。
第一步很简单,我们暂且为“获得块设备物理结构”函数取个名字叫simp_blkdev_getgeo()吧,也避免了在下文中把这么一大堆汉字拷来拷去。
在simp_blkdev_fops中添加.getgeo指向simp_blkdev_getgeo,也就是把simp_blkdev_fops结构改成这个样子:
struct block_device_operations simp_blkdev_fops = {
.owner = THIS_MODULE,
.getgeo = simp_blkdev_getgeo,
};
第二步难一些,但也难不到哪去,在代码中的struct block_device_operations simp_blkdev_fops这行之前找个空点的场子,把如下函数插进去:
static int simp_blkdev_getgeo(struct block_device *bdev,
struct hd_geometry *geo)
{
/*
* capacity heads sectors cylinders
* 0~16M 1 1 0~32768
* 16M~512M 1 32 1024~32768
* 512M~16G 32 32 1024~32768
* 16G~... 255 63 2088~...
*/
if (SIMP_BLKDEV_BYTES < 16 * 1024 * 1024) {
geo->heads = 1;
geo->sectors = 1;
} else if (SIMP_BLKDEV_BYTES < 512 * 1024 * 1024) {
geo->heads = 1;
geo->sectors = 32;
} else if (SIMP_BLKDEV_BYTES < 16ULL * 1024 * 1024 * 1024) {
geo->heads = 32;
geo->sectors = 32;
} else {
geo->heads = 255;
geo->sectors = 63;
}
geo->cylinders = SIMP_BLKDEV_BYTES>>9/geo->heads/geo->sectors;
return 0;
}
因为这里我们用到了struct hd_geometry结构,所以还要增加一行#include <linux/hdreg.h>。
这个函数的目的,是选择适当的物理结构信息装入struct hd_geometry *geo结构。
当然,为了克服上一章中只能分成2个区的问题,我们应该尽可能增加磁道的数量。
希望读者不要理解成分几个区就需要几个磁道,这意味着一个磁道一个区,也意味着每个区必须一般大小。
由于分区总是以磁道为边界,尽可能增加磁道的数量不仅仅是为了让块设备容纳更多的分区,
更重要的是让分区的实际大小更接近于分区时的指定值,也就是提高实际做出的分区容量的精度。
不过对于设置的物理结构值,还存在一个限制,就是struct hd_geometry中的数值上限。
我们看struct hd_geometry的内容:
struct hd_geometry {
unsigned char heads;
unsigned char sectors;
unsigned short cylinders;
unsigned long start;
};
unsigned char的磁头数和每磁道扇区数决定了其255的上限,同样,unsigned short的磁道数决定了其65535的上限。
这还不算,但在前一章中,我们知道对于现代硬盘,磁头数和每磁道扇区数通常取的值是255和63,
再组合上这里的65535的磁道数上限,hd_geometry能够表示的最大块设备容量是255*63*65535*512/1024/1024/1024=502G。
显然目前linux支持的最大硬盘容量大于502G,那么对于这类块设备,内核是如何通过hd_geometry结构表示其物理结构的呢?
诀窍不在内核,而在于用户态程序如fdisk等通过内核调用获得hd_geometry结构后,
会舍弃hd_geometry.cylinders内容,取而代之的是直接通过hd_geometry中的磁头数和每磁道扇区数以及硬盘大小去计算磁道数。
因此对于超过502G的硬盘,由于用户程序得出的磁道数与hd_geometry.cylinders无关,所以我们往往在fdisk中能看到这块硬盘的磁道数大于65535。
刚才扯远了,现在言归正题,我们决定让这个函数对于任何尺寸的块设备,总是试图返回比较漂亮的物理结构。
漂亮意味着返回的物理结构既要保证拥有足够多的磁道,也要保证磁头数和每磁道扇区数不超过255和63,同时最好使用程序员看起来比较顺眼的数字,
如:1、2、4、8、16、32、64等。
当然,我们也希望找到某个One Shot公式适用于所有大小的块设备,但很遗憾目前作者没找到,因此采用了分段计算的方法:
首先考虑容量很小的块设备:
即使磁头数和每磁道扇区数都是1,磁道数也不够多时,我们会将磁头数和每磁道扇区数都固定为1,以使磁道数尽可能多,以提高分区的精度。
因此磁道数随块设备容量而上升。
虽然我们已经知道了磁道数其实可以超过unsigned short的65535上限,但在这里却没有必要,因此我们要给磁道数设置一个上限。
因为不想让上限超过65535,同时还希望上限也是一个程序员喜欢的数字,因此这里选择了32768。
当然,当磁道数超过32768时,已经意味着块设备容量不那么小了,也就没有必要使用这种情况中如此苛刻的磁头数和每磁道扇区数了。
简单来说,当块设备容量小于1个磁头、每磁道1扇区和32768个磁道对应的容量--也就是16M时,我们将按照这种情况处理。
然后假设块设备容量已经大于16M了:
我们希望保证块设备包含足够多的磁道,这里我们认为1024个磁道应该不少了。
磁道的最小值发生在块设备容量为16M的时候,这时使用1024作为磁道数,可以计算出磁头数*每磁道扇区数=32。
这里暂且把磁头数和每磁道扇区数固定为1和32,而让磁道数随着块设备容量的增大而增加。
同时,我们还是磁道的上限设置成32768,这时的块设备容量为512M。
总结来说,当块设备容量在16M和512M之间时,我们把磁头数和每磁道扇区数固定为1和32。
然后对于容量大于512M的块设备:
与上述处理相似,当块设备容量在512M和16G之间时,我们把磁头数和每磁道扇区数固定为32和32。
最后的一种情况:
块设备已经足够大了,大到即使我们使用磁头数和每磁道扇区数的上限,
也能获得足够多的磁道数。这时把磁头数和每磁道扇区数固定为255和63。
至于磁道数就算出多少是多少了,即使超过unsigned short的上限也无所谓,反正用不着。
随着这个函数解说到此结束,我们对代码的修改也结束了。
现在开始试验:
编译和加载:
# make
make -C /lib/modules/2.6.27.4/build SUBDIRS=/mnt/host_test/simp_blkdev/simp_blkdev_step05 modules
make[1]: Entering directory `/mnt/ltt-kernel'
CC [M] /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.o
Building modules, stage 2.
MODPOST 1 modules
CC /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.mod.o
LD [M] /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.ko
make[1]: Leaving directory `/mnt/ltt-kernel'
# insmod simp_blkdev.ko
#
用fdisk打开设备文件
# fdisk /dev/simp_blkdev
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
Command (m for help):
看看设备的物理结构:
Command (m for help): p
Disk /dev/simp_blkdev: 16 MB, 16777216 bytes
1 heads, 32 sectors/track, 1024 cylinders
Units = cylinders of 32 * 512 = 16384 bytes
Device Boot Start End Blocks Id System
Command (m for help):
我们发现,现在的设备有1个磁头、32扇区每磁道、1024个磁道。
这是符合代码中的处理的。
本章的内容也不是太难,连同上一章,我们已经休息2章了。
聪明的读者可能已经猜到作者打算说什么了。
不错,下一章会有一个surprise。
<未完,待续>