[原创] 写一个块设备驱动(13)

2020-06-02 00:00:00 函数 代码 内存 这一 指针

第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()函数基本上是能够工作的。

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

<未完,待续>

文章来源CU社区:[原创] 写一个块设备驱动


相关文章