分类: LINUX
2011-06-07 18:28:02
+---------------------------------------------------+ | 写一个块设备驱动 | +---------------------------------------------------+ | 作者:赵磊 | | email: | +---------------------------------------------------+ | 文章版权归原作者所有。 | | 大家可以自由转载这篇文章,但原版权信息必须保留。 | | 如需用于商业用途,请务必与原作者联系,若因未取得 | | 授权而收起的版权争议,由侵权者自行负责。 | +---------------------------------------------------+ 既然上一章结束时我们已经预告了本章的内容, 那么本章中我们就让这个块设备有能力告知操作系统它的“物理结构”。 当然,对于基于内存的块设备来说,什么样的物理结构并不重要, 这就如同从酒吧带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 这个函数的目的,是选择适当的物理结构信息装入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。 <未完,待续> |
shaoguangleo2011-06-08 19:51:29
内核代码如下所示:
static int shao_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
case IOCTLSHAO_READ:
kernel_data[0] = 0xFFFFFFFF;
kernel_data[1] = 0xFFFFFFF1;
kernel_data[2] =