[转]写一个块设备驱动(第五章)

第5章+—————————————————+| 写一个块设备驱动 |+—————————————————+| 作者:赵磊 || email: zhaoleidd@hotmail.com |+—————————————————+| 文章版权归原作者所有。 || 大家可以自由转载这篇文章,但原版权信息必须保留。 || 如需用于商业用途,请务必与原作者联系,若因未取得 || 授权而收起的版权争议,由侵权者自行负责。 |+—————————————————+既然上一章结束时我们已经预告了本章的内容,那么本章中我们就让这个块设备有能力告知操作系统它的“物理结构”。当然,对于基于内存的块设备来说,什么样的物理结构并不重要,这就如同从酒吧带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的上限也无所谓,反正用不着。随着这个函数解说到此结束,我们对代码的修改也结束了。现在开始试验:编译和加载:# makemake -C /lib/modules/2.6.27.4/build SUBDIRS=/mnt/host_test/simp_blkdev/simp_blkdev_step05 modulesmake[1]: Entering directory `/mnt/ltt-kernel’CC [M] /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.oBuilding modules, stage 2.MODPOST 1 modulesCC /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.mod.oLD [M] /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.komake[1]: Leaving directory `/mnt/ltt-kernel’# insmod simp_blkdev.ko#用fdisk打开设备文件# fdisk /dev/simp_blkdevDevice contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabelBuilding a new DOS disklabel. Changes will remain in memory only,until you decide to write them. After that, of course, the previouscontent 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): pDisk /dev/simp_blkdev: 16 MB, 16777216 bytes1 heads, 32 sectors/track, 1024 cylindersUnits = cylinders of 32 * 512 = 16384 bytesDevice Boot Start End Blocks Id SystemCommand (m for help):我们发现,现在的设备有1个磁头、32扇区每磁道、1024个磁道。这是符合代码中的处理的。本章的内容也不是太难,连同上一章,我们已经休息2章了。聪明的读者可能已经猜到作者打算说什么了。不错,下一章会有一个surprise。<未完,待续>

自己要先看得起自己,别人才会看得起你

[转]写一个块设备驱动(第五章)

相关文章:

你感兴趣的文章:

标签云: