寫一個塊設備驅動 13

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

本章的改動不大,就算是暫作休整,以留住忍耐至今忍無可忍認爲無需再忍而開始打包收拾行李準備溜

之大吉的讀者們

不過下一章中倒是預備了一個做起來讓人比較有成就感的功能

<未完,待續>


發佈了0 篇原創文章 · 獲贊 2 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章