第 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()函数基本上是能够工作的
本章的改动不大,就算是暂作休整,以留住忍耐至今忍无可忍认为无需再忍而开始打包收拾行李准备溜
之大吉的读者们
不过下一章中倒是预备了一个做起来让人比较有成就感的功能
<未完,待续>