免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: OstrichFly

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

论坛徽章:
0
发表于 2009-02-05 20:02 |显示全部楼层
原帖由 Godbach 于 2008-12-31 23:11 发表


呵呵,偶在家里也是这样子。 不知道作者知道咱们这样看书,会不会郁闷。

唉,也就是在卫生间能够不被打扰啊
好在我家卫生间比较大,十几个平方

论坛徽章:
0
发表于 2009-02-05 20:05 |显示全部楼层
原帖由 qiwei9743 于 2009-1-1 19:48 发表
非常感谢楼主的文章!
新的火影和死神看完了!
楼主的文章也看完了,呵呵。
看了一下大概,想先自己做一下,看看自己的手艺。
自己动手太少了,就像学文化课一下。多做题才会理解的更深刻!
然后再仔细看看楼主的,检查一下对楼主的思路是不是理解了。
呵呵!再次感谢楼主的无私奉献!

最后,敢问楼主是80后还是70后?

谢谢支持!
严格来说属于70后,在70、80的分界位置,就像南京跨越了江南江北一样。

论坛徽章:
0
发表于 2009-02-05 20:06 |显示全部楼层
原帖由 aaaaa5aa 于 2009-1-2 00:45 发表
看完有点晕乎乎的,~~~~

没事,再看看就清醒了

论坛徽章:
0
发表于 2009-02-05 20:20 |显示全部楼层
原帖由 pigcrying 于 2009-1-4 10:12 发表

呵呵。。。很好的欣赏完一个人的作品就是对创作者最好的支持。
虽然可以协商轮着搞,但是目前好像两个男的都想占为己有,都想让女的怀上自己的孩子。
而且针对开灯问题,目前的情况是大灯全部坏掉了,而整个 ...

在内核中对数据访问的同步,必须由访问数据的代码遵循一定规则访问来解决。
当然我们可以通过一些方法加以限制,让代码无法去做破坏数据完整性的事情,比如不允许代码直接访问数据,
而必须通过指定的函数访问,这时可以在函数中处理同步问题。
但大多数情况下,还是要求访问数据的代码去遵循约定的规则的(比如获取锁)。

对于“两个男的都想占为己有”的情况,大概可以解释为程序Bug了,
对于这种情况,要做的大概是让他们投胎吧,也就是改正这段代码。

在读写块设备时没有加锁,假设用户态的2个进程同时读写块设备的相邻区域,确实会出现数据混乱,
但这不是块设备驱动程序需要处理的,而是这两个进程本身的问题。
应该改的是这两个进程的代码(比如加文件锁),避免它们同时读写块设备的相同区域,
这与两个进程同时读写普通文件是一个概念。

不过在将来的章节中确实将出现需要同步的问题,到时我们将做一个示例。

论坛徽章:
0
发表于 2009-02-05 20:21 |显示全部楼层
原帖由 itaomail 于 2009-1-7 20:40 发表
楼主哥哥写的真好,大师

过奖过奖,希望能对你有用哦。

论坛徽章:
0
发表于 2009-02-05 20:27 |显示全部楼层
原帖由 fly6 于 2009-1-7 21:35 发表
LZ入行入错了,应该去写网络小说,而不应该做IT
呵呵,支持!!!!!!!!!

确实觉得写网络小说倒是比写IT教程爽不少。
前者写得好,会有好多mm崇拜者,甚至...(口水ing......)
后者充其得到一些同行的认可,而且99%都是爷们

论坛徽章:
0
发表于 2009-02-05 20:50 |显示全部楼层
原帖由 fishswimming 于 2009-1-11 22:41 发表
今天也试着写了一个块设备驱动来加密磁盘,有个地方可被折腾坏了:
如果是用blk_init_queue()来指定函数foo()来为请求队列服务,而又需要在foo()函数中进行非原子操作,一定要用spin_lock_irq()来释放请求队列 ...

不错哦。
希望国内的linux爱好者、特别是能编程的爱好者能多起来。
前些天总书记看望军队的新闻中,部队专用设备的屏幕上出现的居然是熟悉的蓝天白云,不免有些担忧,
但往好的方面想,军队里的电脑高手应该早已知道其中的隐患、并准备好破解之道吧。

论坛徽章:
0
发表于 2009-02-05 20:59 |显示全部楼层
原帖由 fly6 于 2009-2-3 14:00 发表
LZ怎么还没来

不好意思让大家久等了,
年前为对得起奖金,把单位的活儿做得快了一些,
然后是9天的假,沉醉于睡觉和看电影之中,家门口的小店也关门了,导致为每顿吃饭发愁
上班时身体极度虚弱,连爬楼的力气都没了
不过在你的督促下,我又来了!

论坛徽章:
0
发表于 2009-02-05 21:12 |显示全部楼层
原帖由 fly6 于 2009-2-3 17:19 发表
由于内核的虚拟地址空间和用户虚拟地址空间共用4GB,且内核空间只有1GB
所以当内存>1GB时对超过1GB的空间就不能直接访问了,必须先映
为了可以访问高端内存,系统预留了一部分低端地址空间(128M)作为高端内存的临时映射,当然这128M还有别的用途
请问我的理解对吗?如果是,那128M还有些什么具体用途?

基本上是对的,我做个补充:
1:虽然一般说是“超过1GB的空间就不能直接访问”,但严格来说,是超过896M的空间就不能直接访问了,因为还要减去预留的128M
2:这仅限于在x86体系的默认config下的情况,不过倒是我们最经常接触的情况了
3:关于这128M的其余用途,我google了一下(关键字:linux 896M 128M),发现这篇文章讲的比较详细:
   http://www.diybl.com/course/6_sy ... /200899/141218.html

论坛徽章:
0
发表于 2009-02-05 21:19 |显示全部楼层

第13章

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

没有最好的代码,是因为我们总能把代码改得更好。
因此我们现在打算做一个小的性能改进,这次我们准备拿free_diskmem()函数下刀。

本质上说,这个改进的意义不大,这是因为free_diskmem()函数仅仅是在模块卸载时被调用,
而对这种执行次数即少又不在关键路径上的函数来说,最好是尽量让他简单以增加可靠性和可读性,
除非它的耗时已经慢到能让人有所感觉,否则0.01秒和0.00001秒是差不多的,毕竟在现实中尼奥不太可能用我们的程序。

但我们仍然打算继续这一改进,一是为了示范什么是没有意义的改进,二是为了通过这一改进示范使用radix_tree_gang_lookup()函数和page->index的技巧。

首先我们看看原先的free_diskmem()函数:
void free_diskmem(void)
{
        int i;
        struct page *page;

        for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
                >> SIMP_BLKDEV_DATASEGSHIFT; i++) {
                page = radix_tree_lookup(&simp_blkdev_data, i);
                radix_tree_delete(&simp_blkdev_data, i);
                /* free NULL is safe */
                __free_pages(page, SIMP_BLKDEV_DATASEGORDER);
        }
}

它遍历所有的内存块索引,在基树中找到这个内存块的page指针,然后释放内存,顺带着释放掉基数中的这个节点。
考虑到这个函数不仅会在模块卸载时被调用,也会在模块加载时、申请内存中途掉链子时用来擦屁股,因此也需要考虑内存没有完全申请的情况。
所幸的是这种情况下radix_tree_lookup()函数会返回NULL指针,而radix_tree_delete()和__free_pages()函数都能对NULL指针做出我们最期待的处理:就是什么也不做。

这段代码很小很直接,逻辑简单而清晰,性能也差不到哪里去,完全符合设计要求,
不幸的是我们还是打算做一些没必要的优化,借此还可以顺便读一读基树的内核代码。
首先看radix_tree_lookup()函数,它在基数中查找指定索引对应的指针,为了获得这一指针的值,基本上它需要把基树从上到下找一遍。
而对于free_diskmem()函数而言,我们仅仅是需要遍历基树中的所有节点,使用逐一查找的方法进行遍历未免代价太大了。
就像是我们要给在场的所有同学每人发一个糖果,只需要让他们排好队,每人领一个即可,而不需要按照名单找出每个人再发。
为了实现这一思想,我们跑到linux/lib/radix-tree.c中找函数,找啊找,找到了radix_tree_gang_lookup()函数。

radix_tree_gang_lookup()函数虽然不是我们理想中的遍历函数,但也有了八九不离十的功能。
就像在酒吧里找不到D Cup,带回去个C Cup也总比看A片强。
通过radix_tree_gang_lookup()函数,我们可以一次从基树中获取多个节点的信息:
unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items);
具体的参数嘛,RTFSC吧。

这是我们注意到使用这个函数时顾此失彼的一面,虽然我们获得了一组需要释放的指针,但却无法获得这些指针的索引。
而执行释放基树中节点的操作时却恰恰需要使用索引作参数。
然后就是一个技巧了,我们借用page结构的index成员来存储这一索引。
之所以可以这样用,是因为page结构的index成员在该页用作页高速缓存时存储相对文件起始处的以页大小为单位的偏移,
而我们所使用的页面不会被同时用作页高速缓存,因此这里可以借用page.index成员。
按照以上思路,我们写出了修改后的代码:
void free_diskmem(void)
{
        unsigned long long next_seg;
        struct page *seglist[64];
        int listcnt;
        int i;

        next_seg = 0;
        do {
                listcnt = radix_tree_gang_lookup(&simp_blkdev_data,
                        (void **)seglist, next_seg, ARRAY_SIZE(seglist));

                for (i = 0; i < listcnt; i++) {
                        next_seg = seglist[i]->index;
                        radix_tree_delete(&simp_blkdev_data, next_seg);
                        __free_pages(seglist[i], SIMP_BLKDEV_DATASEGORDER);
                }

                next_seg++;
        } while (listcnt == ARRAY_SIZE(seglist));
}

当然,alloc_diskmem()函数中也需要加上page->index = i这一行,用于把基树的索引存入page.index,修改后的代码如下:
int alloc_diskmem(void)
{
        int ret;
        int i;
        struct page *page;

        INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);

        for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
                >> SIMP_BLKDEV_DATASEGSHIFT; i++) {
                page = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
                        SIMP_BLKDEV_DATASEGORDER);
                if (!page) {
                        ret = -ENOMEM;
                        goto err_alloc;
                }

                page->index = i;
                ret = radix_tree_insert(&simp_blkdev_data, i, page);
                if (IS_ERR_VALUE(ret))
                        goto err_radix_tree_insert;
        }
        return 0;

err_radix_tree_insert:
        __free_pages(page, SIMP_BLKDEV_DATASEGORDER);
err_alloc:
        free_diskmem();
        return ret;
}

现在试验一下修改后的代码,先看看能不能编译:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step13 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step13/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step13/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step13/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
#

看看当前系统的内存情况:
# cat /proc/meminfo
HighTotal:     1146816 kB
HighFree:       339144 kB
LowTotal:       896356 kB
LowFree:        630920 kB
...
#
这里显示现在剩余339M高端内存和630M低端内存。

然后加载我们的模块,让它吃掉300M内存:
# insmod simp_blkdev.ko size=300M
# cat /proc/meminfo
HighTotal:     1146816 kB
HighFree:       137964 kB
LowTotal:       896356 kB
LowFree:        523900 kB
...
#
正如我们的预期,剩余内存减少300M左右。

然后看看卸载模块后的内存情况:
# rmmod simp_blkdev
# cat /proc/meminfo
HighTotal:     1146816 kB
HighFree:       338028 kB
LowTotal:       896356 kB
LowFree:        631044 kB
...
#
我们发现剩余内存增加了300M,这意味着模块已经把吃掉的内存吐回来了,
从而可以推断出我们修改过的free_diskmem()函数基本上是能够工作的。

本章的改动不大,就算是暂作休整,以留住忍耐至今忍无可忍认为无需再忍而开始打包收拾行李准备溜之大吉的读者们。
不过下一章中倒是预备了一个做起来让人比较有成就感的功能。

<未完,待续>
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

SACC2021中国系统架构师大会

【数字转型 架构重塑】2021年5月20日-22日第十三届中国系统架构师大会将在云端进行网络直播。

大会为期3天的议程,涉及20+专场,近120个主题,完整迁移到线上进行网络直播对会议组织来说绝非易事;但考虑到云端会议的直播形式可以实现全国各地技术爱好者的参与,也使ITPUB作为技术共享交流平台得到更好的普及,我们决定迎难而上。
http://sacc.it168.com/


大会官网>>
  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP