您的位置:首页 > 其它

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

2011-07-18 15:14 169 查看
第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的上限也无所谓,反正用不着。

随着这个函数解说到此结束,我们对代码的修改也结束了。

现在开始试验:

编译和加载:

# 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。

<未完,待续>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: