第 13章
+---------------------------------------------------+
| 寫一個塊設備驅動 |
+---------------------------------------------------+
| 作者:趙磊 |
| email: [email protected] |
+---------------------------------------------------+
| 文章版權歸原作者所有。 |
| 大家可以自由轉載這篇文章,但原版權信息必須保留。 |
| 如需用於商業用途,請務必與原作者聯繫,若因未取得 |
| 授權而收起的版權爭議,由侵權者自行負責。 |
+---------------------------------------------------+
沒有最好的代碼,是因爲我們總能把代碼改得更好
因此我們現在打算做一個小的性能改進,這次我們準備拿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()函數基本上是能夠工作的
本章的改動不大,就算是暫作休整,以留住忍耐至今忍無可忍認爲無需再忍而開始打包收拾行李準備溜
之大吉的讀者們
不過下一章中倒是預備了一個做起來讓人比較有成就感的功能
<未完,待續>