一步一步走進塊驅動之第十一章

第十一章

本教程修改自趙磊的網上的一系列教程.本人覺得該系列教程寫的非常不錯.以風趣幽默的語言將塊驅動寫的非常詳細,對於入門教程,應該屬於一份經典了. 本人在這對此係列教程最後附上對Linux 2.6.36版本的代碼.並編譯運行成功. 該教程所有版權仍歸作者趙磊所有,本人只做附錄代碼的添加,併爲對原文修改.有不懂的地方,可以聯繫我 [email protected] 或者給我留言  

+---------------------------------------------------+

|                 寫一個塊設備驅動                  

+---------------------------------------------------+

作者:趙磊                                        

| email: [email protected]                      

+---------------------------------------------------+

文章版權歸原作者所有。                            

大家可以自由轉載這篇文章,但原版權信息必須保留。  

如需用於商業用途,請務必與原作者聯繫,若因未取得  

授權而收起的版權爭議,由侵權者自行負責。          

+---------------------------------------------------+

本章中我們仍然爲塊設備驅動程序使用高端內存做準備工作。
這裏要進行的準備工作並不意味着要增加或改變什麼功能,
而是要收拾一部分代碼,因爲它們看起來已經有點複雜了。


有編程經驗的讀者大概能夠意識到,編程時最常做的往往不是輸入程序,而是拷貝-粘貼。
這是由於我們在編程時可能會不斷地發現設計上的問題,或意識到還可以採用更好的結構,然後當然是實現它。
當然,更理想的情況大概是在一開始規劃時就確定一個最佳的結構,以避免將來的更改,
但事實往往會與理想背道而馳,但關鍵是我們發現這種苗頭時要及時糾正,而不是像某些部門一樣去得過且過大事化小來掩蓋問題。
要知道,酒是越陳越香,而垃圾卻是越捂越臭,如果我們無法在最初做出完美的設計,至少我們還擁有糾正的勇氣。


這裏讀者可能已經感覺到了,這裏我們將要修改simp_blkdev_make_request()函數,因爲它顯得有些大了,
以至於在前幾章中對其進行修改時,不得不列出大段的代碼來展示修改結果。
不過這不是主要原因,相對於縮短函數長度來說,我們分割函數時可能更加在意的是提高代碼的可讀性。


其實這裏分割simp_blkdev_make_request()也是爲了將來實現對高端內存的支持,
因爲訪問高端內存無疑將牽涉到頁面映射問題,而頁面映射的處理又牽涉到了這個函數,
因此我們也希望把這部分功能獨立出來,以免動戳就改動這個大函數,
也可能是爲了作者的偏好,因爲作者作者哪怕是改動函數中的一個字符,也會把整個函數從頭到尾檢查一番,
以確定這次改動不會產生其他影響,這就解釋了作者爲什麼更加偏愛簡單一些的函數了。
當然這種偏好也不一定完全是好事,比如前兩天選擇液晶電視時,作者就趨向於顯示器+機頂盒...


對於一直堅持到這一章的讀者而言,應該對simp_blkdev_make_request()函數的功能爛熟於心了,
因此我們直接列出修改後的代碼:

static int simp_blkdev_trans_oneseg(struct page *start_page,
                unsigned long offset, void *buf, unsigned int len, int dir)
{
        void *dsk_mem;


        dsk_mem = page_address(start_page);
        if (!dsk_mem) {
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": get page's address failed: %p\n", start_page);
                return -ENOMEM;
        }
        dsk_mem += offset;


        if (!dir)
                memcpy(buf, dsk_mem, len);
        else
                memcpy(dsk_mem, buf, len);


        return 0;
}


static int simp_blkdev_trans(unsigned long long dsk_offset, void *buf,
                unsigned int len, int dir)
{
        unsigned int done_cnt;
        struct page *this_first_page;
        unsigned int this_off;
        unsigned int this_cnt;


        done_cnt = 0;
        while (done_cnt < len) {
                /* iterate each data segment */
                this_off = (dsk_offset + done_cnt) & ~SIMP_BLKDEV_DATASEGMASK;
                this_cnt = min(len - done_cnt,
                        (unsigned int)SIMP_BLKDEV_DATASEGSIZE - this_off);


                this_first_page = radix_tree_lookup(&simp_blkdev_data,
                        (dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
                if (!this_first_page) {
                        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                ": search memory failed: %llu\n",
                                (dsk_offset + done_cnt)
                                >> SIMP_BLKDEV_DATASEGSHIFT);
                        return -ENOENT;
                }


                if (IS_ERR_VALUE(simp_blkdev_trans_oneseg(this_first_page,
                        this_off, buf + done_cnt, this_cnt, dir)))
                        return -EIO;


                done_cnt += this_cnt;
        }


        return 0;
}


static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
        int dir;
        unsigned long long dsk_offset;
        struct bio_vec *bvec;
        int i;
        void *iovec_mem;


        switch (bio_rw(bio)) {
        case READ:
        case READA:
                dir = 0;
                break;
        case WRITE:
                dir = 1;
                break;
        default:
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": unknown value of bio_rw: %lu\n", bio_rw(bio));
                goto bio_err;
        }


        if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
                > simp_blkdev_bytes) {
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": bad request: block=%llu, count=%u\n",
                        (unsigned long long)bio->bi_sector, bio->bi_size);
                goto bio_err;
        }


        dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;


        bio_for_each_segment(bvec, bio, i) {
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                if (!iovec_mem) {
                        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                ": map iovec page failed: %p\n", bvec->bv_page);
                        goto bio_err;
                }


                if (IS_ERR_VALUE(simp_blkdev_trans(dsk_offset, iovec_mem,
                        bvec->bv_len, dir)))
                        goto bio_err;


                kunmap(bvec->bv_page);


                dsk_offset += bvec->bv_len;
        }


#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
        bio_endio(bio, bio->bi_size, 0);
#else
        bio_endio(bio, 0);
#endif


        return 0;


bio_err:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
        bio_endio(bio, 0, -EIO);
#else
        bio_endio(bio, -EIO);
#endif
        return 0;
}

代碼在功能上與原先沒什麼不同,
我們只是從中抽象出處理塊設備與一段連續內存之間數據傳輸的simp_blkdev_trans()函數,
和同樣功能的、但數據長度符合塊設備數據塊長度限制的simp_blkdev_trans_oneseg()函數。


這樣一來,程序的結構就比較明顯了:
simp_blkdev_make_request()負責決定數據傳輸方向、檢查bio請求是否合法、遍歷bio中的每個bvec、映射bvec中的內存頁,
然後把剩餘的工作扔給simp_blkdev_trans(),
而simp_blkdev_trans()函數通過分割請求數據搞定了數據跨越多個塊設備數據塊的問題,並且順便把塊設備數據塊的第一個page給找了出來,
然後邀請simp_blkdev_trans_oneseg()函數出場。
simp_blkdev_trans_oneseg()函數是幸運的,因爲前期的大多數鋪墊工作已經做完了,而它只要像領導種樹一樣裝模作樣的添上最後一剷土,
就可以迎來開熱烈的掌聲。實際上,simp_blkdev_trans_oneseg()拿到page指針對應的內存,然後根據給定的數據方向執行指定長度的數據傳輸。
simp_blkdev_trans_oneseg()不需要關心數據長度是否超出塊設備數據塊邊界的問題,正如領導也不會去管那棵樹的死活一樣。


本章的代碼也同樣不做實驗,因爲我們確實也沒什麼好做的。
至於能不能通過編譯,作者已經試過了,有興趣的讀者大概可以驗證一下前一句話是不是真的。


作爲支持高端內存的前奏,前一章和本章中做了一些可能讓人覺得莫名其妙的改動。
不過到此爲止,準備工作已經做得差不多了,我們的程序已經爲支持高端內存打下堅實的基礎。
下一章將進入正題,我們將實現這一期盼已久的功能。


<未完,待續>

本章代碼同第十章


本人是在參考教程之後修改的教程內容.如有不同.可能有遺漏沒有修改.造成對讀者的迷惑,在此致歉~~ 

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