免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: OstrichFly
打印 上一主题 下一主题

[原创] 写一个块设备驱动 [复制链接]

论坛徽章:
0
21 [报告]
发表于 2008-12-09 21:49 |显示全部楼层
原帖由 wk9999 于 2008-12-9 18:01 发表
牛人啊!!!!!!!!

谢谢支持!

论坛徽章:
0
22 [报告]
发表于 2008-12-09 22:02 |显示全部楼层
原帖由 Godbach 于 2008-12-9 21:53 发表


这个就是LZ帖子上贴出来的前八章内容吧。

是的,严格来说附件中多出了第8章的内容(还没有贴到论坛中),
我马上就贴上来。

论坛徽章:
0
23 [报告]
发表于 2008-12-09 22:03 |显示全部楼层

第8章

+---------------------------------------------------+
|                 写一个块设备驱动                  |
+---------------------------------------------------+
| 作者:赵磊                                        |
| email: zhaoleidd@hotmail.com                      |
+---------------------------------------------------+
| 文章版权归原作者所有。                            |
| 大家可以自由转载这篇文章,但原版权信息必须保留。  |
| 如需用于商业用途,请务必与原作者联系,若因未取得  |
| 授权而收起的版权争议,由侵权者自行负责。          |
+---------------------------------------------------+

本章的目的是让读者继续休息,因此决定仍然搞一些简单的东西。
比如:给我们的驱动程序模块加上模块参数,这样在加载模块时,可以通过参数设定块设备的大小。

给我们模块加参数的工作不难,这牵涉到1个宏:
module_param_named(name, value, type, perm)
        name是参数的名称
        value是参数在模块中对应的变量
        type是参数的类型
        perm是参数的权限
如,在模块中添加
int disk_size = 1024;
module_param_named(size, disk_size, int, S_IRUGO);
可以给模块加上名称为"size"的参数,如果在加载模块是使用insmod thismodule size=100,那么在模块代码中disk_size的值就是100。
相反,如果加载模块时没有指定参数,那么模块代码中disk_size的值仍是默认的1024。
S_IRUGO指定了这个参数的值在模块加载以后可以被所有人通过/sys/module/[module_name]/parameters/看到,但无法修改。
好了,有关module_param_named就介绍到这里,细节可以google或者看linux/include/linux/moduleparam.h。

然后我们就要给这个模块加个参数,用来在加载时指定块设备的大小。
参数的名字都已经想好了,就叫size吧,类型嘛,32位无符号整数最大能设定到4G,而我们的野心看起来可能更大一些,
为了让这个模块支持4G以上的虚拟磁盘(当然是内存足够的情况下),我们打算使用64位无符号整型。这样能够设定的最大值为16777216T,应该够了吧。

然后我们试图找出module_param_named的参数中与unsigned long long对应的type来。
结果是:google了,没找到;看linux/include/linux/moduleparam.h了,还是没找到。
结论是:目前的linux(2.6.28)还不支持unsigned long long类型的模块参数。
更新一些的内核中会不会有是将来的事,尽快搞定这一章的功能却是现在面临的问题。

然后我们就开始找解决方案:
1:给内核打个补丁,看样子不错,但至少今天之类完成不了我们的程序了
   并且这样一来,我们的程序只能在今后的内核中运行,而失去对旧版linux的兼容性。
2:指定设置磁盘大小的单位为M。这样可设置的最大的数字就成了4G*1M,也就是4096T。
   这个主意看似不错。而且看样子10年内机器的内存应该到不了这个容量。
3:用字符串来指定大小
   这倒是可以解决所有问题,并且我们可以支持16M、1G之类的设定,让我们的程序看起来比较花哨。
   缺点应该是我们需要在程序中自己去解析传入的字符串了,幸运的是,实际的解析代码比想象的容易一些。
因此,我们采用第3个方案,向模块中添加一个名称为size、类型为字符串的参数,并且支持解析以K,M,G,T为单位的设定。

第1步:
  向程序中添加以下参数申明。
  static char *simp_blkdev_param_size = "16M";
  module_param_named(size, simp_blkdev_param_size, charp, S_IRUGO);
  char *simp_blkdev_param_size用于存储设定的磁盘大小,我们把磁盘大小的默认值指定为16M。
  目前我们不允许用户在模块加载后改变磁盘大小,将来嘛,有可能增加这一功能,看起来很眩。
第2步:
  原来的程序使用
  #define SIMP_BLKDEV_BYTES      (16*1024*1024)
  定义磁盘大小,而现在我们不需要这一行了。
  同时,我们需要一个unsigned long long变量来存储用户设定的磁盘大小,因此我们增加这个变量:
  static unsigned long long simp_blkdev_bytes;
  然后把程序中所有使用SIMP_BLKDEV_BYTES的位置换成使用simp_blkdev_bytes变量。
第3步:
  在模块加载时对模块参数进行解析,设置simp_blkdev_bytes变量的值。
  我们增加一个函数进行解析工作:
int getparam(void)
{
        char unit;
        char tailc;

        if (sscanf(simp_blkdev_param_size, "%llu%c%c", &simp_blkdev_bytes,
                &unit, &tailc) != 2) {
                return -EINVAL;
        }

        if (!simp_blkdev_bytes)
                return -EINVAL;

        switch (unit) {
        case 'g':
        case 'G':
                simp_blkdev_bytes <<= 30;
                break;
        case 'm':
        case 'M':
                simp_blkdev_bytes <<= 20;
                break;
        case 'k':
        case 'K':
                simp_blkdev_bytes <<= 10;
                break;
        case 'b':
        case 'B':
                break;
        default:
                return -EINVAL;
        }

        /* make simp_blkdev_bytes fits sector's size */
        simp_blkdev_bytes = (simp_blkdev_bytes + (1<<9) - 1) & ~((1ULL<<9) - 1);

        return 0;
}
然后在simp_blkdev_init()中调用这个函数:
ret = getparam();
if (IS_ERR_VALUE(ret))
        goto err_getparam;
当然,err_getparam的位置读者应该能猜出来了。

这样一来,工作大概就完成了,让我们看看结果:
使用默认值:
# insmod simp_blkdev.ko
# 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): 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): q

#
设定成20M:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=20M
# 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.


The number of cylinders for this disk is set to 1280.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help): p

Disk /dev/simp_blkdev: 20 MB, 20971520 bytes
1 heads, 32 sectors/track, 1280 cylinders
Units = cylinders of 32 * 512 = 16384 bytes

           Device Boot      Start         End      Blocks   Id  System

Command (m for help): q

#
变态一下,还是设定成20M,但用k作单位:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=20480k
# 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.


The number of cylinders for this disk is set to 1280.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
   (e.g., DOS FDISK, OS/2 FDISK)
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help): p

Disk /dev/simp_blkdev: 20 MB, 20971520 bytes
1 heads, 32 sectors/track, 1280 cylinders
Units = cylinders of 32 * 512 = 16384 bytes

           Device Boot      Start         End      Blocks   Id  System

Command (m for help): q

#

看样子结果不错。
这一章中基本上没有提到什么比较晦涩的知识,而且看样子通过这一章的学习,大家也应该休息好了。
如果读者现在感觉到精神百倍,那么这一章的目的应该就达到了。

<未完,待续>

论坛徽章:
0
24 [报告]
发表于 2008-12-12 20:53 |显示全部楼层
原帖由 Godbach 于 2008-12-9 22:04 发表


好的。再次感谢LZ的分享啊。

有时间也多来内核版和大家交流啊。

好的,我很喜欢ChinaUnix的内核版,这也是我把文章投到这里的原因。

论坛徽章:
0
25 [报告]
发表于 2008-12-12 21:03 |显示全部楼层
原帖由 Godbach 于 2008-12-9 22:10 发表
另外一个建议:由于LZ的八章内容是分散的,所以如果能在1楼把各个章节以及对应的位置标出来,也相当于个目录,那样大家参考起来更容易找。

谢谢建议!
有空我更新一下1楼。

论坛徽章:
0
26 [报告]
发表于 2008-12-12 21:05 |显示全部楼层
原帖由 2004sz 于 2008-12-11 11:45 发表
OstrichFly  大好人,多谢奉献

谢谢支持!
其实不光人好,还很帅很有品位呢: )

论坛徽章:
0
27 [报告]
发表于 2008-12-12 21:06 |显示全部楼层
原帖由 hb12112 于 2008-12-12 15:31 发表
期待中呵!

谢谢支持!

论坛徽章:
0
28 [报告]
发表于 2008-12-12 21:08 |显示全部楼层

第9章

+---------------------------------------------------+
|                 写一个块设备驱动                  |
+---------------------------------------------------+
| 作者:赵磊                                        |
| email: zhaoleidd@hotmail.com                      |
+---------------------------------------------------+
| 文章版权归原作者所有。                            |
| 大家可以自由转载这篇文章,但原版权信息必须保留。  |
| 如需用于商业用途,请务必与原作者联系,若因未取得  |
| 授权而收起的版权争议,由侵权者自行负责。          |
+---------------------------------------------------+

在本章中我们来讨论一下这个驱动程序的数据安全,
因为最近的一些事情让作者愈发地感觉到数据泄漏对当事人来说是麻烦的。

我们开门见山的解释一下数据安全问题:
内核常常会向用户态传递数据,而作为内核程序的开发者,我们必须意识到不能把包含意料内容之外的数据随便透露给用户态,
因为如果这些数据不巧被别有用心者利用,就会带来不少麻烦。
比如陈冠希就犯了这样的错误。新余市出国考察团也没有在陈冠希身上吸取教训,把单据也不当回事儿。
单据对于考察团而言并不是什么重要的玩意儿,但一旦落到“别有用心”的人手中被加以利用,就不得不当一回事了。
由此我们发现了单据的商业价值。
今后在旅游公司干过的员工拿着手头攒到的大量单据,可能会比KIRA更有前途。
因此公务员确实属于高风险职业,加薪也是情理当中的了。

对于内核而言,其中的数据也是如此。
即使一些数据对内核而言没有价值,但也不能随意地向用户态传递,因为这段内存中可能不巧包含了不能随意让用户获取的数据,
比如用户A使用linux整理他女友的裸照文件,裸照的数据很可能存在于用户A的进程的虚存中,也可能还存在于文件缓存中,
A的进程结束后,系统回收了进程的内存,这时内存中的数据被系统认定为无效数据,但系统并没有清空这段数据。
A打开的文件的缓存也类似,缓存被系统回收后,内存中的数据并没有被清除。
随后用户B使用了我们的块设备驱动程序。驱动程序初始化时需要获取足够的内存以存储块设备中的数据,
系统很可能将用户A使用过的那段包含裸照数据的内存分配给我们的块设备驱动程序。
这时如果用户B老老实实分区、创建文件系统、写入文件,这当然没事,
但如果用户B别有用心的上来就直接去读块设备中的数据,那么他可能很幸运的看到不该看的东西。

因此我们咬牙切齿,嫉妒心促使我们修改这个块设备驱动,我们都没遇到的好事儿,也决不允许用户B遇到。
修改的方法很简单,我们申请内存时使用了__get_free_pages()函数,
这个函数的第一个参数是gfp_mask,原先我们传递的是GFP_KERNEL,表示用于内核中的一般情况。
现在我们只要向gfp_mask中添加__GFP_ZERO标志,以提示需要申请清0后的内存。
这样驱动程序加载后,块设备中数据的初始值全为0,这就避免了上文中提到的安全问题。
详细来说,就是把alloc_diskmem()函数中的
p = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
这一行改成
p = (void *)__get_free_pages(GFP_KERNEL,

安全方面的改动已经完成了,但为了避免读者认为本章偷工减料,我们再多改一些代码。

块设备中每扇区的数据长度为512字节,我们在驱动程序经常遇到与此相关的转换。
为了快速运算,我们经常用到9这个常数,比如:
乘以512就是左移9、除以512就是右移9、除以512的余数就是& ((1ULL<<9) - 1)、
向上对齐到512的倍数就是加上(1<<9) - 1再& ~((1ULL<<9) - 1)。

不过现在我们决定通过定义几个宏来吧这些操作写得好看一些。
先定义:
#define SIMP_BLKDEV_SECTORSHIFT        (9)
#define SIMP_BLKDEV_SECTORSIZE        (1ULL<<SIMP_BLKDEV_SECTORSHIFT)
#define SIMP_BLKDEV_SECTORMASK        (~(SIMP_BLKDEV_SECTORSIZE-1))

然后使用这几个宏来进行扇区相关的转换工作。

详细来说,就是把simp_blkdev_make_request()函数中的:
if ((bio->bi_sector << 9) + bio->bi_size > simp_blkdev_bytes) {
改成
if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
        > simp_blkdev_bytes) {

dsk_offset = bio->bi_sector << 9;
改成
dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;

把simp_blkdev_getgeo()函数中的:
geo->cylinders = simp_blkdev_bytes>>9/geo->heads/geo->sectors;
改成
geo->cylinders = simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT
        / geo->heads / geo->sectors;

把getparam()函数中的:
simp_blkdev_bytes = (simp_blkdev_bytes + (1<<9) - 1) & ~((1ULL<<9) - 1);
改成
simp_blkdev_bytes = (simp_blkdev_bytes + SIMP_BLKDEV_SECTORSIZE - 1)
        & SIMP_BLKDEV_SECTORMASK;

把simp_blkdev_init()函数中的:
set_capacity(simp_blkdev_disk, simp_blkdev_bytes>>9);
改成
set_capacity(simp_blkdev_disk,
        simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT);

如果运气不算太背的话,程序应该是能够运行的,让我们试试:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step09 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
# insmod simp_blkdev.ko
#
看一看驱动程序刚刚加载时里面的数据:
# hexdump /dev/simp_blkdev -vn512
0000000 0000 0000 0000 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
0000040 0000 0000 0000 0000 0000 0000 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
0000060 0000 0000 0000 0000 0000 0000 0000 0000
0000070 0000 0000 0000 0000 0000 0000 0000 0000
0000080 0000 0000 0000 0000 0000 0000 0000 0000
0000090 0000 0000 0000 0000 0000 0000 0000 0000
00000a0 0000 0000 0000 0000 0000 0000 0000 0000
00000b0 0000 0000 0000 0000 0000 0000 0000 0000
00000c0 0000 0000 0000 0000 0000 0000 0000 0000
00000d0 0000 0000 0000 0000 0000 0000 0000 0000
00000e0 0000 0000 0000 0000 0000 0000 0000 0000
00000f0 0000 0000 0000 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0000 0000 0000 0000
0000110 0000 0000 0000 0000 0000 0000 0000 0000
0000120 0000 0000 0000 0000 0000 0000 0000 0000
0000130 0000 0000 0000 0000 0000 0000 0000 0000
0000140 0000 0000 0000 0000 0000 0000 0000 0000
0000150 0000 0000 0000 0000 0000 0000 0000 0000
0000160 0000 0000 0000 0000 0000 0000 0000 0000
0000170 0000 0000 0000 0000 0000 0000 0000 0000
0000180 0000 0000 0000 0000 0000 0000 0000 0000
0000190 0000 0000 0000 0000 0000 0000 0000 0000
00001a0 0000 0000 0000 0000 0000 0000 0000 0000
00001b0 0000 0000 0000 0000 0000 0000 0000 0000
00001c0 0000 0000 0000 0000 0000 0000 0000 0000
00001d0 0000 0000 0000 0000 0000 0000 0000 0000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
00001f0 0000 0000 0000 0000 0000 0000 0000 0000
0000200
#
对比一下修改前的效果:
# hexdump /dev/simp_blkdev -vn512
0000000 f300 0800 1200 0000 b804 1200 0000 0500
0000010 501a 6930 1806 246a bf0a 7700 256a bf0b
0000020 1f80 256b bf0b 47a0 266b bf0b 0ff0 246a
0000030 bf0a 1708 ffff 00ff 5028 256b bf0b 00a8
0000040 ffff 00ff 04b8 ffff 00ff 10c8 256b bf0b
0000050 00e8 246a bf0a 0229 ffff 00ff 1339 ffff
0000060 00ff 0059 246a bf0a 1669 ffff 00ff 12a9
0000070 256b bf0b 02c9 ffff 00ff 12d9 246a bf0a
0000080 215a ffff 00ff 302c 256b bf0b 03ac ffff
0000090 00ff 10cc 256b bf0b 03ec 246a bf0a 522d
00000a0 256b bf0b 32bd 2318 266b bf0c 2700 266c
00000b0 bf0c 2730 276c bf0c 1f60 276c bf0d 3580
00000c0 276d bf0d 1bc0 286d bf0d 05e0 286d bf0e
00000d0 04f0 ffff 00ff 07f5 276c bf0d 0186 ffff
00000e0 00ff 1596 276c bf0d 01b6 ffff 00ff 15e6
00000f0 266b bf0c 0708 266b bf0c 0018 ffff 00ff
0000100 0428 ffff 00ff 1038 266c bf0c 0058 ffff
0000110 00ff 3088 ffff 00ff 1219 266c bf0c 0239
0000120 ffff 00ff 1249 276c bf0d 0689 276c bf0d
0000130 02b9 266b bf0c 031c ffff 00ff 103c 266c
0000140 bf0c 035c 276c bf0d 039c ffff 00ff 20ac
0000150 276d bf0d 03dc 286d bf0d 03ec 266b bf0c
0000160 022d 266c bf0c 223d 276c bf0d 12ad 276d
0000170 bf0d 12cd 286d bf0e 02fd 2b18 286d bf0e
0000180 4400 296e bf0e 1450 296e bf0f 4470 2a6e
0000190 bf0f 14c0 2a6f bf0f 04e0 2a6f bf10 04f0
00001a0 ffff 00ff 2005 286d bf0e 1035 ffff 00ff
00001b0 5055 296e bf0f 0ab5 ffff 00ff 30c5 286d
00001c0 bf0e 1006 ffff 00ff 1426 286d bf0e 0946
00001d0 ffff 00ff 1056 296e bf0f 0176 ffff 00ff
00001e0 1186 296e bf0f 14a6 2a6e bf0f 05c6 ffff
00001f0 00ff 16d6 2a6f bf10 05f6 286d bf0e 0007
0000200
#

本章到此结束,读者是不是感觉我们的教程越来越简单了?

<未完,待续>

论坛徽章:
0
29 [报告]
发表于 2008-12-16 20:21 |显示全部楼层
原帖由 Godbach 于 2008-12-12 21:23 发表
LZ辛苦。

所以也希望你能同样辛苦地看完哦: )

论坛徽章:
0
30 [报告]
发表于 2008-12-16 20:24 |显示全部楼层
原帖由 myem007 于 2008-12-12 22:20 发表
虽然没看懂,但是还是有支持下楼主!

谢谢支持!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP