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

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
樓主: OstrichFly
打印 上一主題 下一主題

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

論壇徽章:
0
11 [報告]
發(fā)表于 2008-11-17 19:27 |只看該作者
寫的很好啊。。。可是在網吧里面沒有這么多的時間看啊。。。。。

論壇徽章:
0
12 [報告]
發(fā)表于 2008-11-18 14:54 |只看該作者
原帖由 platinum 于 2008-11-15 10:26 發(fā)表

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


mail list真是藏龍臥虎啊。

論壇徽章:
0
13 [報告]
發(fā)表于 2008-11-18 21:19 |只看該作者
原帖由 platinum 于 2008-11-15 10:26 發(fā)表

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

我是Zhaolei,但不是ShadowStar。
因為我不懂ipp2p。

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

第4章

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

上一章結束時說過,本章會準備一些不需要動腦子的內容,現在我們開始履行諾言。

看上去簡單的事情實際上往往會被弄得很復雜,比如取消公仆們的招待費用問題;
看上去復雜的事情真正做起來也可能很簡單,比如本章中要讓我們的塊設備支持分區(qū)操作。

談到分區(qū),不懂電腦的人想到了去找“專家”幫忙;電腦入門者想到了“高手”這個名詞;
漸入佳境者想到了fdisk;資深級玩家想到了dm;紅點玩家想到了隱藏的系統(tǒng)恢復區(qū);
程序員想到了分區(qū)表;病毒制造者想到了把分區(qū)表清空......

作為塊設備驅動程序的設計者,我們似乎需要想的比他們更多一些,
我們大概需要在驅動程序開始識別塊設備時訪問設備上的分區(qū)表,讀出里面的數據進行分析,
找出這個塊設備中包含哪一類的分區(qū)(奇怪吧,但真相是分區(qū)表確實有很多種,只是我們經常遇到的大概只有ibm類型罷了)、
幾個分區(qū),每個分區(qū)在塊設備上的區(qū)域等信息,再在驅動程序中對每個分區(qū)進行注冊、創(chuàng)建其管理信息......
讀到這里,正在系鞋帶準備溜之大吉的同學們請稍等片刻聽我說完,
雖然實際上作者也鼓勵同學們多作嘗試,甚至是這種無謂的嘗試,但本章中的做法卻比上述的內容簡單得多。
因為這一回linux居然幫了我們的忙,并且不是I/O調度器的那種倒忙。

打開linux代碼,我們會在fs/partitions/目錄中發(fā)現一些文件,這些友好的文件將會默默無聞地幫我們的大忙。

而我們需要做的居然如此簡單,還記得alloc_disk()函數嗎?
我們一直用1作參數來調用它的,但現在,我們換成64,這意味著設定塊設備最大支持63個分區(qū)。
然后......不要問然后,因為已經做完了。
當然,如果要讓代碼看起來漂亮一些的話,我們可以考慮用一個宏來定義最大分區(qū)數。
也就是,在文件的頭部增加:
/* usable partitions is SIMP_BLKDEV_MAXPARTITIONS - 1 */
#define SIMP_BLKDEV_MAXPARTITIONS      (64)

然后把
simp_blkdev_disk = alloc_disk(1);
改成
simp_blkdev_disk = alloc_disk(SIMP_BLKDEV_MAXPARTITIONS);

好了,真的改好了。
上一章那樣改得太多看起來會讓讀者不爽,那么這里改得太少,是不是也同樣不爽?
大概有關部門深信老百姓接受不了有害物質含量過少的食品,因此制定了食品中三聚氰胺含量的標準。
于是,今后我們大概會制定出一系列標準,比如插入多深才能叫強奸什么的。

為了達到所謂的標準,我們破例補充介紹一下alloc_disk()函數:
這個函數的原型為:
struct gendisk *alloc_disk(int minors);
用于申請一個gendisk結構,并做好一些初始化工作。
minors用于指定這個設備使用的次設備號數量,因為第一個次設備號已經用于表示整個塊設備了,
因此余下的minors-1個設備號用于表示塊設備中的分區(qū),這就限制了這個塊設備中的最大可訪問分區(qū)數。
我們注意“最大可訪問分區(qū)數”這個詞:
“最大”雖然指的是上限,但并不意味這是唯一的上限。
極端情況下如果這個塊設備只有2個磁道,那么無論minors多大,塊設備本身充其量也只能建立2個分區(qū)。
這時再談minors值能到達多少簡直就是扯淡,就像腐敗不根除,建多少經濟適用房都是白搭一樣。
“可訪問”指的是通過驅動程序可以訪問的分區(qū)數量,這是因為我們只有那么多次設備號。
但這個數字并不妨礙用戶在塊設備上面建多少個區(qū)。比如我們把minors設定為4,那么最大可訪問的分區(qū)數量是3,
足夠變態(tài)的用戶完全可以在塊設備上建立幾十個分區(qū),只不過結果是只能使用前3個分區(qū)而已。

現在我們可以試試這個程序了。
與以往相同的是,我們編譯和加載這個模塊:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step04 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step04/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step04/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step04/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
# insmod simp_blkdev.ko
#
與以往不同的是,這一次加載完模塊后,我們并不直接在塊設備上創(chuàng)建文件系統(tǒng),而是進行分區(qū):
# fdisk /dev/simp_blkdev
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.

Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help):

關于fdisk我們不打算在這里介紹,因為我們試圖讓這篇文檔看起來專家一些。
使用n命令創(chuàng)建第一個主分區(qū):
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-2, default 1): 1
Last cylinder or +size or +sizeM or +sizeK (1-2, default 2): 1

Command (m for help):
如果細心一些的話,在這里可以看出一個小麻煩,就是:這塊磁盤一共只有2個磁道。
因此,我們只好指定第一個分區(qū)僅占用1個磁道。畢竟,還要為第2個分區(qū)留一些空間。
然后建立第二個分區(qū):
Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 2
First cylinder (2-2, default 2): 2

Command (m for help):
這一步中由于只剩下1個磁道,fdisk便不再問我們Last cylinder,而是自作主張地把最后一個磁道分配給新的分區(qū)。
這時我們的分區(qū)情況是:
Command (m for help): p

Disk /dev/simp_blkdev: 16 MB, 16777216 bytes
255 heads, 63 sectors/track, 2 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

           Device Boot      Start         End      Blocks   Id  System
/dev/simp_blkdev1               1           1        8001   83  Linux
/dev/simp_blkdev2               2           2        8032+  83  Linux

Command (m for help):
寫入分區(qū),退出fdisk:
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.
#

然后我們在這兩個分區(qū)中創(chuàng)建文件系統(tǒng)
# mkfs.ext3 /dev/simp_blkdev1
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
2000 inodes, 8000 blocks
400 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=8388608
1 block group
8192 blocks per group, 8192 fragments per group
2000 inodes per group

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

This filesystem will be automatically checked every 27 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
# mkfs.ext3 /dev/simp_blkdev2
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
2008 inodes, 8032 blocks
401 blocks (4.99%) reserved for the super user
First data block=1
Maximum filesystem blocks=8388608
1 block group
8192 blocks per group, 8192 fragments per group
2008 inodes per group

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

This filesystem will be automatically checked every 23 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
#
然后mount設兩個設備:
# mount /dev/simp_blkdev1 /mnt/temp1
# mount /dev/simp_blkdev2 /mnt/temp2
#
看看結果:
# mount
/dev/hda1 on / type ext3 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)
/dev/simp_blkdev1 on /mnt/temp1 type ext3 (rw)
/dev/simp_blkdev2 on /mnt/temp2 type ext3 (rw)
#
然后讀/寫:
# cp /etc/init.d/* /mnt/temp1/
# cp /etc/passwd /mnt/temp2
# ls /mnt/temp1/
NetworkManager            avahi-dnsconfd      dund       ipmi        lost+found     netfs     portmap          rpcsvcgssd      vncserver
NetworkManagerDispatcher  bluetooth           firstboot  iptables    lvm2-monitor   netplugd  psacct           saslauthd       winbind
acpid                     capi                functions  irda        mcstrans       network   rdisc            sendmail        wpa_supplicant
anacron                   conman              gpm        irqbalance  mdmonitor      nfs       readahead_early  setroubleshoot  xfs
apmd                      cpuspeed            haldaemon  isdn        mdmpd          nfslock   readahead_later  single          ypbind
atd                       crond               halt       kdump       messagebus     nscd      restorecond      smartd          yum-updatesd
auditd                    cups                hidd       killall     microcode_ctl  ntpd      rhnsd            sshd
autofs                    cups-config-daemon  hplip      krb524      multipathd     pand      rpcgssd          syslog
avahi-daemon              dhcdbd              ip6tables  kudzu       netconsole     pcscd     rpcidmapd        vmware-tools
# ls /mnt/temp2
lost+found  passwd
#
收尾工作:
# umount /dev/temp1
# umount /dev/temp2
# rmmod simp_blkdev
#

看起來本章應該結束了,但為了耽誤大家更多的時間,我們來回憶一下剛才出現的小麻煩。
我們發(fā)現這塊磁盤只有2個磁道,由于分區(qū)是以磁道為邊界的,因此最大只能創(chuàng)建2個分區(qū)。
不過謝天謝地,好歹我們能夠證明我們的程序是支持“多個”分區(qū)的......盡管只有2個。

那么為什么系統(tǒng)會認為我們的塊設備只有2個磁道呢?其實這不怪系統(tǒng),因為我們根本沒有告訴系統(tǒng)我們的磁盤究竟有多少個磁道。
因此系統(tǒng)只好去猜、猜、猜,結果就猜成2個磁道了。
好吧,說的細節(jié)一些,傳統(tǒng)的磁盤使用8個位表示盤面數、6個位表示每磁道扇區(qū)數、10個位表示磁道數,因此盤面、每磁道扇區(qū)、磁道的最大數值分別為255、63和1023。
這也是傳說中啟動操作系統(tǒng)時的1024柱面(磁道)和硬盤容量8G限制的根源。
現代磁盤采用線性尋址方式突破了這一限制,從本質上說,如果你的機器還沒生銹,那么你的硬盤無論是內部結構還是訪問方式都與常識中的盤面、每磁道扇區(qū)、磁道無關。
但為了與原先的理解兼容,對于現代磁盤,我們在訪問時還是假設它具有傳統(tǒng)的結構。目前比較通用的假設是:所有磁盤具有最大數目的(也就是恒定的)盤面和每磁道扇區(qū)數,而磁盤大小與磁道數與成正比。
因此,對于一塊80G的硬盤,根據假設,這塊磁盤的盤面和每磁道扇區(qū)數肯定是255和63,磁道數為:80*1024*1024*1024/512(字節(jié)每扇區(qū))/255(盤面數)/63(每磁道扇區(qū)數)=10043(小數部分看作不完整的磁道被丟棄)。
話歸原題,在驅動程序中我們指定了磁盤大小為16M,共包含16*1024*1024/512=32768個扇區(qū)。假設這塊磁盤具有最大盤面和每磁道扇區(qū)數后,它的磁道數就是:32768/255/63=2。

我們看起開應該很happy,因為系統(tǒng)太看得起我們了,竟然把我們的塊設備看成現代磁盤進行磁道數的換算處理。
不過我們也可能unhappy,因為這造成磁盤最大只能被分成2個區(qū)。(至于為什么分區(qū)以磁道作為邊界,可以想象一下磁盤的結構)
但我們的磁盤只有區(qū)區(qū)16M啊,所以最好還是告訴系統(tǒng)我們的磁盤沒有那么多的盤面數和每磁道扇區(qū)數,這將讓磁道數來得多一些。

在下一章中,我們打算搞定這個問題。

<未完,待續(xù)>

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

第5章

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

既然上一章結束時我們已經預告了本章的內容,
那么本章中我們就讓這個塊設備有能力告知操作系統(tǒng)它的“物理結構”。

當然,對于基于內存的塊設備來說,什么樣的物理結構并不重要,
這就如同從酒吧帶mm回家時不需要打聽她的姓名一樣。
但如果不幸遇到的是兼職,并且?guī)ゲ蝗肓鞯恼写鶗r,
建議最好還是先串供一下姓名、生日和職業(yè)等信息,
以便JJ查房時可以偽裝成情侶。
同樣,如果要實現的是真實的物理塊設備驅動,
那么返回設備的物理結構時大概不能這么隨意。

對于塊設備驅動程序而言,我們現在需要關注那條目前只有一行的struct block_device_operations simp_blkdev_fops結構。
到目前為止,它存在的目的僅僅是因為它必須存在,但馬上我們將發(fā)現它存在的另一個目的:為塊設備驅動添加獲得塊設備物理結構的接口。

對于具有極強鉆研精神的極品讀者來說,大概在第一章中就會自己去看struct block_device_operations結構,然后將發(fā)現這個結構其實還挺復雜:
struct block_device_operations {
        int (*open) (struct block_device *, fmode_t);
        int (*release) (struct gendisk *, fmode_t);
        int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        int (*direct_access) (struct block_device *, sector_t,
                                                void **, unsigned long *);
        int (*media_changed) (struct gendisk *);
        int (*revalidate_disk) (struct gendisk *);
        int (*getgeo)(struct block_device *, struct hd_geometry *);
        struct module *owner;
};
在前幾章中,我們邂逅過其中的owner成員變量,它用于存儲這個結構的所有者,也就是我們的模塊,因此我們做了如下的賦值:
.owner                = THIS_MODULE,
而這一章中,我們將與它的同胞妹妹------getgeo也親密接觸一下。

我們要做的是:
1:在block_device_operations中增加getgeo成員變量初值的設定,指向我們的“獲得塊設備物理結構”函數。
2:實現我們的“獲得塊設備物理結構”函數。

第一步很簡單,我們暫且為“獲得塊設備物理結構”函數取個名字叫simp_blkdev_getgeo()吧,也避免了在下文中把這么一大堆漢字拷來拷去。
在simp_blkdev_fops中添加.getgeo指向simp_blkdev_getgeo,也就是把simp_blkdev_fops結構改成這個樣子:
struct block_device_operations simp_blkdev_fops = {
        .owner                = THIS_MODULE,
        .getgeo                = simp_blkdev_getgeo,
};

第二步難一些,但也難不到哪去,在代碼中的struct block_device_operations simp_blkdev_fops這行之前找個空點的場子,把如下函數插進去:
static int simp_blkdev_getgeo(struct block_device *bdev,
                struct hd_geometry *geo)
{
        /*
         * capacity        heads        sectors        cylinders
         * 0~16M        1        1        0~32768
         * 16M~512M        1        32        1024~32768
         * 512M~16G        32        32        1024~32768
         * 16G~...        255        63        2088~...
         */
        if (SIMP_BLKDEV_BYTES < 16 * 1024 * 1024) {
                geo->heads = 1;
                geo->sectors = 1;

        } else if (SIMP_BLKDEV_BYTES < 512 * 1024 * 1024) {
                geo->heads = 1;
                geo->sectors = 32;
        } else if (SIMP_BLKDEV_BYTES < 16ULL * 1024 * 1024 * 1024) {
                geo->heads = 32;
                geo->sectors = 32;
        } else {
                geo->heads = 255;
                geo->sectors = 63;
        }

        geo->cylinders = SIMP_BLKDEV_BYTES>>9/geo->heads/geo->sectors;

        return 0;
}
因為這里我們用到了struct hd_geometry結構,所以還要增加一行#include <linux/hdreg.h>。

這個函數的目的,是選擇適當的物理結構信息裝入struct hd_geometry *geo結構。
當然,為了克服上一章中只能分成2個區(qū)的問題,我們應該盡可能增加磁道的數量。
希望讀者不要理解成分幾個區(qū)就需要幾個磁道,這意味著一個磁道一個區(qū),也意味著每個區(qū)必須一般大小。
由于分區(qū)總是以磁道為邊界,盡可能增加磁道的數量不僅僅是為了讓塊設備容納更多的分區(qū),
更重要的是讓分區(qū)的實際大小更接近于分區(qū)時的指定值,也就是提高實際做出的分區(qū)容量的精度。

不過對于設置的物理結構值,還存在一個限制,就是struct hd_geometry中的數值上限。
我們看struct hd_geometry的內容:
struct hd_geometry {
        unsigned char heads;
        unsigned char sectors;
        unsigned short cylinders;
        unsigned long start;
};
unsigned char的磁頭數和每磁道扇區(qū)數決定了其255的上限,同樣,unsigned short的磁道數決定了其65535的上限。
這還不算,但在前一章中,我們知道對于現代硬盤,磁頭數和每磁道扇區(qū)數通常取的值是255和63,
再組合上這里的65535的磁道數上限,hd_geometry能夠表示的最大塊設備容量是255*63*65535*512/1024/1024/1024=502G。
顯然目前l(fā)inux支持的最大硬盤容量大于502G,那么對于這類塊設備,內核是如何通過hd_geometry結構表示其物理結構的呢?
訣竅不在內核,而在于用戶態(tài)程序如fdisk等通過內核調用獲得hd_geometry結構后,
會舍棄hd_geometry.cylinders內容,取而代之的是直接通過hd_geometry中的磁頭數和每磁道扇區(qū)數以及硬盤大小去計算磁道數。
因此對于超過502G的硬盤,由于用戶程序得出的磁道數與hd_geometry.cylinders無關,所以我們往往在fdisk中能看到這塊硬盤的磁道數大于65535。

剛才扯遠了,現在言歸正題,我們決定讓這個函數對于任何尺寸的塊設備,總是試圖返回比較漂亮的物理結構。
漂亮意味著返回的物理結構既要保證擁有足夠多的磁道,也要保證磁頭數和每磁道扇區(qū)數不超過255和63,同時最好使用程序員看起來比較順眼的數字,
如:1、2、4、8、16、32、64等。
當然,我們也希望找到某個One Shot公式適用于所有大小的塊設備,但很遺憾目前作者沒找到,因此采用了分段計算的方法:
首先考慮容量很小的塊設備:
  即使磁頭數和每磁道扇區(qū)數都是1,磁道數也不夠多時,我們會將磁頭數和每磁道扇區(qū)數都固定為1,以使磁道數盡可能多,以提高分區(qū)的精度。
  因此磁道數隨塊設備容量而上升。
  雖然我們已經知道了磁道數其實可以超過unsigned short的65535上限,但在這里卻沒有必要,因此我們要給磁道數設置一個上限。
  因為不想讓上限超過65535,同時還希望上限也是一個程序員喜歡的數字,因此這里選擇了32768。
  當然,當磁道數超過32768時,已經意味著塊設備容量不那么小了,也就沒有必要使用這種情況中如此苛刻的磁頭數和每磁道扇區(qū)數了。
  簡單來說,當塊設備容量小于1個磁頭、每磁道1扇區(qū)和32768個磁道對應的容量--也就是16M時,我們將按照這種情況處理。
然后假設塊設備容量已經大于16M了:
  我們希望保證塊設備包含足夠多的磁道,這里我們認為1024個磁道應該不少了。
  磁道的最小值發(fā)生在塊設備容量為16M的時候,這時使用1024作為磁道數,可以計算出磁頭數*每磁道扇區(qū)數=32。
  這里暫且把磁頭數和每磁道扇區(qū)數固定為1和32,而讓磁道數隨著塊設備容量的增大而增加。
  同時,我們還是磁道的上限設置成32768,這時的塊設備容量為512M。
  總結來說,當塊設備容量在16M和512M之間時,我們把磁頭數和每磁道扇區(qū)數固定為1和32。
然后對于容量大于512M的塊設備:
  與上述處理相似,當塊設備容量在512M和16G之間時,我們把磁頭數和每磁道扇區(qū)數固定為32和32。
最后的一種情況:
  塊設備已經足夠大了,大到即使我們使用磁頭數和每磁道扇區(qū)數的上限,
  也能獲得足夠多的磁道數。這時把磁頭數和每磁道扇區(qū)數固定為255和63。
  至于磁道數就算出多少是多少了,即使超過unsigned short的上限也無所謂,反正用不著。

隨著這個函數解說到此結束,我們對代碼的修改也結束了。

現在開始試驗:
編譯和加載:
# make
make -C /lib/modules/2.6.27.4/build SUBDIRS=/mnt/host_test/simp_blkdev/simp_blkdev_step05 modules
make[1]: Entering directory `/mnt/ltt-kernel'
  CC [M]  /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.mod.o
  LD [M]  /mnt/host_test/simp_blkdev/simp_blkdev_step05/simp_blkdev.ko
make[1]: Leaving directory `/mnt/ltt-kernel'
# insmod simp_blkdev.ko
#
用fdisk打開設備文件
# fdisk /dev/simp_blkdev
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that, of course, the previous
content won't be recoverable.

Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help):
看看設備的物理結構:
Command (m for help): p

Disk /dev/simp_blkdev: 16 MB, 16777216 bytes
1 heads, 32 sectors/track, 1024 cylinders
Units = cylinders of 32 * 512 = 16384 bytes

           Device Boot      Start         End      Blocks   Id  System

Command (m for help):
我們發(fā)現,現在的設備有1個磁頭、32扇區(qū)每磁道、1024個磁道。
這是符合代碼中的處理的。

本章的內容也不是太難,連同上一章,我們已經休息2章了。
聰明的讀者可能已經猜到作者打算說什么了。
不錯,下一章會有一個surprise。

<未完,待續(xù)>

論壇徽章:
0
16 [報告]
發(fā)表于 2008-11-18 21:25 |只看該作者
第6章可能要等一段時間了,因為準備講述動態(tài)申請內存存儲設備數據,并用基樹來管理,更關鍵的理由是:
我打算先把英雄無敵的一個戰(zhàn)役打完,還有老婆在催我畫墻上的那棵樹,都畫了幾個月了,還只有樹根......

論壇徽章:
0
17 [報告]
發(fā)表于 2008-11-21 14:08 |只看該作者
急切等待中~~~~~~~~~~~~~~~~~~~~~~~

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

第6章

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

經歷了內容極為簡單的前兩章的休息,現在大家一定感到精神百倍了。
作為已經堅持到現在的讀者,對接下去將要面臨的內容大概應該能夠猜得八九不離十了,
具體的內容猜不出來也無妨,但一定將是具有增加顱壓功效的。

與物理塊設備驅動程序的區(qū)別在于,我們的驅動程序使用內存來存儲塊設備中的數據。
到目前為止,我們一直都是使用這樣一個靜態(tài)數組來擔負這一功能的:
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];

如果讀者懂得一些模塊的知識,或者現在趕緊去臨時抱佛腳google一些模塊知識,
應該知道模塊其實是加載在非線性映射區(qū)域的。
詳細來說,在加載模塊時,根據模塊的ELF信息(天哪,又要去google elf了),確定這個模塊所需的靜態(tài)內存大小。
這些內存用來容納模塊的二進制代碼,以及靜態(tài)變量。然后申請容納這些數據的一大堆頁面。
當然,這些頁面并不是連續(xù)的,而代碼和變量卻不可能神奇到即使被切成一塊一塊的也能正常工作。
因此需要在非線性映射區(qū)域中找到一塊連續(xù)的地址(現在有要去google非線性映射區(qū)域了),用來將剛才申請到的一塊一塊的內存頁映射到這個地址段中。
最后模塊被請到這段區(qū)域中,然后執(zhí)行模塊的初始化函數......

現在看我們這個模塊中的simp_blkdev_data變量,如果不是現在刻意關注,這個變量看起來顯得那么得普通。
正如其它的一些名字原先也是那么的普通,但由于一些突發(fā)的事件受到大家的熱烈關注,
比如一段視頻讓我們熟悉了kappa和陸佳妮,比如呼吸稅讓我們認識了蔣有緒。

現在我們開始關注simp_blkdev_data變量了,導火索是剛才介紹的非線性映射區(qū)域。
模塊之所以被加載到非線性映射區(qū)域,是因為很難在線性映射區(qū)域中得到加載模塊所需的連續(xù)的內存。
但使用非線性映射區(qū)域也并非只賺不賠的生意,至少在i386結構中,非線性映射區(qū)域實在是太小了。
在物理內存大于896M的i386系統(tǒng)中,整個非線性映射區(qū)域不會超過128M。
相反如果物理內存小于896M(不知道該算是幸運還是不幸),非線性映射區(qū)域反而會稍微大一些,這種情況我想我們可以不用討論了,畢竟不能為了加載一個模塊去拔內存。
因此我們的結論是:非線性映射區(qū)域是很緊張的資源,我們要節(jié)約使用。
而像我們現在這個模塊中的simp_blkdev_data卻是個如假包換的反面典型,居然上來就吃掉了16M!這還是因為我們沒有把SIMP_BLKDEV_BYTES定義得更大。

現在我們開始列舉simp_blkdev_data的種種罪行:
1:剩余的非線性映射區(qū)域較小時導致模塊加載失敗
2:模塊加載后占用了大量的非線性映射區(qū)域,導致其它模塊加載失敗。
3:模塊加載后占用了大量的非線性映射區(qū)域,影響系統(tǒng)的正常運行。
   這是因為不光模塊,系統(tǒng)本身的很多功能也依賴非線性映射區(qū)域空間。
對于這樣的害群之馬,我們難道還有留下他的理由嗎?
本章的內容雖然麻煩一些,但想到能夠一了百了地清除這個體大膘肥的simp_blkdev_data,倒也相當值得。
也希望今后能夠看到在對貪官的處理上,能夠也拿出這樣的魄力和勇氣。

現在在清除simp_blkdev_data的問題上,已經不存在什么懸念了,接下來我們需要關注的是將simp_blkdev_data碎尸萬段后,拿出一個更恰當方法來代替它。
首先,我們決定不用靜態(tài)聲明的數組,而改用動態(tài)申請的內存。
其次,使用類似vmalloc()的函數可以動態(tài)申請大段內存,但其實這段內存占用的還是非線性映射區(qū)域,就好像用一個比較隱蔽的貪官來代替下馬的貪官,我們不會愚蠢在這種地步。
剩下的,就是在線性映射區(qū)域申請很多個頁的內存,然后自己去管理。這個方法一了百了地解決了使用大段非線性映射區(qū)域的問題,而唯一的問題是由于需要自己管理申請到的頁面,使程序復雜了不少。
但為了整個系統(tǒng)的利益,這難道不是我們該做的嗎?

申請一個內存頁是很容易的,這里我們將采用所有容易的方法中最容易的那個:
__get_free_page函數,原型是:
unsigned long __get_free_page(gfp_t gfp_mask);
這個函數用來申請一個頁面的內存。gfp_mask包含一些對申請內存時的指定,比如,要在DMA區(qū)域中啦、必須清零等。
我們這里倒是使用最常見的__get_free_page(GFP_KERNEL)就可以了。

通過__get_free_page申請到了一大堆內存頁,新的問題來了,在讀寫塊設備時,我們得到是塊設備的偏移,如何快速地通過偏移找到對應的內存頁呢?

最簡單的方法是建立一個數組,用來存放偏移到內存的映射,數組中的每項對應一個一個頁:
數組定義如下:
void *simp_blkdev_data[(SIMP_BLKDEV_BYTES + PAGE_SIZE - 1) / PAGE_SIZE];
PAGE_SIZE是系統(tǒng)中每個頁的大小,對i386來說,通常是4K,那堆加PAGE_SIZE減1的代碼是考慮到SIMP_BLKDEV_BYTES不是PAGE_SIZE的整數倍時要讓末尾的空間也能訪問。
然后申請內存的代碼大概是:
for (i=0; i < (SIMP_BLKDEV_BYTES + PAGE_SIZE - 1) / PAGE_SIZE; i++) {
        p = (void *)__get_free_page(GFP_KERNEL);
        simp_blkdev_data[i] = p;
}
通過塊設備偏移得到內存中的數據地址的代碼大概是:
mem_addr = simp_blkdev_data[dev_addr/PAGE_SIZE] + dev_addr % PAGE_SIZE;
這種方法實現起來還是比較簡單的,但缺點也不是沒有:存放各個頁面地址的數組雖然其體積比原先那個直接存放數據的數組已經縮小了很多,
但本身畢竟還是在非線性映射區(qū)域中。如果塊設備大小為16M,在i386上,需要4096個頁面,數組大小16K,這不算太大。
但如果某個瘋子打算建立一個2G的虛擬磁盤,數組大小將達到2M,這就不算小了。

或者我們可以不用數組,而用鏈表來存儲偏移到內存頁的映射關系,這樣可以回避掉數組存在的問題,但在鏈表中查找指定元素卻不是一般的費時,
畢竟我們不希望用戶覺得這是個軟盤。

接下來作者不打斷繼續(xù)賣關子了,我們最終選擇使用的是傳說中的基樹。
關于linux中基樹細節(jié)的文檔不多,特別是中文文檔更少,更糟的是我們這篇文檔也不打算作詳細的介紹(因為作者建議去RTFSC)。
但總的來說,相對于二叉平衡樹的紅黑樹來說,基樹是一個n叉(一般為64叉)非平衡樹,n叉減少了搜索的深度,非平衡減少了復雜的平衡操作。
當然,這兩個特點也不是僅僅帶來優(yōu)點,但在這里我們就視而不見了,畢竟我們已經選擇了基樹,因此護短也是自認而然的事情,正如公仆護著王細牛一樣。
從功能上來說,基樹好像是為我們量身定做的一樣,好用至極。
(其實我們也可以考慮選擇紅黑樹和哈希表來實現這個功能,感興趣的讀者可以了解一下)

接下來的代碼中,我們將要用到基樹種的如下函數:
  void INIT_RADIX_TREE((struct radix_tree_root *root, gfp_t mask);
  用來初始化一個基樹的結構,root是基樹結構指針,mask是基樹內部申請內存時使用的標志。

  int radix_tree_insert(struct radix_tree_root *root, unsigned long index, void *item);
  用來往基樹中插入一個指針,index是指針的索引,item是指針,將來可以通過index從基樹中快速獲得這個指針的值。

  void *radix_tree_delete(struct radix_tree_root *root, unsigned long index);
  用來根據索引從基樹中刪除一個指針,index是指針的索引。

  void *radix_tree_lookup(struct radix_tree_root *root, unsigned long index);
  用來根據索引從基樹中查找對應的指針,index是指針的索引。
其實基樹的功能不僅限于此,比如,還可以給指針設定標志,詳情還是請去讀linux/lib/radix-tree.c

現在開始改造我們的代碼:
首先刪除那個無恥的數組:
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
然后引入它的替代者--一個基樹結構:
static struct radix_tree_root simp_blkdev_data;

然后增加兩個函數,用來申請和釋放塊設備的內存:

申請內存的函數如下:
int alloc_diskmem(void)
{
        int ret;
        int i;
        void *p;

        INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);

        for (i = 0; i < (SIMP_BLKDEV_BYTES + PAGE_SIZE - 1) >> PAGE_SHIFT;
                i++) {
                p = (void *)__get_free_page(GFP_KERNEL);
                if (!p) {
                        ret = -ENOMEM;
                        goto err_alloc;
                }

                ret = radix_tree_insert(&simp_blkdev_data, i, p);
                if (IS_ERR_VALUE(ret))
                        goto err_radix_tree_insert;
        }
        return 0;

err_radix_tree_insert:
        free_page((unsigned long)p);
err_alloc:
        free_diskmem();
        return ret;
}
先初始化基樹結構,然后申請需要的每一個頁面,按照每頁面的次序作為索引,將指針插入基樹。
代碼中的“>> PAGE_SHIFT”與“/ PAGE_SIZE”作用相同,
if (不明白為什么要這樣)
        do_google();

釋放內存的函數如下:
void free_diskmem(void)
{
        int i;
        void *p;

        for (i = 0; i < (SIMP_BLKDEV_BYTES + PAGE_SIZE - 1) >> PAGE_SHIFT;
                i++) {
                p = radix_tree_lookup(&simp_blkdev_data, i);
                radix_tree_delete(&simp_blkdev_data, i);
                /* free NULL is safe */
                free_page((unsigned long)p);
        }
}
遍歷每一個索引,得到頁面的指針,釋放頁面,然后從基樹中釋放這個指針。
由于alloc_diskmem()函數在中途失敗時需要釋放申請過的頁面,因此我們把free_diskmem()函數設計成能夠釋放建立了一半的基樹的形式。
對于只建立了一半的基樹而言,有一部分索引對應的指針還沒來得及插入基樹,對于不存在的索引,radix_tree_delete()函數會返回NULL,幸運的是free_page()函數能夠忽略傳入的NULL指針。

因為alloc_diskmem()函數需要調用free_diskmem()函數,在代碼中需要把free_diskmem()函數寫在alloc_diskmem()前面,或者在文件頭添加函數的聲明。

然后在模塊的初始化和釋放函數中添加對alloc_diskmem()和free_diskmem()的調用,
也就是改成這個樣子:
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(SIMP_BLKDEV_MAXPARTITIONS);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

        ret = alloc_diskmem();
        if (IS_ERR_VALUE(ret))
                goto err_alloc_diskmem;

        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_diskmem:
        put_disk(simp_blkdev_disk);
err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
        return ret;
}

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

最麻煩的放在最后:
我們需要修改simp_blkdev_make_request()函數,讓它適應新的數據結構。

原先的實現中,對于一個bio_vec,我們找到對應的內存中數據的起點,直接傳送bvec->bv_len個字節(jié)就大功告成了,比如,讀塊設備時就是:
memcpy(iovec_mem, dsk_mem, bvec->bv_len);
但現在由于容納數據的每個頁面地址是不連續(xù)的,因此可能出現bio_vec中的數據跨越頁面邊界的情況。
也就是說,一個bio_vec中的數據的前半段在一個頁面中,后半段在另一個頁面中。
雖然這兩個頁面對應的塊設備地址連續(xù),但在內存中的地址不一定連續(xù),因此像原先那樣簡單使用memcpy看樣子是解決不了問題了。

實際上,雖然bio_vec可能跨越頁面邊界,但它無論如何也不可能跨越2個以上的頁面。
這是因為bio_vec本身對應的數據最大長度只有一個頁面。
因此如果希望做最簡單的實現,只要在代碼中做一個條件判斷就OK了:
if (沒有跨越頁面) {
        1個memcpy搞定
} else {
        /* 肯定是跨越2個頁面了 */
        2個memcpy搞定
}

但為了表現出物理設備一次傳送1個扇區(qū)數據的處理方式(這種情況下一個bio_vec可能會跨越2個以上的扇區(qū)),我們讓代碼支持2個以上頁面的情況。
首先列出修改后的simp_blkdev_make_request()函數:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
        struct bio_vec *bvec;
        int i;
        unsigned long long dsk_offset;

        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_offset = bio->bi_sector << 9;

        bio_for_each_segment(bvec, bio, i) {
                unsigned int count_done, count_current;
                void *iovec_mem;
                void *dsk_mem;

                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;

                count_done = 0;
                while (count_done < bvec->bv_len) {
                        count_current = min(bvec->bv_len - count_done,
                                (unsigned int)(PAGE_SIZE
                                - ((dsk_offset + count_done) & ~PAGE_MASK)));

                        dsk_mem = radix_tree_lookup(&simp_blkdev_data,
                                (dsk_offset + count_done) >> PAGE_SHIFT);
                        if (!dsk_mem) {
                                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                        ": search memory failed: %llu\n",
                                        (dsk_offset + count_done)
                                        >> PAGE_SHIFT);
                                kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
                                bio_endio(bio, 0, -EIO);
#else
                                bio_endio(bio, -EIO);
#endif
                                return 0;
                        }
                        dsk_mem += (dsk_offset + count_done) & ~PAGE_MASK;

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

                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;
}

看樣子長了一些,但不要被嚇著了,因為讀的時候我們可以對代碼做一些簡化:
1:去掉亂七八糟的出錯處理
2:無視每行80字符限制
3:把比特運算改成等價但更易讀的乘除運算
4:無視礙眼的類型轉換
5:假設內核版本大于2.6.24,以去掉判斷版本的宏
就會變成這樣了:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
        struct bio_vec *bvec;
        int i;
        unsigned long long dsk_offset;

        dsk_offset = bio->bi_sector * 512;

        bio_for_each_segment(bvec, bio, i) {
                unsigned int count_done, count_current;
                void *iovec_mem;
                void *dsk_mem;

                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;

                count_done = 0;
                while (count_done < bvec->bv_len) {
                        count_current = min(bvec->bv_len - count_done, PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE);

                        dsk_mem = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) / PAGE_SIZE);
                        dsk_mem += (dsk_offset + count_done) % PAGE_SIZE;

                        switch (bio_rw(bio)) {
                        case READ:
                        case READA:
                                memcpy(iovec_mem + count_done, dsk_mem, count_current);
                                break;
                        case WRITE:
                                memcpy(dsk_mem, iovec_mem + count_done, count_current);
                                break;
                        }
                        count_done += count_current;
                }

                kunmap(bvec->bv_page);
                dsk_offset += bvec->bv_len;
        }

        bio_endio(bio, 0);
        return 0;
}
是不是清楚多了?

dsk_offset用來存儲當前要處理的數據在塊設備上的偏移,初始值是bio->bi_sector * 512,也就是起始扇區(qū)對應的偏移,也是第一個bio_vec對應的塊設備偏移。
每處理完成一個bio_vec時,dsk_offset值會被更新:dsk_offset += bvec->bv_len,以指向將要處理的數據在塊設備上的偏移。

在bio_for_each_segment()中代碼的起始和末尾,執(zhí)行kmap和kunmap開映射當前這個bio_vec的內存,這個知識在前面的章節(jié)中已經提到了,
這個處理的結果是iovec_mem指向當前的bio_vec中的緩沖區(qū)。

現在在kmap和kunmap之間的代碼的功能已經很明確了,就是完成塊設備上偏移為dsk_offset、長度為bvec->bv_len的數據與iovec_mem地址之間的傳送。
假設不考慮bio_vec跨越頁面邊界的情況,這段代碼應該十分寫意:
dsk_mem = radix_tree_lookup(&simp_blkdev_data, dsk_offset / PAGE_SIZE) + dsk_offset % PAGE_SIZE;
switch (bio_rw(bio)) {
case READ:
case READA:
        memcpy(iovec_mem, dsk_mem, bvec->bv_len);
        break;
case WRITE:
        memcpy(dsk_mem, iovec_mem, bvec->bv_len);
        break;
}
首先使用dsk_offset / PAGE_SIZE、也就是塊設備偏移在內存中數據所位于的頁面次序作為索引,查找該頁的內存起始地址,
然后加上塊設備偏移在該頁內的偏移、也就是dsk_offset % PAGE_SIZE,
就得到了內存中數據的地址,然后就是簡單的數據傳送。
關于塊設備偏移到內存地址的轉換,我們舉個例子:
假使模塊加載時我們分配的第1個頁面的地址為0xd0000000,用于存放塊設備偏移為0~4095的數據
第2個頁面的地址為0xd1000000,用于存放塊設備偏移為4096~8191的數據
第3個頁面的地址為0xc8000000,用于存放塊設備偏移為8192~12287的數據
第4個頁面的地址為0xe2000000,用于存放塊設備偏移為12288~16383的數據
對于塊設備偏移為9000的數據,首先通過9000 / PAGE_SIZE確定它位于第3個頁面中,
然后使用radix_tree_lookup(&simp_blkdev_data, 3)將查找出0xc8000000這個地址。
這是第3個頁面的起始地址,這個地址的數據在塊設備中的偏移是8192,
因此我們還要加上塊設備偏移在頁內的偏移量,也就是9000 % PAGE_SIZE = 808,
得到的才是塊設備偏移為9000的數據在內存中的數據地址。

當然,假設終歸是假設,往往大多數情況下是自欺欺人的,就好像彩迷總喜歡跟女友說如果中了500萬,就要怎么怎么對她好一樣。
現在回到殘酷的現實,我們還是要去考慮bio_vec跨越頁面邊界的情況。
這意味著對于一個bio_vec,我們將有可能傳送多次。
為了記錄前幾次中已經完成的數據量,我們引入了一個新的變量,叫做count_done。
在進行bio_vec內的第一次傳送前,count_done的值是0,每完成一次傳送,count_done將加上這次完成的數據量。
當count_done == bvec->bv_len時,就是大功告成的之日。

接著就是邊界的判斷。
當前塊設備偏移所在的內存頁中,塊設備偏移所在位置到頁頭的距離為:
offset % PAGE_SIZE
塊設備偏移所在位置到頁尾的距離為:
PAGE_SIZE - offset % PAGE_SIZE
這個距離也就是不超越頁邊界時所能傳送的數據的最大值。

因此在bio_vec內的每一次中,我們使用
count_current = min(bvec->bv_len - count_done, PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE);
來確定這一次傳送的數據量。
bvec->bv_len - count_done指的是余下需要傳送的數據總量,
PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE指的是從當前塊設備偏移開始、不超越頁邊界時所能傳送的數據的最大值。
如果bvec->bv_len - count_done > PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE,說明這一次將傳送從當前塊設備偏移到其所在內存頁的頁尾之間的數據,
余下的數據位于后續(xù)的頁面中,將在接下來的循環(huán)中搞定,
如果bvec->bv_len - count_done <= PAGE_SIZE - (dsk_offset + count_done) % PAGE_SIZE,那么可喜可賀,這將是當前bio_vec的最后一次傳送,完成后就可以回家洗澡了。

結合以上的說明,我想應該不難看懂simp_blkdev_make_request()的代碼了,而我們的程序也已經大功告成。

現在總結一下修改的位置:
1:把unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];換成static struct radix_tree_root simp_blkdev_data;
2:把本文中的free_diskmem()和alloc_diskmem()函數添加到代碼中,雖然沒有特別意義,但建議插在緊鄰simp_blkdev_init()之前的位置。
   但有特別意義的是free_diskmem()和alloc_diskmem()的順序,如果讀者到這里還打算提問是什么順序,作者可要哭了。
3:把simp_blkdev_make_request()、simp_blkdev_init()和simp_blkdev_exit()函數替換成文中的代碼。
   注意不要企圖使用簡化過的simp_blkdev_make_request()函數,否則造成的后果:從程序編譯失敗到讀者被若干美女輪奸,作者都概不負責。

從常理分析,在修改完程序后,我們都將試驗一次修改的效果。
這次也不例外,因為審判彭宇的王法官也是這么推斷的。

首先證明我們的模塊至今為止仍然經得起編譯、能夠加載:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step06 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step06/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step06/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step06/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
# insmod simp_blkdev.ko
#

看看模塊的加載時分配的非線性映射區(qū)域大。
# lsmod
Module                  Size  Used by
simp_blkdev             8212  0
...
#
如果這個Size一欄的數字沒有引起讀者的足夠重視的話,我們拿修改前的模塊來對比一下:
# lsmod
Module                  Size  Used by
simp_blkdev         16784392  0
看出區(qū)別了沒?

如果本章到這里還不結束的話,估計讀者要開始閃人了。
好的,我們馬上就結束,希望在這之前閃掉的讀者不要太多。
由于還沒有來得及閃掉而看到這段話的讀者們,作者相信你們具有相當的毅力。
學習是需要毅力的,這時的作者同樣也需要毅力來堅持完成這本教程。

最后還是希望讀者堅持,堅持看完所有的章節(jié),堅持在遇到每一個不明白的問題時都努力尋求答案,
堅持在發(fā)現自己感興趣的內容時能夠深入地去了解、探尋、思考。

<未完,待續(xù)>

[[i] 本帖最后由 OstrichFly 于 2008-11-26 09:31 編輯 [/i]]

論壇徽章:
0
19 [報告]
發(fā)表于 2008-11-25 21:32 |只看該作者
原帖由 2004sz 于 2008-11-21 14:08 發(fā)表
急切等待中~~~~~~~~~~~~~~~~~~~~~~~

謝謝支持!

論壇徽章:
0
20 [報告]
發(fā)表于 2008-11-26 10:05 |只看該作者
正在看塊設備方面,對我?guī)椭浅4蟆?br /> 謝謝!
您需要登錄后才可以回帖 登錄 | 注冊

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

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP