关闭

写一个块设备驱动 13

415人阅读 评论(0) 收藏 举报

第 13章

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

没有最好的代码,是因为我们总能把代码改得更好

因此我们现在打算做一个小的性能改进,这次我们准备拿free_diskmem()函数下刀

本质上说,这个改进的意义不大,这是因为 free_diskmem()函数仅仅是在模块卸载时被调用,

----------------------- Page 91-----------------------

而对这种执行次数即少 不在关键路径上的函数来说,最好是尽量让他简单以增加可靠性和可读性,

除非它的耗时已经慢到能让人有所感觉,否则0.01秒和 0.                    1秒是差不多的,毕竟在现实中尼奥不

太可能用我们的程序

但我们仍然打算继续这一改进,一是为了示范什么是没有意义的改进,二是为了通过这一改进示范使用

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片强

----------------------- Page 92-----------------------

通过 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;

----------------------- Page 93-----------------------

        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

----------------------- Page 94-----------------------

HighFree:       339144 kB
LowTotal:       896356 kB
LowFree:        630920 kB
...
#
这里显示现在剩余339M高端内存和 630M低端内存

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

然后看看卸载模块后的内存情况:

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

本章的改动不大,就算是暂作休整,以留住忍耐至今忍无可忍认为无需再忍而开始打包收拾行李准备溜

之大吉的读者们

不过下一章中倒是预备了一个做起来让人比较有成就感的功能

<未完,待续>


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:76048次
    • 积分:1047
    • 等级:
    • 排名:千里之外
    • 原创:0篇
    • 转载:83篇
    • 译文:0篇
    • 评论:8条
    文章分类
    最新评论