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

  免費(fèi)注冊 查看新帖 |

Chinaunix

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

[原創(chuàng)] 寫一個塊設(shè)備驅(qū)動 [復(fù)制鏈接]

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

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

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

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

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

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

這里插一句,我們不打算在這里介紹如何寫模塊,理由是介紹的文章已經(jīng)滿天飛舞了。
如果你能看得懂、并且成功地編譯、運(yùn)行了這段代碼,我們認(rèn)為你已經(jīng)達(dá)到了本教程的入學(xué)資格,
當(dā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)搞定上面的最簡單的模塊了,懂得什么是看模塊,以及簡單模塊的編寫、編譯、加載和卸載。
還有就是,什么是塊設(shè)備,什么是塊設(shè)備驅(qū)動,這個也請自行g(shù)oogle吧,因?yàn)槲覀円呀?jīng)迫不及待要寫完程序下課。

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

當(dāng)然,在添加設(shè)備之前我們需要申請這個設(shè)備的資源,這用到了alloc_disk()函數(shù),因此模塊入口函數(shù)simp_blkdev_init(void)應(yīng)該是:
   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);

還有就是,設(shè)備有關(guān)的屬性也是需要設(shè)置的,因此在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其實(shí)是這個塊設(shè)備的名稱,為了紳士一些,我們把它定義成宏了:
#define SIMP_BLKDEV_DISKNAME        "simp_blkdev"

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

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

為了理清思路,我們把目前為止涉及到的代碼整理出來:
#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);

剩下部分的不多了,真的不多了。請相信我,因?yàn)槲也辉谫|(zhì)監(jiān)局上班。
我寫的文章誠實(shí)可靠,并且不拿你納稅的錢。

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

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

然后我們著手實(shí)現(xiàn)simp_blkdev_do_request。
這里介紹elv_next_request()函數(shù),原型是:
struct request *elv_next_request(struct request_queue *q);
用來從一個請求隊(duì)列中拿出一條請求(其實(shí)嚴(yán)格來說,拿出的可能是請求中的一段)。
隨后的處理請求本質(zhì)上是根據(jù)rq_data_dir(req)返回的該請求的方向(讀/寫),把塊設(shè)備中的數(shù)據(jù)裝入req->buffer、或是把req->buffer中的數(shù)據(jù)寫入塊設(shè)備。
剛才已經(jīng)提及了與request結(jié)構(gòu)相關(guān)的rq_data_dir()宏和.buffer成員,其他幾個相關(guān)的結(jié)構(gòu)成員和函數(shù)是:
request.sector:請求的開始磁道
request.current_nr_sectors:請求磁道數(shù)
end_request():結(jié)束一個請求,第2個參數(shù)表示請求處理結(jié)果,成功時設(shè)定為1,失敗時設(shè)置為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表示的每一段,首先判斷這個請求是否超過了我們的塊設(shè)備的最大容量,
然后根據(jù)請求的方向rq_data_dir(req)進(jìn)行相應(yīng)的請求處理。由于我們使用的是指簡單的數(shù)組,因此請求處理僅僅是2條memcpy。
memcpy中也牽涉到了扇區(qū)號到線性地址的轉(zhuǎn)換操作,我想對堅(jiān)持到這里的讀者來說,這個操作應(yīng)該不需要進(jìn)一步解釋了。

編碼到此結(jié)束,然后我們試試這個程序:
首先編譯:
# 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,因?yàn)樗葲]有被其他模塊使用,也沒有被mount。
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  0
...
#
如果當(dāng)前系統(tǒng)支持udev,在調(diào)用add_disk()函數(shù)時即插即用機(jī)制會自動為我們在/dev/目錄下建立設(shè)備文件。
設(shè)備文件的名稱為我們在gendisk.disk_name中設(shè)置的simp_blkdev,主、從設(shè)備號也是我們在程序中設(shè)定的72和0。
如果當(dāng)前系統(tǒng)不支持udev,那么很不幸,你需要自己用mknod /dev/simp_blkdev  b 72 0來創(chuàng)建設(shè)備文件了。
# ls -l /dev/simp_blkdev
brw-r----- 1 root disk 72, 0 11-10 18:13 /dev/simp_blkdev
#
在塊設(shè)備中創(chuàng)建文件系統(tǒng),這里我們創(chuàng)建常用的ext3。
當(dāng)然,作為通用的塊設(shè)備,創(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這個設(shè)備中的文件系統(tǒng)。
當(dāng)然,這不是必需的。如果你對mount之類的用法很熟,你完全能夠自己決定在這里干什么,甚至把這個設(shè)備mount成root。
# mkdir -p /mnt/temp1
#
把建立好文件系統(tǒng)的塊設(shè)備mount到剛才建立的目錄中
# mount /dev/simp_blkdev /mnt/temp1
#
看看現(xiàn)在的mount表
# mount
...
/dev/simp_blkdev on /mnt/temp1 type ext3 (rw)
#
看看現(xiàn)在的模塊引用計(jì)數(shù),從剛才的0變成1了,
原因是我們mount了。
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  1
...
#
看看文件系統(tǒng)的內(nèi)容,有個mkfs時自動建立的lost+found目錄。
# ls /mnt/temp1
lost+found
#
隨便拷點(diǎn)東西進(jìn)去
# 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)在這個塊設(shè)備的使用情況是
# df
文件系統(tǒng)               1K-塊        已用     可用 已用% 掛載點(diǎn)
...
/dev/simp_blkdev         15863      1440     13604  10% /mnt/temp1
#
再全刪了玩玩
# rm -rf /mnt/temp1/*
#
看看刪完了沒有
# ls /mnt/temp1
#
好了,大概玩夠了,我們把文件系統(tǒng)umount掉
# umount /mnt/temp1
#
模塊的引用計(jì)數(shù)應(yīng)該還原成0了吧
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  0
...
#
最后一步,移除模塊
# rmmod simp_blkdev
#

這是這部教程的第1章,不好意思的是,內(nèi)容比預(yù)期還是難了一些。
當(dāng)初還有一種考慮是在本章中僅僅實(shí)現(xiàn)一個寫了就丟的塊設(shè)備驅(qū)動,也就是說,對這個塊設(shè)備的操作只能到mkfs這一部,而不能繼續(xù)mount,因?yàn)閯偛艑懙臄?shù)據(jù)全被扔了。
或者更簡單些,僅僅寫一個hello world的模塊。
但最后還是寫成了現(xiàn)在這樣沒,因?yàn)槲矣X得拿出一個真正可用的塊設(shè)備驅(qū)動程序?qū)ψx者來說更有成就感。

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

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

<未完,待續(xù)>

評分

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

查看全部評分

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

第2章

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

上一章不但實(shí)現(xiàn)了一個最簡單的塊設(shè)備驅(qū)動程序,而且可能也成功地嚇退了不少準(zhǔn)備繼續(xù)看下去的讀者。
因?yàn)榈谝徽驴雌饋砗孟裉y了。
不過讀者也不要過于埋怨作者,因?yàn)榇蠖鄶?shù)情況下第一次都不是什么好的體驗(yàn)......

對于堅(jiān)持到這里的讀者,這一章中,我們準(zhǔn)備了一些簡單的內(nèi)容來犒勞大家。

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

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

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

選擇一個指定的I/O調(diào)度器需要這個函數(shù):
int elevator_init(struct request_queue *q, char *name);
q是請求隊(duì)列的指針,name是需要設(shè)定的I/O調(diào)度器的名稱。
如果name為NULL,那么內(nèi)核會首先嘗試選擇啟動參數(shù)"elevator="中指定的調(diào)度器,
不成功的話就去選擇編譯內(nèi)核時指定的默認(rèn)調(diào)度器,
如果運(yùn)氣太背還是不成功,就去選擇"noop"調(diào)度器。
不要問我怎么知道的,一切皆在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)幫我們申請一個了,因此這里我們需要費(fèi)點(diǎn)周折,把老的那個送回去。
所以我們的代碼應(yī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
#
然后看一看咱們的這個塊設(shè)備現(xiàn)在使用的I/O調(diào)度器:
# cat /sys/block/simp_blkdev/queue/scheduler
[noop] anticipatory deadline cfq
#
看樣子是成功了。

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

OK,我們完成簡單的一章,并且用事實(shí)說明了作者并沒有在開頭撒謊。
當(dāng)然,作者也會力圖讓接下來的章節(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運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-10 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-16 06:20:0015-16賽季CBA聯(lián)賽之廣東
日期:2016-04-16 19:59:32IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-18 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-19 06:20:00每日論壇發(fā)貼之星
日期:2016-04-19 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-04-25 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-06 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-08 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-13 06:20:00IT運(yùn)維版塊每日發(fā)帖之星
日期:2016-05-28 06:20:00每日論壇發(fā)貼之星
日期:2016-05-28 06:20:00
4 [報告]
發(fā)表于 2008-11-14 18:11 |只看該作者
頂一下。
中文內(nèi)核郵件列表中有個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無私的奉獻(xiàn)
看樓主的行文風(fēng)格 冒昧的猜測 很像fudan_abc大俠啊  

論壇徽章:
0
6 [報告]
發(fā)表于 2008-11-15 10:26 |只看該作者
原帖由 Godbach 于 2008-11-14 18:11 發(fā)表
頂一下。
中文內(nèi)核郵件列表中有個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  只搞過字符設(shè)備和網(wǎng)絡(luò)設(shè)備驅(qū)動    這回有機(jī)會看看了
不過不知道是否直接可以看 還是要把ldd相關(guān)章節(jié)干掉才能閱讀

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

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

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

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

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

謝謝支持!

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

第3章

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

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

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

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

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

對通用塊層的訪問,比如請求讀某個塊設(shè)備上的一段數(shù)據(jù),通常是準(zhǔn)備一個bio,然后調(diào)用generic_make_request()函數(shù)來實(shí)現(xiàn)的。
調(diào)用者是幸運(yùn)的,因?yàn)樗恍枰リP(guān)心generic_make_request()函數(shù)如何做的,只需要知道這個神奇的函數(shù)會為他搞定所有的問題就OK了。
而我們卻沒有這么幸運(yùn),因?yàn)閷σ粋塊設(shè)備驅(qū)動的設(shè)計(jì)者來說,如果不知道generic_make_request()函數(shù)的內(nèi)部情況,很可能會讓驅(qū)動的使用者得不到安全感。

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

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

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

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

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

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

如果讀者是mm,或許會認(rèn)為一個摔鍋把咸魚嚼好了含情脈脈地喂過來是一件很浪漫的事情(也希望這位讀者與作者聯(lián)系),
但對于大多數(shù)男性IT工作者來說,除非取向問題,否則......
因此現(xiàn)在我們寧可把__make_request()函數(shù)一腳踢飛,然后自己去嚼bio這條咸魚。
當(dāng)然,踢飛__make_request()函數(shù)也意味著擺脫了I/O調(dià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)在要脫光,所以上一章中我們費(fèi)好大勁換上的那件薄內(nèi)衣也不需要了,
也就是把上一章中增加的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ù)實(shí)現(xiàn)的,把這面這行加在blk_alloc_queue()之后:
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
然后實(shí)現(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,希望讀者不要打算就這一點(diǎn)進(jìn)行提問。

正如本章開頭所述,這一章的內(nèi)容可能要復(fù)雜一些,而現(xiàn)在看來似乎已經(jīng)做到了。
而現(xiàn)在的進(jìn)度大概是......一半!
不過值得安慰的是,余下的內(nèi)容只有我們的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ù),與請求有關(guān)的信息在bio參數(shù)中,
而struct request_queue *q并沒有經(jīng)過__make_request()的處理,這也意味著我們不能用前幾章那種方式使用q。
因此這里我們關(guān)注的是:bio。

關(guān)于bio和bio_vec的格式我們?nèi)匀徊淮蛩阍谶@里做過多的解釋,理由同樣是因?yàn)槲覀円苊馀cgoogle出的一大堆文章撞衫。
這里我們只說一句話:
bio對應(yīng)塊設(shè)備上一段連續(xù)空間的請求,bio中包含的多個bio_vec用來指出這個請求對應(yīng)的每段內(nèi)存。
因此simp_blkdev_make_request()本質(zhì)上是在一個循環(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請求的塊設(shè)備起始扇區(qū)和扇區(qū)數(shù)存儲在bio.bi_sector和bio.bi_size中,
我們首先通過bio.bi_sector獲得這個bio請求在我們的塊設(shè)備內(nèi)存中的起始部分位置,存入dsk_mem。
然后遍歷bio中的每個bio_vec,這里我們使用了系統(tǒng)提供的bio_for_each_segment宏。

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

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

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

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

讀者或許開始耐不住性子想這一章怎么還不結(jié)束了,是的,馬上就結(jié)束,不過我們還要在循環(huán)的前后加上一丁點(diǎn):
1:循環(huán)之前的變量聲明:
   struct bio_vec *bvec;
   int i;
   void *dsk_mem;
2:循環(huán)之前檢測訪問請求是否超越了塊設(shè)備限制:
   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)之后結(jié)束這個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請求的處理結(jié)果,在2.6.24之后的內(nèi)核中,第一個參數(shù)是被處理的bio指針,第二個參數(shù)成功時為0,失敗時為-ERRNO。
   在2.6.24之前的內(nèi)核中,中間還多了個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>,
就得到了本章的最終代碼。

在結(jié)束本章之前,我們還是試驗(yàn)一下:
首先還是編譯和加載:
# 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中的這個設(shè)備的信息:
# ls /sys/block/simp_blkdev
dev  holders  range  removable  size  slaves  stat  subsystem  uevent
#
我們發(fā)現(xiàn)我們的驅(qū)動程序在sysfs目錄中的queue子目錄不見了。
這并不奇怪,否則就要抓狂了。

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

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

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

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

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

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP