亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 297700 | 回復: 257
打印 上一主題 下一主題

[原創(chuàng)] 寫一個塊設備驅動 [復制鏈接]

論壇徽章:
0
跳轉到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2008-11-14 17:46 |只看該作者 |倒序瀏覽
第1章

+---------------------------------------------------+
|                 寫一個塊設備驅動                  |
+---------------------------------------------------+
| 作者:趙磊                                        |
| 網(wǎng)名:OstrichFly、飛翔的鴕鳥                      |
| email: zhaoleidd@hotmail.com                      |
+---------------------------------------------------+
| 文章版權歸原作者所有。                            |
| 大家可以自由轉載這篇文章,但原版權信息必須保留。  |
| 如需用于商業(yè)用途,請務必與原作者聯(lián)系,若因未取得  |
| 授權而收起的版權爭議,由侵權者自行負責。          |
+---------------------------------------------------+

同樣是讀書,讀小說可以行云流水,讀完后心情舒暢,意猶未盡;讀電腦書卻舉步艱難,讀完后目光呆滯,也是意猶未盡,只不過未盡的是痛苦的回憶。
研究證明,痛苦的記憶比快樂的更難忘記,因此電腦書中的內容比小說記得持久。
而這套教程的目的是要打破這種狀況,以至于讀者在忘記小說內容忘記本文。

在這套教程中,我們通過寫一個建立在內存中的塊設備驅動,來學習linux內核和相關設備驅動知識。
選擇寫塊設備驅動的原因是:
1:容易上手
2:可以牽連出更多的內核知識
3:像本文這樣的塊設備驅動教程不多,所以需要一個

好吧,扯淡到此結束,我們開始寫了。

本章的目的用盡可能最簡單的方法寫出一個能用的塊設備驅動。
所謂的能用,是指我們可以對這個驅動生成的塊設備進行mkfs,mount和讀寫文件。
為了盡可能簡單,這個驅動的規(guī)模不是1000行,也不是500行,而是100行以內。

這里插一句,我們不打算在這里介紹如何寫模塊,理由是介紹的文章已經(jīng)滿天飛舞了。
如果你能看得懂、并且成功地編譯、運行了這段代碼,我們認為你已經(jīng)達到了本教程的入學資格,
當然,如果你不幸的卡在這段代碼中,那么請等到搞定它以后再往下看:
mod.c:
#include <linux/module.h>

static int __init init_base(void)
{
        printk("----Hello. World----\n");
        return 0;
}

static void __exit exit_base(void)
{
        printk("----Bye----\n");
}

module_init(init_base);
module_exit(exit_base);

MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Zhao Lei");
MODULE_DESCRIPTION("For test");

Makefile:
obj-m := mod.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
        rm -rf Module.markers modules.order Module.symvers


好了,這里我們假定你已經(jīng)搞定上面的最簡單的模塊了,懂得什么是看模塊,以及簡單模塊的編寫、編譯、加載和卸載。
還有就是,什么是塊設備,什么是塊設備驅動,這個也請自行google吧,因為我們已經(jīng)迫不及待要寫完程序下課。

為了建立一個可用的塊設備,我們需要做......1件事情:
1:用add_disk()函數(shù)向系統(tǒng)中添加這個塊設備
   添加一個全局的
   static struct gendisk *simp_blkdev_disk;
   然后申明模塊的入口和出口:
   module_init(simp_blkdev_init);
   module_exit(simp_blkdev_exit);
   然后在入口處添加這個設備、出口處私房這個設備:
   static int __init simp_blkdev_init(void)
   {
           add_disk(simp_blkdev_disk);
        return 0;
   }
   static void __exit simp_blkdev_exit(void)
   {
           del_gendisk(simp_blkdev_disk);
   }

當然,在添加設備之前我們需要申請這個設備的資源,這用到了alloc_disk()函數(shù),因此模塊入口函數(shù)simp_blkdev_init(void)應該是:
   static int __init simp_blkdev_init(void)
   {
        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

           add_disk(simp_blkdev_disk);

        return 0;

   err_alloc_disk:
        return ret;
   }
還有別忘了在卸載模塊的代碼中也加一個行清理函數(shù):
  put_disk(simp_blkdev_disk);

還有就是,設備有關的屬性也是需要設置的,因此在alloc_disk()和add_disk()之間我們需要:
        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = ?1;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = ?2;
        simp_blkdev_disk->queue = ?3;
        set_capacity(simp_blkdev_disk, ?4);

SIMP_BLKDEV_DISKNAME其實是這個塊設備的名稱,為了紳士一些,我們把它定義成宏了:
#define SIMP_BLKDEV_DISKNAME        "simp_blkdev"

這里又引出了4個問號。(天哪,是不是有種受騙的感覺,像是陪老婆去做頭發(fā))
第1個問號:
  每個設備需要對應的主、從驅動號。
  我們的設備當然也需要,但很明顯我不是腦科醫(yī)生,因此跟寫linux的那幫瘋子不熟,得不到預先為我保留的設備號。
  還有一種方法是使用動態(tài)分配的設備號,但在這一章中我們希望盡可能做得簡單,因此也不采用這種方法。
  那么我們采用的是:搶別人的設備號。
  我們手頭沒有AK47,因此不敢干的太轟轟烈烈,而偷偷摸摸的事情倒是可以考慮的。
  柿子要撿軟的捏,而我們試圖找出一個不怎么用得上的設備,然后搶他的ID。
  打開linux/include/linux/major.h,把所有的設備一個個看下來,我們覺得最勝任被搶設備號的家伙非COMPAQ_SMART2_XXX莫屬。
  第一因為它不強勢,基本不會被用到,因此也不會造成沖突;第二因為它有錢,從COMPAQ_SMART2_MAJOR到COMPAQ_SMART2_MAJOR7有那8個之多的設備號可以被搶,不過癮的話還有它妹妹:COMPAQ_CISS_MAJOR~COMPAQ_CISS_MAJOR7。
  為了讓搶劫顯得紳士一些,我們在外面又定義一個宏:
  #define SIMP_BLKDEV_DEVICEMAJOR        COMPAQ_SMART2_MAJOR
  然后在?1的位置填上SIMP_BLKDEV_DEVICEMAJOR。
第2個問號:
  gendisk結構需要設置fops指針,雖然我們用不到,但該設還是要設的。
  好吧,就設個空得給它:
  在全局部分添加:
  struct block_device_operations simp_blkdev_fops = {
          .owner                = THIS_MODULE,
  };
  然后把?2的位置填上&simp_blkdev_fops。
第3個問號:
  這個比較麻煩一些。
  首先介紹請求隊列的概念。對大多數(shù)塊設備來說,系統(tǒng)會把對塊設備的訪問需求用bio和bio_vec表示,然后提交給通用塊層。
  通用塊層為了減少塊設備在尋道時損失的時間,使用I/O調度器對這些訪問需求進行排序,以盡可能提高塊設備效率。
  關于I/O調度器在本章中不打算進行深入的講解,但我們必須知道的是:
  1:I/O調度器把排序后的訪問需求通過request_queue結構傳遞給塊設備驅動程序處理
  2:我們的驅動程序需要設置一個request_queue結構
  申請request_queue結構的函數(shù)是blk_init_queue(),而調用blk_init_queue()函數(shù)時需要傳入一個函數(shù)的地址,這個函數(shù)擔負著處理對塊設備數(shù)據(jù)的請求。
  因此我們需要做的就是:
  1:實現(xiàn)一個static void simp_blkdev_do_request(struct request_queue *q)函數(shù)。
  2:加入一個全局變量,指向塊設備需要的請求隊列:
     static struct request_queue *simp_blkdev_queue;
  3:在加載模塊時用simp_blkdev_do_request()函數(shù)的地址作參數(shù)調用blk_init_queue()初始化一個請求隊列:
     simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
     if (!simp_blkdev_queue) {
             ret = -ENOMEM;
             goto err_init_queue;
     }
  4:卸載模塊時把simp_blkdev_queue還回去:
     blk_cleanup_queue(simp_blkdev_queue);
  5:在?3的位置填上simp_blkdev_queue。
第4個問號:
  這個還好,比前面的簡單多了,這里需要設置塊設備的大小。
  塊設備的大小使用扇區(qū)作為單位設置,而扇區(qū)的大小默認是512字節(jié)。
  當然,在把字節(jié)為單位的大小轉換為以扇區(qū)為單位時,我們需要除以512,或者右移9位可能更快一些。
  同樣,我們試圖把這一步也做得紳士一些,因此使用宏定義了塊設備的大小,目前我們定為16M:
  #define SIMP_BLKDEV_BYTES        (16*1024*1024)
  然后在?4的位置填上SIMP_BLKDEV_BYTES>>9。

看到這里,是不是有種身陷茫茫大海的無助感?并且一波未平,一波又起,在搞定這4個問號的同時,居然又引入了simp_blkdev_do_request函數(shù)!
當然,如果在身陷茫茫波濤中時你認為到處都是海,因此絕望,那么恭喜你可以不必挨到65歲再退休;
反之,如果你認為到處都是沒有三聚氰胺鮮魚,并且隨便哪個方向都是岸時,那么也恭喜你,你可以活著回來繼續(xù)享受身為納稅人的榮譽。

為了理清思路,我們把目前為止涉及到的代碼整理出來:
#define SIMP_BLKDEV_DEVICEMAJOR        COMPAQ_SMART2_MAJOR
#define SIMP_BLKDEV_DISKNAME        "simp_blkdev"
#define SIMP_BLKDEV_BYTES        (16*1024*1024)

static struct request_queue *simp_blkdev_queue;
static struct gendisk *simp_blkdev_disk;

static void simp_blkdev_do_request(struct request_queue *q);

struct block_device_operations simp_blkdev_fops = {
        .owner                = THIS_MODULE,
};

static int __init simp_blkdev_init(void)
{
        int ret;

        simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
        if (!simp_blkdev_queue) {
                ret = -ENOMEM;
                goto err_init_queue;
        }

        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = &simp_blkdev_fops;
        simp_blkdev_disk->queue = simp_blkdev_queue;
        set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
        add_disk(simp_blkdev_disk);

        return 0;

err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
        return ret;
}

static void __exit simp_blkdev_exit(void)
{
        del_gendisk(simp_blkdev_disk);
        put_disk(simp_blkdev_disk);
        blk_cleanup_queue(simp_blkdev_queue);
}

module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);

剩下部分的不多了,真的不多了。請相信我,因為我不在質監(jiān)局上班。
我寫的文章誠實可靠,并且不拿你納稅的錢。

我們還有一個最重要的函數(shù)需要實現(xiàn),就是負責處理塊設備請求的simp_blkdev_do_request()。

首先我們看看究竟把塊設備的數(shù)據(jù)以什么方式放在內存中。
畢竟這是在第1章,因此我們將使用最simple的方式實現(xiàn),也就是,數(shù)組。
我們在全局代碼中定義:
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
對驅動程序來說,這個數(shù)組看起來大了一些,如果不幸被懂行的人看到,將100%遭到最無情、最嚴重的鄙視。
而我們卻從極少數(shù)公仆那里學到了最有效的應對之策,那就是:無視他,然后把他定為成“不明真相的群眾”。

然后我們著手實現(xiàn)simp_blkdev_do_request。
這里介紹elv_next_request()函數(shù),原型是:
struct request *elv_next_request(struct request_queue *q);
用來從一個請求隊列中拿出一條請求(其實嚴格來說,拿出的可能是請求中的一段)。
隨后的處理請求本質上是根據(jù)rq_data_dir(req)返回的該請求的方向(讀/寫),把塊設備中的數(shù)據(jù)裝入req->buffer、或是把req->buffer中的數(shù)據(jù)寫入塊設備。
剛才已經(jīng)提及了與request結構相關的rq_data_dir()宏和.buffer成員,其他幾個相關的結構成員和函數(shù)是:
request.sector:請求的開始磁道
request.current_nr_sectors:請求磁道數(shù)
end_request():結束一個請求,第2個參數(shù)表示請求處理結果,成功時設定為1,失敗時設置為0或者錯誤號。
因此我們的simp_blkdev_do_request()函數(shù)為:
static void simp_blkdev_do_request(struct request_queue *q)
{
        struct request *req;
        while ((req = elv_next_request(q)) != NULL) {
                if ((req->sector + req->current_nr_sectors) << 9
                        > SIMP_BLKDEV_BYTES) {
                        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                ": bad request: block=%llu, count=%u\n",
                                (unsigned long long)req->sector,
                                req->current_nr_sectors);
                        end_request(req, 0);
                        continue;
                }

                switch (rq_data_dir(req)) {
                case READ:
                        memcpy(req->buffer,
                                simp_blkdev_data + (req->sector << 9),
                                req->current_nr_sectors << 9);
                        end_request(req, 1);
                        break;
                case WRITE:
                        memcpy(simp_blkdev_data + (req->sector << 9),
                                req->buffer, req->current_nr_sectors << 9);
                        end_request(req, 1);
                        break;
                default:
                        /* No default because rq_data_dir(req) is 1 bit */
                        break;
                }
        }
}
函數(shù)使用elv_next_request()遍歷struct request_queue *q中使用struct request *req表示的每一段,首先判斷這個請求是否超過了我們的塊設備的最大容量,
然后根據(jù)請求的方向rq_data_dir(req)進行相應的請求處理。由于我們使用的是指簡單的數(shù)組,因此請求處理僅僅是2條memcpy。
memcpy中也牽涉到了扇區(qū)號到線性地址的轉換操作,我想對堅持到這里的讀者來說,這個操作應該不需要進一步解釋了。

編碼到此結束,然后我們試試這個程序:
首先編譯:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step1 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
#
加載模塊
# insmod simp_blkdev.ko
#
用lsmod看看。
這里我們注意到,該模塊的Used by為0,因為它既沒有被其他模塊使用,也沒有被mount。
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  0
...
#
如果當前系統(tǒng)支持udev,在調用add_disk()函數(shù)時即插即用機制會自動為我們在/dev/目錄下建立設備文件。
設備文件的名稱為我們在gendisk.disk_name中設置的simp_blkdev,主、從設備號也是我們在程序中設定的72和0。
如果當前系統(tǒng)不支持udev,那么很不幸,你需要自己用mknod /dev/simp_blkdev  b 72 0來創(chuàng)建設備文件了。
# ls -l /dev/simp_blkdev
brw-r----- 1 root disk 72, 0 11-10 18:13 /dev/simp_blkdev
#
在塊設備中創(chuàng)建文件系統(tǒng),這里我們創(chuàng)建常用的ext3。
當然,作為通用的塊設備,創(chuàng)建其他類型的文件系統(tǒng)也沒問題。
# mkfs.ext3 /dev/simp_blkdev
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
4096 inodes, 16384 blocks
819 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=16777216
2 block groups
8192 blocks per group, 8192 fragments per group
2048 inodes per group
Superblock backups stored on blocks:
        8193

Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 38 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
#
如果這是第一次使用,建議創(chuàng)建一個目錄用來mount這個設備中的文件系統(tǒng)。
當然,這不是必需的。如果你對mount之類的用法很熟,你完全能夠自己決定在這里干什么,甚至把這個設備mount成root。
# mkdir -p /mnt/temp1
#
把建立好文件系統(tǒng)的塊設備mount到剛才建立的目錄中
# mount /dev/simp_blkdev /mnt/temp1
#
看看現(xiàn)在的mount表
# mount
...
/dev/simp_blkdev on /mnt/temp1 type ext3 (rw)
#
看看現(xiàn)在的模塊引用計數(shù),從剛才的0變成1了,
原因是我們mount了。
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  1
...
#
看看文件系統(tǒng)的內容,有個mkfs時自動建立的lost+found目錄。
# ls /mnt/temp1
lost+found
#
隨便拷點東西進去
# cp /etc/init.d/* /mnt/temp1
#
再看看
# ls /mnt/temp1
acpid           conman              functions  irqbalance    mdmpd           NetworkManagerDispatcher  rdisc            sendmail        winbind
anacron         cpuspeed            gpm        kdump         messagebus      nfs                       readahead_early  setroubleshoot  wpa_supplicant
apmd            crond               haldaemon  killall       microcode_ctl   nfslock                   readahead_later  single          xfs
atd             cups                halt       krb524        multipathd      nscd                      restorecond      smartd          xinetd
auditd          cups-config-daemon  hidd       kudzu         netconsole      ntpd                      rhnsd            smb             ypbind
autofs          dhcdbd              ip6tables  lost+found    netfs           pand                      rpcgssd          sshd            yum-updatesd
avahi-daemon    dund                ipmi       lvm2-monitor  netplugd        pcscd                     rpcidmapd        syslog
avahi-dnsconfd  firstboot           iptables   mcstrans      network         portmap                   rpcsvcgssd       vmware
bluetooth       frecord             irda       mdmonitor     NetworkManager  psacct                    saslauthd        vncserver
#
現(xiàn)在這個塊設備的使用情況是
# df
文件系統(tǒng)               1K-塊        已用     可用 已用% 掛載點
...
/dev/simp_blkdev         15863      1440     13604  10% /mnt/temp1
#
再全刪了玩玩
# rm -rf /mnt/temp1/*
#
看看刪完了沒有
# ls /mnt/temp1
#
好了,大概玩夠了,我們把文件系統(tǒng)umount掉
# umount /mnt/temp1
#
模塊的引用計數(shù)應該還原成0了吧
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  0
...
#
最后一步,移除模塊
# rmmod simp_blkdev
#

這是這部教程的第1章,不好意思的是,內容比預期還是難了一些。
當初還有一種考慮是在本章中僅僅實現(xiàn)一個寫了就丟的塊設備驅動,也就是說,對這個塊設備的操作只能到mkfs這一部,而不能繼續(xù)mount,因為剛才寫的數(shù)據(jù)全被扔了。
或者更簡單些,僅僅寫一個hello world的模塊。
但最后還是寫成了現(xiàn)在這樣沒,因為我覺得拿出一個真正可用的塊設備驅動程序對讀者來說更有成就感。

無論如何,本章是一個開始,而你,已經(jīng)跨入了學習塊設備驅動教室的大門,或者通俗來說,上了賊船。
而在后續(xù)的章節(jié)中,我們將陸續(xù)完善對這個程序,通過追加或者強化這個程序,來學習與塊設備有關、或與塊設備無關但與linux有關的方方面面。
總之,我希望通過這部教程,起碼讓讀者學到有用的知識,或者更進一步,引導讀者對linux的興趣,甚至領悟學習一切科學所需要的鉆研精神。

作為第一章的結尾,引用我在另一篇文章中的序言:
謹以此文向讀者示范什么叫做嚴謹?shù)难芯俊?br /> 呼喚踏實的治學態(tài)度,反對浮躁的論壇風氣。
--OstrichFly

<未完,待續(xù)>

評分

參與人數(shù) 2可用積分 +60 收起 理由
cjaizss + 30 我很贊同
bitmilong + 30 精品文章

查看全部評分

論壇徽章:
0
2 [報告]
發(fā)表于 2008-11-14 17:47 |只看該作者

第2章

+---------------------------------------------------+
|                 寫一個塊設備驅動                  |
+---------------------------------------------------+
| 作者:趙磊                                        |
| email: zhaoleidd@hotmail.com                      |
+---------------------------------------------------+
| 文章版權歸原作者所有。                            |
| 大家可以自由轉載這篇文章,但原版權信息必須保留。  |
| 如需用于商業(yè)用途,請務必與原作者聯(lián)系,若因未取得  |
| 授權而收起的版權爭議,由侵權者自行負責。          |
+---------------------------------------------------+

上一章不但實現(xiàn)了一個最簡單的塊設備驅動程序,而且可能也成功地嚇退了不少準備繼續(xù)看下去的讀者。
因為第一章看起來好像太難了。
不過讀者也不要過于埋怨作者,因為大多數(shù)情況下第一次都不是什么好的體驗......

對于堅持到這里的讀者,這一章中,我們準備了一些簡單的內容來犒勞大家。

關于塊設備與I/O調度器的關系,我們在上一章中已經(jīng)有所提及。
I/O調度器可以通過合并請求、重排塊設備操作順序等方式提高塊設備訪問的順序。
就好像吃街邊的大排檔,如果點一個冷門的品種,可能會等更長的時間,
而如果點的恰好與旁邊桌子上剛點的相同,那么會很快上來,因為廚師八成索性一起炒了。
然而I/O調度器和塊設備的情況卻有一些微妙的區(qū)別,大概可以類比成人家點了個西紅柿雞蛋湯你接著就點了個西紅柿炒蛋。
聰明的廚師一定會先做你的菜,因為隨后可以直接往鍋里加水煮湯,可憐比你先來的人喝的卻是你的刷鍋水。
兩個菜一鍋煮表現(xiàn)在塊設備上可以類比成先后訪問塊設備的同一個位置,這倒是與I/O調度器無關,有空學習linux緩存策略時可以想想這種情況。

一個女孩子換了好多件衣服問我漂不漂亮,而我的評價只要一眼就能拿出來。
對方總覺得衣服要牌子好、面料好、搭配合理、要符合個人的氣質、要有文化,而我的標準卻簡單的多:越薄越好。
所謂臭氣相投,我寫的塊設備驅動程序對I/O調度器的要求大概也是如此。
究其原因倒不是因為塊設備驅動程序好色,而是這個所謂塊設備中的數(shù)據(jù)都是在內存中的。
這也意味著我們的“塊設備”讀寫迅速、并且不存在磁盤之類設備通常面臨的尋道時間。
因此對這個“塊設備”而言,一個復雜的I/O調度器不但發(fā)揮不了絲毫作用,反而其本身將白白耗掉不少內存和CPU。
同樣的情況還出現(xiàn)在固態(tài)硬盤、U盤、記憶棒之類驅動中。將來固態(tài)硬盤流行之時,大概就是I/O調度器消亡之日了。

這里我們試圖給我們的塊設備驅動選擇一個最簡單的I/O調度器。
目前l(fā)inux中包含anticipatory、cfq、deadline和noop這4個I/O調度器。
2.6.18之前的linux默認使用anticipatory,而之后的默認使用cfq。
關于這4個調度器的原理和特性我們不打算在這里介紹,原因是相關的介紹滿網(wǎng)都是。
但我們還是不能避免在這里提及一下noop調度器,因為我們馬上要用到它。
noop顧名思義,是一個基本上不干事的調度器。它基本不對請求進行什么附加的處理,僅僅假惺惺地告訴通用塊設備層:我處理完了。
但與吃空餉的公仆不同,noop的存在還是有不少進步意義的。至少我們現(xiàn)在就需要一個不要沒事添亂的I/O調度器。

選擇一個指定的I/O調度器需要這個函數(shù):
int elevator_init(struct request_queue *q, char *name);
q是請求隊列的指針,name是需要設定的I/O調度器的名稱。
如果name為NULL,那么內核會首先嘗試選擇啟動參數(shù)"elevator="中指定的調度器,
不成功的話就去選擇編譯內核時指定的默認調度器,
如果運氣太背還是不成功,就去選擇"noop"調度器。
不要問我怎么知道的,一切皆在RTFSC(Read the F**ing Source Code --Linus Torvalds)。

對于我們的代碼,就是在simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL)后面加上:
elevator_init(simp_blkdev_queue, "noop");

但問題是在blk_init_queue()函數(shù)中系統(tǒng)已經(jīng)幫我們申請一個了,因此這里我們需要費點周折,把老的那個送回去。
所以我們的代碼應該是:
simp_blkdev_init()函數(shù)開頭處:
elevator_t *old_e;
blk_init_queue()函數(shù)之后:
old_e = simp_blkdev_queue->elevator;
if (IS_ERR_VALUE(elevator_init(simp_blkdev_queue, "noop")))
        printk(KERN_WARNING "Switch elevator failed, using default\n");
else
        elevator_exit(old_e);

為方便閱讀并提高本文在google磁盤中的占用率,我們給出修改后的整個simp_blkdev_init()函數(shù):
static int __init simp_blkdev_init(void)
{
        int ret;
        elevator_t *old_e;

        simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
        if (!simp_blkdev_queue) {
                ret = -ENOMEM;
                goto err_init_queue;
        }

        old_e = simp_blkdev_queue->elevator;
        if (IS_ERR_VALUE(elevator_init(simp_blkdev_queue, "noop")))
                printk(KERN_WARNING "Switch elevator failed, using default\n");
        else
                elevator_exit(old_e);

        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = &simp_blkdev_fops;
        simp_blkdev_disk->queue = simp_blkdev_queue;
        set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
        add_disk(simp_blkdev_disk);

        return 0;

err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
        return ret;
}

本章的改動很小,我們現(xiàn)在測試一下這段代碼:
首先我們像原先那樣編譯模塊并加載:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step2 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step2/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step2/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step2/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
# insmod simp_blkdev.ko
#
然后看一看咱們的這個塊設備現(xiàn)在使用的I/O調度器:
# cat /sys/block/simp_blkdev/queue/scheduler
[noop] anticipatory deadline cfq
#
看樣子是成功了。

哦,上一章中忘了看老程序的調度器信息了,這里補上老程序的情況:
# cat /sys/block/simp_blkdev/queue/scheduler
noop anticipatory deadline [cfq]
#

OK,我們完成簡單的一章,并且用事實說明了作者并沒有在開頭撒謊。
當然,作者也會力圖讓接下來的章節(jié)同樣比小說易讀。

<未完,待續(xù)>

論壇徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辭舊歲徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
3 [報告]
發(fā)表于 2008-11-14 17:47 |只看該作者
支持原創(chuàng),期待繼續(xù)

論壇徽章:
36
IT運維版塊每日發(fā)帖之星
日期:2016-04-10 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-04-16 06:20:0015-16賽季CBA聯(lián)賽之廣東
日期:2016-04-16 19:59:32IT運維版塊每日發(fā)帖之星
日期:2016-04-18 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-04-19 06:20:00每日論壇發(fā)貼之星
日期:2016-04-19 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-04-25 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-06 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-08 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-13 06:20:00IT運維版塊每日發(fā)帖之星
日期:2016-05-28 06:20:00每日論壇發(fā)貼之星
日期:2016-05-28 06:20:00
4 [報告]
發(fā)表于 2008-11-14 18:11 |只看該作者
頂一下。
中文內核郵件列表中有個Zhao Lei是不是LZ啊

論壇徽章:
2
2015年辭舊歲徽章
日期:2015-03-03 16:54:152015元宵節(jié)徽章
日期:2015-03-06 15:50:39
5 [報告]
發(fā)表于 2008-11-14 22:42 |只看該作者
看完了第一章 感覺很是受益啊 明天再看第二章 感謝LZ無私的奉獻
看樓主的行文風格 冒昧的猜測 很像fudan_abc大俠啊  

論壇徽章:
0
6 [報告]
發(fā)表于 2008-11-15 10:26 |只看該作者
原帖由 Godbach 于 2008-11-14 18:11 發(fā)表
頂一下。
中文內核郵件列表中有個Zhao Lei是不是LZ啊

Mail List 里面那個 zhao lei 是 ShadowStar,是 ipp2p 模塊修改者
http://linux.chinaunix.net/bbs/thread-1038871-1-1.html

論壇徽章:
0
7 [報告]
發(fā)表于 2008-11-15 11:22 |只看該作者
感謝lz  只搞過字符設備和網(wǎng)絡設備驅動    這回有機會看看了
不過不知道是否直接可以看 還是要把ldd相關章節(jié)干掉才能閱讀

論壇徽章:
0
8 [報告]
發(fā)表于 2008-11-17 08:55 |只看該作者
原帖由 Godbach 于 2008-11-14 18:11 發(fā)表
頂一下。
中文內核郵件列表中有個Zhao Lei是不是LZ啊

有個叫zhaolei@cn。fujitsu。c0m的是我

論壇徽章:
0
9 [報告]
發(fā)表于 2008-11-17 09:00 |只看該作者
原帖由 duanius 于 2008-11-15 11:22 發(fā)表
感謝lz  只搞過字符設備和網(wǎng)絡設備驅動    這回有機會看看了
不過不知道是否直接可以看 還是要把ldd相關章節(jié)干掉才能閱讀

建議的讀法:
直接讀,不懂的地方直接去google。

比如文章中用到的一些函數(shù)都沒有給出詳細說明,畢竟我不想寫成函數(shù)說明書。
如果希望深入研究的話,很多文章中沒有提到的內容還是需要了解的。
不過有了google咱們還怕什么?google不到,還有源碼不是?

謝謝支持!

論壇徽章:
0
10 [報告]
發(fā)表于 2008-11-17 18:47 |只看該作者

第3章

+---------------------------------------------------+
|                 寫一個塊設備驅動                  |
+---------------------------------------------------+
| 作者:趙磊                                        |
| email: zhaoleidd@hotmail.com                      |
+---------------------------------------------------+
| 文章版權歸原作者所有。                            |
| 大家可以自由轉載這篇文章,但原版權信息必須保留。  |
| 如需用于商業(yè)用途,請務必與原作者聯(lián)系,若因未取得  |
| 授權而收起的版權爭議,由侵權者自行負責。          |
+---------------------------------------------------+

上一章中我們討論了mm的衣服問題,并成功地為她換上了一件輕如鴻毛、關鍵是薄如蟬翼的新衣服。
而這一章中,我們打算稍稍再前進一步,也就是:給她脫光。
目的是更加符合我們的審美觀、并且能夠更加深入地了解該mm(喜歡制服皮草的讀者除外)。
付出的代價是這一章的內容要稍稍復雜一些。

雖然noop調度器確實已經(jīng)很簡單了,簡單到比我們的驅動程序還簡單,在2.6.27中的120行代碼量已經(jīng)充分說明了這個問題。
但顯而易見的是,不管它多簡單,只要它存在,我們就把它看成累贅。
這里我們不打算再次去反復磨嘴皮子論證不使用I/O調度器能給我們的驅動程序帶來什么樣的好處、面臨的困難、以及如何與國際接軌的諸多事宜,
畢竟現(xiàn)在不是在討論汽油降價,而我們也不是中石油。我們更關心的是實實在在地做一些對驅動程序有益的事情。

不過I/O調度器這層遮體衣服倒也不是這么容易脫掉的,因為實際上我們還使用了它捆綁的另一個功能,就是請求隊列。
因此我們在前兩章中的程序才如此簡單。
從細節(jié)上來說,請求隊列request_queue中有個make_request_fn成員變量,我們看它的定義:
struct request_queue
{
        ...
        make_request_fn         *make_request_fn;
        ...
}
它實際上是:
typedef int (make_request_fn) (struct request_queue *q, struct bio *bio);
也就是一個函數(shù)的指針。

如果上面這段話讓讀者感到莫名其妙,那么請搬個板凳坐下,Let's Begin the Story。

對通用塊層的訪問,比如請求讀某個塊設備上的一段數(shù)據(jù),通常是準備一個bio,然后調用generic_make_request()函數(shù)來實現(xiàn)的。
調用者是幸運的,因為他往往不需要去關心generic_make_request()函數(shù)如何做的,只需要知道這個神奇的函數(shù)會為他搞定所有的問題就OK了。
而我們卻沒有這么幸運,因為對一個塊設備驅動的設計者來說,如果不知道generic_make_request()函數(shù)的內部情況,很可能會讓驅動的使用者得不到安全感。

了解generic_make_request()內部的有效方法還是RTFSC,但這里會給出一些提示。
我們可以在generic_make_request()中找到__generic_make_request(bio)這么一句,
然后在__generic_make_request()函數(shù)中找到ret = q->make_request_fn(q, bio)這么一行。
偷懶省略掉解開謎題的所有關鍵步驟后,這里可以得出一個作者相信但讀者不一定相信的正確結論:
generic_make_request()最終是通過調用request_queue.make_request_fn函數(shù)完成bio所描述的請求處理的。

Story到此結束,現(xiàn)在我們可以解釋剛才為什么列出那段莫名其妙的數(shù)據(jù)結構的意圖了。
對于塊設備驅動來說,正是request_queue.make_request_fn函數(shù)負責處理這個塊設備上的所有請求。
也就是說,只要我們實現(xiàn)了request_queue.make_request_fn,那么塊設備驅動的Primary Mission就接近完成了。
在本章中,我們要做的就是:
1:讓request_queue.make_request_fn指向我們設計的make_request函數(shù)
2:把我們設計的make_request函數(shù)寫出來

如果讀者現(xiàn)在已經(jīng)意氣風發(fā)地拿起鍵盤躍躍欲試了,作者一定會假裝謙虛地問讀者一個問題:
你的鉆研精神遇到城管了?
如果這句話問得讀者莫名其妙的話,作者將補充另一個問題:
前兩章中明顯沒有實現(xiàn)make_request函數(shù),那時的驅動程序倒是如何工作的?
然后就是清清嗓子自問自答。

前兩章確實沒有用到make_request函數(shù),但當我們使用blk_init_queue()獲得request_queue時,
萬能的系統(tǒng)知道我們搞IT的都低收入,因此救濟了我們一個,這就是大名鼎鼎的__make_request()函數(shù)。
request_queue.make_request_fn指向了__make_request()函數(shù),因此對塊設備的所有請求被導向了__make_request()函數(shù)中。

__make_request()函數(shù)不是吃素的,馬上喊上了他的兄弟,也就是I/O調度器來幫忙,結果就是bio請求被I/O調度器處理了。
同時,__make_request()自身也沒閑著,它把bio這條咸魚嗅了嗅,舔了舔,然后放到嘴里嚼了嚼,把魚刺魚鱗剔掉,
然后情意綿綿地通過do_request函數(shù)(也就是blk_init_queue的第一個參數(shù))喂到驅動程序作者的口中。
這就解釋了前兩章中我們如何通過simp_blkdev_do_request()函數(shù)處理塊設備請求的。

我們理解__make_request()函數(shù)本意不錯,它把bio這條咸魚嚼成request_queue喂給do_request函數(shù),能讓我們的到如下好處:
1:request.buffer不在高端內存
   這意味著我們不需要考慮映射高端內存到虛存的情況
2:request.buffer的內存是連續(xù)的
   因此我們不需要考慮request.buffer對應的內存地址是否分成幾段的問題
這些好處看起來都很自然,正如某些行政不作為的“有關部門”認為老百姓納稅養(yǎng)他們也自然,
但不久我們就會看到不很自然的情況。

如果讀者是mm,或許會認為一個摔鍋把咸魚嚼好了含情脈脈地喂過來是一件很浪漫的事情(也希望這位讀者與作者聯(lián)系),
但對于大多數(shù)男性IT工作者來說,除非取向問題,否則......
因此現(xiàn)在我們寧可把__make_request()函數(shù)一腳踢飛,然后自己去嚼bio這條咸魚。
當然,踢飛__make_request()函數(shù)也意味著擺脫了I/O調度器的處理。

踢飛__make_request()很容易,使用blk_alloc_queue()函數(shù)代替blk_init_queue()函數(shù)來獲取request_queue就行了。
也就是說,我們把原先的
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
改成了
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
這樣。

至于嚼人家口水渣的simp_blkdev_do_request()函數(shù),我們也一并扔掉:
把simp_blkdev_do_request()函數(shù)從頭到尾刪掉。

同時,由于現(xiàn)在要脫光,所以上一章中我們費好大勁換上的那件薄內衣也不需要了,
也就是把上一章中增加的elevator_init()這部分的函數(shù)也刪了,也就是刪掉如下部分:
old_e = simp_blkdev_queue->elevator;
if (IS_ERR_VALUE(elevator_init(simp_blkdev_queue, "noop")))
        printk(KERN_WARNING "Switch elevator failed, using default\n");
else
        elevator_exit(old_e);

到這里我們已經(jīng)成功地讓__make_request()升空了,但要自己嚼bio,還需要添加一些東西:
首先給request_queue指定我們自己的bio處理函數(shù),這是通過blk_queue_make_request()函數(shù)實現(xiàn)的,把這面這行加在blk_alloc_queue()之后:
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
然后實現(xiàn)我們自己的simp_blkdev_make_request()函數(shù),
然后編譯。

如果按照上述的描述修改出的代碼讓讀者感到信心不足,我們在此列出修改過的simp_blkdev_init()函數(shù):
static int __init simp_blkdev_init(void)
{
        int ret;

        simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
        if (!simp_blkdev_queue) {
                ret = -ENOMEM;
                goto err_alloc_queue;
        }
        blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);

        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = &simp_blkdev_fops;
        simp_blkdev_disk->queue = simp_blkdev_queue;
        set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
        add_disk(simp_blkdev_disk);

        return 0;

err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
        return ret;
}
這里還把err_init_queue也改成了err_alloc_queue,希望讀者不要打算就這一點進行提問。

正如本章開頭所述,這一章的內容可能要復雜一些,而現(xiàn)在看來似乎已經(jīng)做到了。
而現(xiàn)在的進度大概是......一半!
不過值得安慰的是,余下的內容只有我們的simp_blkdev_make_request()函數(shù)了。

首先給出函數(shù)原型:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio);
該函數(shù)用來處理一個bio請求。
函數(shù)接受struct request_queue *q和struct bio *bio作為參數(shù),與請求有關的信息在bio參數(shù)中,
而struct request_queue *q并沒有經(jīng)過__make_request()的處理,這也意味著我們不能用前幾章那種方式使用q。
因此這里我們關注的是:bio。

關于bio和bio_vec的格式我們仍然不打算在這里做過多的解釋,理由同樣是因為我們要避免與google出的一大堆文章撞衫。
這里我們只說一句話:
bio對應塊設備上一段連續(xù)空間的請求,bio中包含的多個bio_vec用來指出這個請求對應的每段內存。
因此simp_blkdev_make_request()本質上是在一個循環(huán)中搞定bio中的每個bio_vec。

這個神奇的循環(huán)是這樣的:
dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);

bio_for_each_segment(bvec, bio, i) {
        void *iovec_mem;

        switch (bio_rw(bio)) {
        case READ:
        case READA:
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                memcpy(iovec_mem, dsk_mem, bvec->bv_len);
                kunmap(bvec->bv_page);
                break;
        case WRITE:
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                memcpy(dsk_mem, iovec_mem, bvec->bv_len);
                kunmap(bvec->bv_page);
                break;
        default:
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": unknown value of bio_rw: %lu\n",
                        bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
                bio_endio(bio, 0, -EIO);
#else
                bio_endio(bio, -EIO);
#endif
                return 0;
        }
        dsk_mem += bvec->bv_len;
}
bio請求的塊設備起始扇區(qū)和扇區(qū)數(shù)存儲在bio.bi_sector和bio.bi_size中,
我們首先通過bio.bi_sector獲得這個bio請求在我們的塊設備內存中的起始部分位置,存入dsk_mem。
然后遍歷bio中的每個bio_vec,這里我們使用了系統(tǒng)提供的bio_for_each_segment宏。

循環(huán)中的代碼看上去有些眼熟,無非是根據(jù)請求的類型作相應的處理。READA意味著預讀,精心設計的預讀請求可以提高I/O效率,
這有點像內存中的prefetch(),我們同樣不在這里做更詳細的介紹,因為這本身就能寫一整篇文章,對于我們的基于內存的塊設備驅動,
只要按照READ請求同樣處理就OK了。

在很眼熟的memcpy前后,我們發(fā)現(xiàn)了kmap和kunmap這兩個新面孔。
這也證明了咸魚要比爛肉難啃的道理。
bio_vec中的內存地址是使用page *描述的,這也意味著內存頁面有可能處于高端內存中而無法直接訪問。
這種情況下,常規(guī)的處理方法是用kmap映射到非線性映射區(qū)域進行訪問,當然,訪問完后要記得把映射的區(qū)域還回去,
不要仗著你內存大就不還,實際上在i386結構中,你內存越大可用的非線性映射區(qū)域越緊張。
關于高端內存的細節(jié)也請自行google,反正在我的印象中intel總是有事沒事就弄些硬件限制給程序員找麻煩以幫助程序員的就業(yè)。
所幸的是逐漸流行的64位機的限制應該不那么容易突破了,至少我這么認為。

switch中的default用來處理其它情況,而我們的處理卻很簡單,拋出一條錯誤信息,然后調用bio_endio()告訴上層這個bio錯了。
不過這個萬惡的bio_endio()函數(shù)在2.6.24中改了,如果我們的驅動程序是內核的一部分,那么我們只要同步更新調用bio_endio()的語句就行了,
但現(xiàn)在的情況顯然不是,而我們又希望這個驅動程序能夠同時適應2.6.24之前和之后的內核,因此這里使用條件編譯來比較內核版本。
同時,由于使用到了LINUX_VERSION_CODE和KERNEL_VERSION宏,因此還需要增加#include <linux/version.h>。

循環(huán)的最后把這一輪循環(huán)中完成處理的字節(jié)數(shù)加到dsk_mem中,這樣dsk_mem指向在下一個bio_vec對應的塊設備中的數(shù)據(jù)。

讀者或許開始耐不住性子想這一章怎么還不結束了,是的,馬上就結束,不過我們還要在循環(huán)的前后加上一丁點:
1:循環(huán)之前的變量聲明:
   struct bio_vec *bvec;
   int i;
   void *dsk_mem;
2:循環(huán)之前檢測訪問請求是否超越了塊設備限制:
   if ((bio->bi_sector << 9) + 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);
   #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
           bio_endio(bio, 0, -EIO);
   #else
           bio_endio(bio, -EIO);
   #endif
           return 0;
   }
3:循環(huán)之后結束這個bio,并返回成功:
   #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_endio用于返回這個對bio請求的處理結果,在2.6.24之后的內核中,第一個參數(shù)是被處理的bio指針,第二個參數(shù)成功時為0,失敗時為-ERRNO。
   在2.6.24之前的內核中,中間還多了個unsigned int bytes_done,用于返回搞定了的字節(jié)數(shù)。

現(xiàn)在可以長長地舒一口氣了,我們完工了。
還是附上simp_blkdev_make_request()的完成代碼:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
        struct bio_vec *bvec;
        int i;
        void *dsk_mem;

        if ((bio->bi_sector << 9) + 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);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
                bio_endio(bio, 0, -EIO);
#else
                bio_endio(bio, -EIO);
#endif
                return 0;
        }

        dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);

        bio_for_each_segment(bvec, bio, i) {
                void *iovec_mem;

                switch (bio_rw(bio)) {
                case READ:
                case READA:
                        iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                        memcpy(iovec_mem, dsk_mem, bvec->bv_len);
                        kunmap(bvec->bv_page);
                        break;
                case WRITE:
                        iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                        memcpy(dsk_mem, iovec_mem, bvec->bv_len);
                        kunmap(bvec->bv_page);
                        break;
                default:
                        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                ": unknown value of bio_rw: %lu\n",
                                bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
                        bio_endio(bio, 0, -EIO);
#else
                        bio_endio(bio, -EIO);
#endif
                        return 0;
                }
                dsk_mem += 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;
}

讀者可以直接用本章的simp_blkdev_make_request()函數(shù)替換掉上一章的simp_blkdev_do_request()函數(shù),
然后用本章的simp_blkdev_init()函數(shù)替換掉上一章的同名函數(shù),再在文件頭部增加#include <linux/version.h>,
就得到了本章的最終代碼。

在結束本章之前,我們還是試驗一下:
首先還是編譯和加載:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step3 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step3/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step3/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step3/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
# insmod simp_blkdev.ko
#
然后使用上一章中的方法看看sysfs中的這個設備的信息:
# ls /sys/block/simp_blkdev
dev  holders  range  removable  size  slaves  stat  subsystem  uevent
#
我們發(fā)現(xiàn)我們的驅動程序在sysfs目錄中的queue子目錄不見了。
這并不奇怪,否則就要抓狂了。

本章中我們實現(xiàn)自己的make_request函數(shù)來處理bio,以此擺脫了I/O調度器和通用的__make_request()對bio的處理。
由于我們的塊設備中的數(shù)據(jù)都是存在于內存中,不牽涉到DMA操作、并且不需要尋道,因此這應該是最適合這種形態(tài)的塊設備的處理方式。
在linux中類似的驅動程序大多使用了本章中的處理方式,但對大多數(shù)基于物理磁盤的塊設備驅動來說,使用適合的I/O調度器更能提高性能。
同時,__make_request()中包含的回彈機制對需要進行DMA操作的塊設備驅動來說,也能提供不錯幫助。

雖然說量變產(chǎn)生質變,通常質變比量變要復雜得多。
同理,相比前一章,把mm衣服脫光也比讓她換一件薄一些的衣服要困難得多。
不過無論如何,我們總算連哄帶騙地讓mm脫下來了,而付出了滿頭大汗的代價:
本章內容的復雜度相比前一章大大加深了。

如果本章的內容不幸使讀者感覺頭部體積有所增加的話,作為彌補,我們將宣布一個好消息:
因為根據(jù)慣例,隨后的1、2章將會出現(xiàn)一些輕松的內容讓讀者得到充分休息。

<未完,待續(xù)>
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復

  

北京盛拓優(yōu)訊信息技術有限公司. 版權所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關心和支持過ChinaUnix的朋友們 轉載本站內容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP