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

第十四章

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

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

|                 寫一個塊設備驅動                  

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

作者:趙磊                                        

| email: [email protected]                      

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

文章版權歸原作者所有。                            

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

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

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

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

在本章中我們要做一個比較大的改進,就是實現內存的推遲分配。

這意味着我們並不是在驅動程序加載時就分配用於容納數據的全部內存,
而是推遲到真正需要用到某塊內存時再進行分配。

詳細來說,我們將在塊設備的某個區域上發生第一次寫請求時分配用於容納被寫入數據的內存,
如果讀者在之前章節的薰陶下養成了細緻的作風和勤于思考的習慣,
應該能發現這裏提到的分配內存的時機是第一次寫,而不是第一次讀寫。
現在可能有些讀者已經悟出了這樣做的道理,讓我們無視他們,依然解釋一下這樣做的目的。
對塊設備而言,只要保證讀出的數據是最近一次寫進的即可。
如果在讀數據之前從來沒有往塊設備的同一塊區域中寫入數據,那麼這時返回任何隨機數據都是正確的。
這意味着對於第一次讀,我們完全可以返回任意的數據給用戶,這時並不需要分配某段內存來存儲它。
對真實的物理設備而言,就像我們買回的新硬盤,出廠時盤片中的數據內容是什麼都無所謂。
在具體的實現中,我們可以不對用以接收被讀出數據的內存進行任何填充,直接告訴上層“已經讀好了”,
這樣做無疑會更加快速,但這會造成2個問題:
1:這塊內存原先的內容最終將被傳送到用戶程序中,這將造成數據安全問題
2:違背了真實設備的一個潛特性,就是即使這個設備沒有寫入任何內容,對同一區域的多次讀操作返回的內容相同。
因此,我們將向接收數據的內存中寫些什麼,最簡單的就是用全0填充了。

實現這一功能的優點在於,塊設備不需要在一開始加載時就佔用全部的內存,這優化了系統資源的使用率。
讓我們假設塊設備自始至終沒有被全部填滿時,通過本章的功能,將佔用更少的內存。
另外,我們甚至可以創建容量遠遠大於機器物理內存的塊設備,只要在隨後的使用中不往這個塊設備中寫入過多的內容即可。

在linux中,類似的思想被廣泛應用。
比如對進程的內存區而言,並不是一開始就爲這段內存區申請和映射全部需要的物理內存,
又如在不少文件系統中,也不會給沒有寫入內容的文件部分分配磁盤的。

現在我們就實現這一功能。
分析代碼,我們發現不太容易找到往什麼地方加代碼。
往往在這種情況下,不如首先看看可以剝掉哪部分不需要的代碼,
正如初次跟一個mm時,如果兩個人都有些害羞,不知道從哪開始、或者正在期待對方打開局面時,
不如先脫下該脫的東西,然後的事情基本上就比較自然了。

現在的代碼中,明顯可以砍掉的是在驅動程序加載時用於申請容納數據的內存的代碼,
也就是alloc_diskmem()函數,把它砍了,沒錯,是全砍了。

還有調用它的代碼,在simp_blkdev_init()函數裏面的這幾行:
ret = alloc_diskmem();
if (IS_ERR_VALUE(ret))
        goto err_alloc_diskmem;
是的,也砍了。

還沒完,既然這個函數的調用都沒了,那麼調用這個函數失敗時的出錯處理也沒用了,也就是:
err_alloc_diskmem:
        put_disk(simp_blkdev_disk);
這兩句,不用猶豫了,砍掉。

經過剛纔的大刀闊斧後,我們發現......剛纔由於砍上癮了,不小心多砍了一條語句,就是對基樹的初始化語句:
INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
原來它是在alloc_diskmem()函數裏面的,現在alloc_diskmem()函數不在了,我們索性把它放到初始化模塊的simp_blkdev_init()函數中,
放到剛纔原來調用alloc_diskmem()函數的位置就行了。
(注:
其實這裏不添加INIT_RADIX_TREE()宏也行,直接在定義基樹結構時順便初始化掉就行了,也就是把
static struct radix_tree_root simp_blkdev_data;
改成
static struct radix_tree_root simp_blkdev_data = RADIX_TREE_INIT(GFP_KERNEL);
就行了,或者改成讓人更加撞牆的形式:
static RADIX_TREE(simp_blkdev_data, GFP_KERNEL);
也可以,但我們這裏的代碼中,依然沿用原先的方式。
)

這樣一來,simp_blkdev_init()函數變成了這個樣子:

static int __init simp_blkdev_init(void)
{
        int ret;
        ret = getparam();
        if (IS_ERR_VALUE(ret))
                goto err_getparam;

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

        INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);

        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 >> SIMP_BLKDEV_SECTORSHIFT);
        add_disk(simp_blkdev_disk);

        return 0;

err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
err_getparam:
        return ret;
}

淋漓盡致地大砍一番之後,我們發現下一步的工作清晰多了。
現在在模塊加載時,已經不會申請所需的內存,而我們需要做的就是,
在處理塊設備讀寫操作時,添加不存在相應內存時的處理代碼。


在程序中,查找基數中的一個內存塊是在simp_blkdev_trans()函數內完成的,目前的處理是:
this_first_page = radix_tree_lookup(&simp_blkdev_data,
        (dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
if (!this_first_page) {
        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                ": search memory failed: %llu\n",
                (dsk_offset + done_cnt)
                >> SIMP_BLKDEV_DATASEGSHIFT);
        return -ENOENT;
}
也就是找不到內存塊時直接看作錯誤。
在以前這是正確的,因爲所有的內存塊都在初始化驅動程序時申請了,因此除非電腦的腦子進水了,
運行錯了指令,或者人腦的腦子進水了,編錯了代碼,否則不會發生這種情況。


但現在情況不同了,這時找不到內存塊是正常的,這意味着該位置的數據從未被寫入過,
因此我們需要在這裏做出合理的動作。
也就是在本章開始時所說的,對於讀處理返回全0,對於寫處理給塊設備的這段空間申請內存,並寫入數據。
因此我們把上段代碼改成了這個樣子:
this_first_page = radix_tree_lookup(&simp_blkdev_data,
        (dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
if (!this_first_page) {
        if (!dir) {
                memset(buf + done_cnt, 0, this_cnt);
                goto trans_done;
        }


        /* prepare new memory segment for write */
        this_first_page = alloc_pages(
                GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
                SIMP_BLKDEV_DATASEGORDER);
        if (!this_first_page) {
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": allocate page failed\n");
                return -ENOMEM;
        }


        this_first_page->index = (dsk_offset + done_cnt)
                >> SIMP_BLKDEV_DATASEGSHIFT;


        if (IS_ERR_VALUE(radix_tree_insert(&simp_blkdev_data,
                this_first_page->index, this_first_page))) {
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": insert page to radix_tree failed"
                        " seg=%lu\n", this_first_page->index);
                __free_pages(this_first_page,
                        SIMP_BLKDEV_DATASEGORDER);
                return -EIO;
        }
}

對這段代碼的流程幾乎不要解釋了,因爲代碼本身就是最好的說明。
唯一要提一下的就是goto trans_done這句話,因爲前一條語句實質上已經完成了數據讀取,
因此需要直接跳轉到該段數據處理完成的位置,也就是函數中的done_cnt += this_cnt語句之前。
說到這裏猴急的讀者可能已經在done_cnt += this_cnt語句之前添加
trans_done:
這一行了,不錯,正是要加這一行。


改過的simp_blkdev_trans()函數變成了這個樣子:
static int simp_blkdev_trans(unsigned long long dsk_offset, void *buf,
                unsigned int len, int dir)
{
        unsigned int done_cnt;
        struct page *this_first_page;
        unsigned int this_off;
        unsigned int this_cnt;

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

                this_first_page = radix_tree_lookup(&simp_blkdev_data,
                        (dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
                if (!this_first_page) {
                        if (!dir) {
                                memset(buf + done_cnt, 0, this_cnt);
                                goto trans_done;
                        }

                        /* prepare new memory segment for write */
                        this_first_page = alloc_pages(
                                GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
                                SIMP_BLKDEV_DATASEGORDER);
                        if (!this_first_page) {
                                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                        ": allocate page failed\n");
                                return -ENOMEM;
                        }

                        this_first_page->index = (dsk_offset + done_cnt)
                                >> SIMP_BLKDEV_DATASEGSHIFT;

                        if (IS_ERR_VALUE(radix_tree_insert(&simp_blkdev_data,
                                this_first_page->index, this_first_page))) {
                                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                        ": insert page to radix_tree failed"
                                        " seg=%lu\n", this_first_page->index);
                                __free_pages(this_first_page,
                                        SIMP_BLKDEV_DATASEGORDER);
                                return -EIO;
                        }
                }

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

trans_done:
                done_cnt += this_cnt;
        }

        return 0;
}

代碼就這樣被莫名其妙地改完了,感覺這次的改動比預想的少,並且也比較集中,
這其實還是託了前些章的福,正是在此之前對程序結構的規劃調整,
在增加可讀性的同時,也給隨後的維護帶來方便。
處於良好維護下的程序代碼結構應該越維護越讓人賞心悅目,而不是越維護越混亂不堪。

現在我們來試驗一下這次修改的效果:
先編譯:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step14 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step14/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step14/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step14/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
#
沒發現問題。

然後看看目前的內存狀況:
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        87920 kB
LowTotal:       896356 kB
LowFree:        791920 kB
...
#
可以看出高端和低端內存分別剩餘87M和791M。

然後指定size=50M加載模塊後看看內存變化:
# insmod simp_blkdev.ko size=50M
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        86804 kB
LowTotal:       896356 kB
LowFree:        791912 kB
...
#
在這裏我們發現剩餘內存的變化不大,
這也證明了這次修改的效果,因爲加載模塊時不會申請用於存儲數據的全部內存。
而在原先的代碼中,這一步驟將使機器減少大約50M的剩餘空間。

然後我們來驗證讀取塊設備時也不會導致分配內存:
# dd if=/dev/simp_blkdev of=/dev/null
102400+0 records in
102400+0 records out
52428800 bytes (52 MB) copied, 0.376118 seconds, 139 MB/s
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        85440 kB
LowTotal:       896356 kB
LowFree:        791888 kB
...
#
剩餘內存幾乎沒有變化,這證明了我們的設想。

然後是寫設備的情況:
# dd if=/dev/zero of=/dev/simp_blkdev
dd: writing to `/dev/simp_blkdev': No space left on device
102401+0 records in
102400+0 records out
52428800 bytes (52 MB) copied, 0.542117 seconds, 96.7 MB/s
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        34116 kB
LowTotal:       896356 kB
LowFree:        791516 kB
...
#
這時剩餘內存終於減少了大約50M,
這意味着驅動程序申請了大約50M的內存用於存儲寫入的數據。


如果向已寫入的位置再次寫入數據,理論上不應該造成再一次的分配,
讓我們試試:
# dd if=/dev/zero of=/dev/simp_blkdev
dd: writing to `/dev/simp_blkdev': No space left on device
102401+0 records in
102400+0 records out
52428800 bytes (52 MB) copied, 0.644972 seconds, 81.3 MB/s
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        33620 kB
LowTotal:       896356 kB
LowFree:        791516 kB
...
#
結果與預想一致。

現在卸載模塊:
# rmmod simp_blkdev
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        84572 kB
LowTotal:       896356 kB
LowFree:        791640 kB
...
#
我們發現被驅動程序使用的內存被釋放回來了。

如果以上的實驗沒有讓讀者過癮的話,我們來繼續一個過分一些的,
也就是創建空間遠遠大於機器物理內存的塊設備。
首先我們看看目前的系統內存狀況:
# cat /proc/meminfo
...

HighTotal:     1146816 kB
HighFree:        77688 kB
LowTotal:       896356 kB
LowFree:        783296 kB
...
#
機器的總內存是2G,目前剩餘的高、低端內存加起來是860M左右。

然後我們加載模塊,注意一下size參數的值:
# insmod simp_blkdev.ko size=10000G
#
命令成功返回,而如果換作原先的代碼,
命令出錯返回......是不太可能的,
最可能的大概是內核直接panic。
這是因爲申請光全部內存的操作將導致申請出錯時運行的用於釋放內存的代碼所需要的內存都無法滿足。

無論我們設置多大的塊設備容量,模塊加載後只要不執行寫操作,
驅動程序都不會申請存儲數據的內存。而這個測試:
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        75208 kB
LowTotal:       896356 kB
LowFree:        783132 kB
...
#
也證明了這一點。

現在我們看看這時的塊設備情況:
# fdisk -l /dev/simp_blkdev

Disk /dev/simp_blkdev: 10737.4 GB, 10737418240000 bytes
255 heads, 63 sectors/track, 1305416 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

Disk /dev/simp_blkdev doesn't contain a valid partition table
#
果然是10000G,這可以通過換算10737418240000 bytes得到。
而fdisk顯示10737.4 GB是因爲它是按照1k=1000字節、1M=1000K、1G=1000M來算的,
這種流氓的算法給硬盤廠商的缺斤少兩行爲提供了極好的藉口。


這裏省略fdisk、mkfs、mount、cp等操作,
直接用dd往這個"10000G磁盤"中寫入50M的數據:
# dd if=/dev/zero of=/dev/simp_blkdev bs=1M count=50
50+0 records in
50+0 records out
52428800 bytes (52 MB) copied, 0.324054 seconds, 162 MB/s
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        23512 kB
LowTotal:       896356 kB
LowFree:        782884 kB
...
#
現在的內存情況證明我們的"10000G磁盤"爲這些數據申請了50M的內存。

實驗差不多了,我們卸載模塊:
# rmmod simp_blkdev.
#

做完以上的實驗,讀者可能會有一個疑問,如果我們真的向那個"10000G磁盤"中寫入了10000G的數據怎麼樣呢?
回答可能不太如人意,就是系統很可能會panic。
因爲這個操作將迫使驅動程序喫掉全部可能獲得的物理內存,並且在喫光最後那麼一丁點內存之前不會發生錯誤,
這也意味着走到出錯處理這一步的時候,系統已經幾乎無可救藥了。其實在此之前系統就會一次進行:
釋放緩存、試圖把所有的用戶進程的內存換出、殺死全部能夠殺死的進程等操作。
而我們的驅動程序由於被看作是內核的一部分,卻不會被停止,而是在繼續不停的喫掉通過上述方式釋放出的可憐的內存。
試想,一個已經走到這一步的系統還有什麼繼續運行的可能呢?

因此,我們的程序確實需要改善以解決這個問題,因爲世界上總是有一些瘋狂的人在想各種辦法虐待電腦。
但我們並不打算在本教程中解決它,因爲這個教程中的每一章都企圖爲讀者說明一類知識或一種方法,
而不是僅僅爲了這個示例性質的程序的功能本身。
所以這一項改善就當作是留給讀者的練習了。

本章通過改善塊設備驅動程序實現了內存的滯後申請,
其目的在於介紹這種方法,以使它在其他的相似程序中也得以實現。
不過,這並不意味着作者希望讀者把這種方法過分引用,
比如引用成平時不學習,考試前臨時抱佛腳。


<未完,待續>

#include <linux/init.h>
#include <linux/module.h>
#include <linux/genhd.h>		//add_disk
#include <linux/blkdev.h>		//struct block_device_operations
#include <linux/hdreg.h>

#define _DEBUG_

#define BLK_PAGE_ORDER			2
#define BLK_PAGE_SIZE			(PAGE_SIZE << BLK_PAGE_ORDER)
#define BLK_PAGE_SHIFT			(PAGE_SHIFT + BLK_PAGE_ORDER)
#define BLK_PAGE_MASK			(~(BLK_PAGE_SIZE - 1))

#define BLK_SECTOR_SHIFT		9
#define BLK_SECTOR_SIZE			(1ULL << BLK_SECTOR_SHIFT)
#define BLK_SECTOR_MASK			(~(BLK_SECTOR_SIZE - 1))

#define BLK_DISK_NAME 			"block_name"
#define BLKDEV_DEVICEMAJOR      	COMPAQ_SMART2_MAJOR
#define BLKDEV_BYTES        		(16*1024*1024)
#define MAX_PARTITIONS			64

static int MAJOR_NR = 0;

static struct gendisk *g_blkdev_disk;
static struct request_queue *g_blkdev_queue;

struct radix_tree_root blk_dev_data;
DEFINE_MUTEX(blk_radix_mutex);
static unsigned long long g_blk_size = 1;
static char*	defaule_size = "16M";

int getsize(void)
{
	char flag;
	char tail = 'N';

#ifdef _DEBUG_
	printk(KERN_WARNING "parameter = %s\n",defaule_size);
#endif		
	
	if(sscanf(defaule_size,"%llu%c%c",&g_blk_size,&flag,&tail) != 2){
		return -EINVAL;
	}
	
	if(!g_blk_size)
		return -EINVAL;
	
	switch(flag){
	case 'g':
	case 'G':
		g_blk_size <<= 30;
		break;
	case 'm':
	case 'M':
		g_blk_size <<= 20;
		break;
	case 'k':
	case 'K':
		g_blk_size <<= 10;
		break;
	}
	
	g_blk_size = (g_blk_size + BLK_SECTOR_SIZE - 1) & BLK_SECTOR_MASK;
	//此處爲字節對齊.(1<<9 - 1) = 255 = 0xFF
	
#ifdef _DEBUG_
	printk(KERN_WARNING "size = %llu tail = %c\n",g_blk_size,tail);
#endif			
	return 0;
}

static int disk_get_pages(struct page **ppage,unsigned int index)
{
	int ret = 0;
	struct page *page = NULL;
	page = (void*)alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,BLK_PAGE_ORDER);
	if(NULL == page){
		ret = -ENOMEM;
		goto err_get_page;
	}
		
	page->index = index;
	ret = radix_tree_insert(&blk_dev_data,index,(void*)page);
	if(IS_ERR_VALUE(ret)){
		ret = -EIO;
		goto err_insert;
	}
	*ppage = page;
	return 0;

err_insert:
	__free_pages(page,BLK_PAGE_ORDER);
err_get_page:
	return ret;
}

/*
	function:	blk_make_once
	description:
		該函數完成一次數據操作,將數據讀/寫到指定的page頁面.
	parameter:	
		struct page *page,	:要讀/寫的頁面
		unsigned int offset,	:頁面的相對偏移長度
		void *iovec_men,		:真實的數據地址
		unsigned int len,		:數據長度
		int dir			:數據方向
	
*/
static int blk_make_once(struct page *page,unsigned int offset,void *iovec_men,unsigned int len,int dir)
{	
	void *dsk_mem = NULL;
	unsigned int count_current = 0;
	unsigned int count_done = 0;
	unsigned int count_offset = 0;
	struct page *this_page = NULL;
	
	while(count_done < len){
		count_offset = (offset + count_done) & ~PAGE_MASK;
		/*
			count_offset:計算當前地址到頁首的偏移
		*/
		count_current = min(len - count_done,
							(unsigned int)(PAGE_SIZE - count_offset));
		/*
			count_current:計算當前傳輸數據的長度,若跨頁,則取此頁到頁尾的長度
			(PAGE_SIZE - count_offset):計算當前偏移到頁尾的長度
		*/
		this_page = page + ((offset + count_done) >> PAGE_SHIFT);
		/*
			this_page:獲取片面,將頁面映射至高端內存
		*/
		dsk_mem = kmap(this_page);
		if(!dsk_mem){
			printk(KERN_ERR BLK_DISK_NAME
					": get memory page address failed: %p\n",
					page);
			return -EIO;
		}
		
		dsk_mem += count_offset;
		
		if(!dir){
			//read
			memcpy(iovec_men + count_done,dsk_mem,count_current);
		}else{
			//write
			memcpy(dsk_mem,iovec_men + count_done,count_current);
		}
		
		kunmap(this_page);
		
		count_done += count_current;
	}

	return 0;	
}


/*
	function:	blk_make_transition
	description:
		該函數完成一次映射好的becv數據操作
	parameter:	
		unsigned long long disk_offset,	:要讀寫的虛擬磁盤的地址
		void *iovec_men,				:映射好的bvec操作的數據地址
		unsigned int len,				:數據長度
		int dir					:數據方向
	
*/
static int blk_make_transition(unsigned long long disk_offset,void *iovec_men,unsigned int len,int dir)
{
	unsigned int count_current = 0;
	unsigned int count_done = 0;
	unsigned int count_offset = 0;
	struct page *page = NULL;
	count_done = 0;
	while(count_done < len){
		count_offset = (disk_offset + count_done) & ~BLK_PAGE_MASK;
		/*
			count_offset:表示當前頁對應頁對齊的地址的偏移長度
		*/
		count_current = min(len - count_done,
						(unsigned int)(BLK_PAGE_SIZE - count_offset));
		/*
			BLK_PAGE_SIZE - count_offset:表示當前頁最後能存儲的長度.
		*/
		
		mutex_lock(&blk_radix_mutex);
		/*
			採用互斥鎖,防止多進程同時訪問至此,該頁面未申請,導致多個進程同時申請同一個地址頁面
			導致頁面相互覆蓋,造成的頁面浪費.浪費的頁面無法釋放.
		*/
		page = (struct page*)radix_tree_lookup(&blk_dev_data,(disk_offset + count_done) >> BLK_PAGE_SHIFT);
		if(!page){
			if(!dir){
				memset(iovec_men + count_done,0,count_current);
				goto trans_done;
			}
			if(IS_ERR_VALUE(disk_get_pages(&page,(disk_offset + count_done) >> BLK_PAGE_SHIFT))){
				printk(KERN_ERR BLK_DISK_NAME
						": alloc page failed index: %llu\n",
						(disk_offset + count_done) >> BLK_PAGE_SHIFT);
				mutex_unlock(&blk_radix_mutex);
				return -EIO;
			}
		}
		//進行一次數據讀寫
		if(IS_ERR_VALUE(blk_make_once(page,count_offset,iovec_men + count_done,count_current,dir))){
			return -EIO;
		}
trans_done:		
		mutex_unlock(&blk_radix_mutex);
		count_done += count_current;
	}
	return 0;	
}

static int blkdev_make_request(struct request_queue *q, struct bio *bio)
{
	struct bio_vec *bvec;
	int i;
	int dir = 0;
	unsigned long long disk_offset = 0;

	if ((bio->bi_sector << BLK_SECTOR_SHIFT) + bio->bi_size > g_blk_size) {
		printk(KERN_ERR BLK_DISK_NAME
				": bad request: block=%llu, count=%u\n",
				(unsigned long long)bio->bi_sector, bio->bi_size);
		goto bio_err;
	}
	
	switch(bio_rw(bio)){
	case READ:
	case READA:
		dir = 0;
		break;
	case WRITE:
		dir = 1;
		break;
	}
	
	disk_offset = bio->bi_sector << BLK_SECTOR_SHIFT;
	
	bio_for_each_segment(bvec, bio, i) {
		void *iovec_mem;
		iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
		
		if(!iovec_mem){
			printk(KERN_ERR BLK_DISK_NAME
					": kmap page faile: %p\n",
                    bvec->bv_page);
            goto bio_err;    
		}
		//進行一次bvec的操作
		if(IS_ERR_VALUE(blk_make_transition(disk_offset,iovec_mem,bvec->bv_len,dir))){
			kunmap(bvec->bv_page);
			goto bio_err;
		}
		kunmap(bvec->bv_page);
		disk_offset += bvec->bv_len;
	}		
	
	bio_endio(bio, 0);
	return 0;
bio_err:
	bio_endio(bio, -EIO);
	return 0;
}

int gendisk_getgeo(struct block_device *pblk_dev, struct hd_geometry *phd_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 (g_blk_size < 16 * 1024 * 1024) {
		phd_geo->heads = 1;
		phd_geo->sectors = 1;
	} else if (g_blk_size < 512 * 1024 * 1024) {
		phd_geo->heads = 1;
		phd_geo->sectors = 32;
	} else if (g_blk_size < 16ULL * 1024 * 1024 * 1024) {
		phd_geo->heads = 32;
		phd_geo->sectors = 32;
	} else {
		phd_geo->heads = 255;
		phd_geo->sectors = 63;
	}

	phd_geo->cylinders = g_blk_size >> BLK_SECTOR_SHIFT / phd_geo->heads / phd_geo->sectors;
	
	return 0;
}

struct block_device_operations fop = {
	.owner = THIS_MODULE,
	.getgeo = gendisk_getgeo,
};

void delete_diskmem(void)
{
	int i = 0;
	int page_count = 0;
	int next_count = 0;
	struct page *pagelist[64] = {NULL};
	
	page_count = 0;
	next_count = 0;
	do{
		page_count = radix_tree_gang_lookup(&blk_dev_data, (void**)pagelist,next_count,ARRAY_SIZE(pagelist));
		for(i = 0;i < page_count;i++){
			next_count = pagelist[i]->index;
			radix_tree_delete(&blk_dev_data,next_count);
			__free_pages(pagelist[i],BLK_PAGE_ORDER);
		}
		next_count++;
	}while(page_count == ARRAY_SIZE(pagelist));
}

static int __init initialization_function(void)
{
	int ret = 0;
	
	getsize();
	
	MAJOR_NR = register_blkdev(0, BLK_DISK_NAME);
	if(MAJOR_NR < 0)
	{		
		return -1;
	}
	
	g_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
	if(NULL == g_blkdev_queue){
		ret = -ENOMEM;		
		goto err_alloc_queue;
	}
	
	blk_queue_make_request(g_blkdev_queue, blkdev_make_request);
	
	g_blkdev_disk = alloc_disk(MAX_PARTITIONS);
	if(NULL == g_blkdev_disk){
		ret = -ENOMEM;		
		goto err_alloc_disk;
	}
	
	INIT_RADIX_TREE(&blk_dev_data,GFP_KERNEL);
	
	strcpy(g_blkdev_disk->disk_name,BLK_DISK_NAME);
	g_blkdev_disk->major = MAJOR_NR;
	g_blkdev_disk->first_minor = 0;
	g_blkdev_disk->fops = &fop;
	g_blkdev_disk->queue = g_blkdev_queue;
	
	set_capacity(g_blkdev_disk, g_blk_size>>BLK_SECTOR_SHIFT);
	
	add_disk(g_blkdev_disk);
#ifdef _DEBUG_
	printk(KERN_WARNING "ok\n");
#endif
	return ret;
	
err_alloc_disk:
	blk_cleanup_queue(g_blkdev_queue);
err_alloc_queue:
	unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);
	return ret;
}

static void __exit cleanup_function(void)
{
	del_gendisk(g_blkdev_disk);						//->add_disk
	delete_diskmem();								//->alloc_diskmem
	put_disk(g_blkdev_disk);						//->alloc_disk
	blk_cleanup_queue(g_blkdev_queue);					//->blk_init_queue
	unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);
}

//註冊模塊加載卸載函數
module_init(initialization_function);					//指定模塊加載函數
module_exit(cleanup_function);						//指定模塊卸載函數

//到處函數.導出名size,對應變量defaule_size
module_param_named(size, defaule_size, charp, S_IRUGO);

//模塊信息及許可證
MODULE_AUTHOR("LvApp");								//作者
MODULE_LICENSE("Dual BSD/GPL");						//許可證
MODULE_DESCRIPTION("A simple block module");				//描述
MODULE_ALIAS("block");						   //別名

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章