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

第十章

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

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

|                 寫一個塊設備驅動                  

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

作者:趙磊                                        

| email: [email protected]                      

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

文章版權歸原作者所有。                            

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

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

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

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

如果你的linux系統是x86平臺,並且內存大於896M,那麼恭喜你,我們大概可以在這個實驗中搞壞你的系統。
反之如果你的系統不符合這些條件,也不用爲無法搞壞系統而感到失望,本章的內容同樣適合你。
這時作者自然也要申明一下對讀者產生的任何損失概不負責,
因爲這年頭一不小心就可能差點成了被告,比如南京的彭宇和鎮江花山灣的小許姑娘。


在實驗看到的情況會因爲系統的實際狀況不同而稍有區別,但我們需要說明的問題倒是相似的。
但希望讀者不要把這種相似理解成了ATM機取款17.5萬和貪污2.6億在判決上的那種相似。


首先我們來看看目前系統的內存狀況:
# cat /proc/meminfo
MemTotal:      1552532 kB
MemFree:       1529236 kB
Buffers:          2716 kB
Cached:          10124 kB
SwapCached:          0 kB
Active:           8608 kB
Inactive:         7664 kB
HighTotal:      655296 kB
HighFree:       640836 kB
LowTotal:       897236 kB
LowFree:        888400 kB
SwapTotal:      522104 kB
SwapFree:       522104 kB
Dirty:              44 kB
Writeback:           0 kB
AnonPages:        3440 kB
Mapped:           3324 kB
Slab:             2916 kB
SReclaimable:      888 kB
SUnreclaim:       2028 kB
PageTables:        272 kB
NFS_Unstable:        0 kB
Bounce:              0 kB
WritebackTmp:        0 kB
CommitLimit:   1298368 kB
Committed_AS:    10580 kB
VmallocTotal:   114680 kB
VmallocUsed:       392 kB
VmallocChunk:   114288 kB
HugePages_Total:     0
HugePages_Free:      0
HugePages_Rsvd:      0
HugePages_Surp:      0
Hugepagesize:     4096 kB
DirectMap4k:     12288 kB
DirectMap4M:    905216 kB
#
輸出很多,但我們只關心這幾行:
MemFree:       1529236 kB --這說明系統中有接近1.5G的空閒內存
HighFree:       640836 kB --這說明空閒內存中,處在高端的有600M左右
LowFree:        888400 kB --這說明空閒內存中,處在低端的有800M左右


現在加載上一章完成的模塊,我們指定創建800M的塊設備:
# insmod simp_blkdev.ko size=800M
#
成功了,我們再看看內存狀況:
# cat /proc/meminfo
MemFree:        708812 kB
HighFree:       640464 kB
LowFree:         68348 kB
...
#
我們發現高端內存沒怎變,低端內存卻已經被耗得差不多了。
我們一不做二不休,繼續加大塊設備的容量,看看極限能到多少:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=860M
# cat /proc/meminfo
MemFree:        651184 kB
HighFree:       641972 kB
LowFree:          9212 kB
...
#
系統居然還沒事,這時雖然高端內存還是沒怎麼變,但低端內存剩下的得已經很可憐了。
然後進一步加大塊設備的容量:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=870M
...
這裏不用再cat /proc/meminfo了,因爲系統已經完蛋了。
如果有些讀者嗜好獨特,對出錯信息情有獨鍾的話,在這裏也滿足一下:
kernel: [ 3588.769050] insmod invoked oom-killer: gfp_mask=0x80d0, order=2, oomkilladj=0
kernel: [ 3588.769516] Pid: 4236, comm: insmod Tainted: G        W 2.6.27.4 #53
kernel: [ 3588.769868]  [<c025e61e>] oom_kill_process+0x42/0x183
kernel: [ 3588.771041]  [<c025ea5c>] out_of_memory+0x157/0x188
kernel: [ 3588.771306]  [<c0260a5c>] __alloc_pages_internal+0x2ab/0x360
kernel: [ 3588.771500]  [<c0260b25>] __get_free_pages+0x14/0x24
kernel: [ 3588.771679]  [<f8865204>] alloc_diskmem+0x45/0xb5 [simp_blkdev]
kernel: [ 3588.771899]  [<f8867054>] simp_blkdev_init+0x54/0xc6 [simp_blkdev]
kernel: [ 3588.772217]  [<c0201125>] _stext+0x3d/0xff
kernel: [ 3588.772393]  [<f8867000>] ? simp_blkdev_init+0x0/0xc6 [simp_blkdev]
kernel: [ 3588.772599]  [<c0235f2f>] ? __blocking_notifier_call_chain+0x40/0x4c
kernel: [ 3588.772845]  [<c0241771>] sys_init_module+0x87/0x19d
kernel: [ 3588.773250]  [<c02038cd>] sysenter_do_call+0x12/0x21
kernel: [ 3588.773884]  =======================
kernel: [ 3588.774237] Mem-Info:
kernel: [ 3588.774241] DMA per-cpu:
kernel: [ 3588.774404] CPU    0: hi:    0, btch:   1 usd:   0
kernel: [ 3588.774582] Normal per-cpu:
kernel: [ 3588.774689] CPU    0: hi:  186, btch:  31 usd:   0
kernel: [ 3588.774870] HighMem per-cpu:
kernel: [ 3588.778602] CPU    0: hi:  186, btch:  31 usd:   0
...


搞壞系統就當是交學費了,但交完學費我們總要學到些東西。
雖然公款出國考察似乎已經斯通見慣,但至少在我們的理解中,學費不是旅遊費,更不是家屬的旅遊費。


我們通過細心觀察、周密推理後得出的結論是:
目前的塊設備驅動程序會一根筋地使用低端內存,即使系統中低端內存很緊缺的時候,
也會直道把系統搞死卻不去動半點的高端內存,這未免也太挑食了,
因此在本章和接下來的幾章中,我們將幫助驅動程序戒掉對低端內存的癮。


相對高端內存而言,低端內存是比較寶貴的,這是因爲它不需要影射就能直接被內核訪問的特性。
而內核中的不少功能都直接使用低端內存,以保證訪問的速度和簡便,
但換句話來說,如果低端內存告急,那麼系統可能離Panic也不遠了。
因此總的來說,對低端內存的使用方法大概應該是:除非有足夠理由,否則就別亂佔着。
詳細來說,就是:
1:不需要使用低端內存的“在內核中不需要映射就能直接訪問”這個特性的功能,應該優先使用高端內存
   如:分配給用戶態進程的內存,和vmalloc的內存
2:需要佔用大量內存的功能,並且也可以通過高端內存實現的,應該優先使用高端內存
   如:我們的程序


與內存有關的知識我們在以前的章節中已經談到,因此這裏不再重複了,
但需要說明的是在高端內存被映射之前,我們是無法通過指針來指向它的。
因爲它不在內核空間的地址範圍以內。


雖然如此,我們卻無論如何都需要找出一種方法來指定一個沒有被映射的高端內存,
這是由於至少在進行映射操作時,我們需要指定去映射誰。
這就像爲一羣猴子取名的時候,如何來說明是正在給哪隻猴子取名一樣。


雖然給猴子取名的問題可能比較容易解決,比如我們可以說,
給哪隻紅屁股的公猴取名叫齊天大聖、給那隻瘦瘦的母猴取名叫白晶晶,
但可惜一塊高端內存即沒有紅屁股,又沒有胖瘦之分,
它們唯一有的就是地址,因此我們也必須通過地址來指定這段高端內存。


剛纔說過,在高端內存被映射之前,他在內核的地址空間中是不存在的,
但雖然如此,它至少存在其物理地址,而我們正是可以通過它的物理地址來指定它。
是的,本質上是這樣的,但在linux中,我們還需要再繞那麼一丁點:
linux在啓動階段爲全部物理內存按頁爲單位建立了的對應的struct page結構,用來管理這些物理內存,
也就是,每個頁的物理內存,都有着1對1的struct page結構,而這些struct page結構是位於低端內存中的,
我們只要使用指向某個struct page結構的指針,就能指定物理內存中的一個頁。
因此,對於沒有被映射到內核空間中的高端內存,我們可以通過對應的struct page結構來指定它。
(如果讀者希望瞭解更詳細的知識,可以考慮從virt_to_page函數一路google下去)


我們在這裏大肆談論高端內存的表示方法,因爲這是讓我們的模塊使用高端內存的前提。


我們的驅動程序使用多段內存來存儲塊設備中的數據。
原先的程序中,我們使用指向這些內存段的指針來指定這些數據的位置,這是沒有問題的,
因爲當時我們是使用__get_free_pages()來申請內存,__get_free_pages()函數只能用來申請低端內存,
因爲這個函數返回的是申請到的內存的指針,而上文中說過,高端內存是不能用這樣的指針表示的。
要申請高端內存,明顯不能使用這樣的函數,因此我們隆重介紹它的代替者出場:
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
這個函數的參數與__get_free_pages()相同,但區別在於,它返回指向struct page的指針,
這個我們在上文中介紹過的指針賦予了alloc_pages()函數申請高端內存的能力。
其實申請一塊高端內存並不難,只要使用__GFP_HIGHMEM參數調用alloc_pages()函數,
就可能返回一塊高端內存,之所以說是“可能”,使因爲在某些情況下,比如高端內存不夠或不存在時,也會用低端內存充數。


我們的現在的目標是讓驅動程序使用高端內存,這需要:
1:讓驅動程序申請高端內存
2:讓驅動程序使用高端內存


但在這一章中,我們要做的即不是1,也不是2,而是1之前的準備工作。
因爲1和2必須一氣呵成地改完,而爲了讓一氣呵成的時候不要再面臨其他插曲,
我們需要做好充足的準備工作,就像ml前尿尿一樣。
對應到程序的修改工作上,我們打算先讓程序使用struct page *來指定申請到的內存。


要實現這個目的,我們先要改申請內存的函數,也就是alloc_diskmem()。
剛纔我們介紹過alloc_pages(),現在就要用它了:


首先把函數中定義的
void *p;
改成
struct page *page;
因爲我們要使用struct page *來指定申請到的內存,而不是地址了。


然後把
p = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, SIMP_BLKDEV_DATASEGORDER);
改成
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, SIMP_BLKDEV_DATASEGORDER);
這一行改動的原因大概已經說得很詳細了。


還有那個if(!p)改成if (!page)


然後就是把指針加入基樹的那一行:
ret = radix_tree_insert(&simp_blkdev_data, i, p);
改成
ret = radix_tree_insert(&simp_blkdev_data, i, page);


由於我們使用了struct page *來指定申請到的內存,因此錯誤處理部分也要小改一下:
free_pages((unsigned long)p, SIMP_BLKDEV_DATASEGORDER);
改成
__free_pages(page, SIMP_BLKDEV_DATASEGORDER);
這裏補充介紹一下__free_pages()函數,可能大家已經猜到其作用了,
其實與我們原先使用的free_pages()函數相似,都是用來釋放一段內存,
但__free_pages()使用struct page *來指定要釋放的內存,這也意味着它能夠用來釋放高端內存。


大家應該已經發現我們雖然改用alloc_pages()函數來申請內存,但並沒有指定__GFP_HIGHMEM參數,
這時申請到的仍然是低端內存,因此避免了在這一章中對訪問內存那部分代碼的大肆改動。


改動過的alloc_pages()函數是這樣的:

int alloc_diskmem(void)
{
        int ret;
        int i;
        struct page *page;


        INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);


        for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
                >> SIMP_BLKDEV_DATASEGSHIFT; i++) {
                page = alloc_pages(GFP_KERNEL | __GFP_ZERO,
                        SIMP_BLKDEV_DATASEGORDER);
                if (!page) {
                        ret = -ENOMEM;
                        goto err_alloc;
                }


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


err_radix_tree_insert:
        __free_pages(page, SIMP_BLKDEV_DATASEGORDER);
err_alloc:
        free_diskmem();
        return ret;
}

相應的,釋放內存用的free_diskmem()函數也需要一些更改,
爲了避免有人說作者唐僧,列出修改後的樣子應該已經足夠了:
void free_diskmem(void)
{
        int i;
        struct page *page;


        for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
                >> SIMP_BLKDEV_DATASEGSHIFT; i++) {
                page = radix_tree_lookup(&simp_blkdev_data, i);
                radix_tree_delete(&simp_blkdev_data, i);
                /* free NULL is safe */
                __free_pages(page, SIMP_BLKDEV_DATASEGORDER);
        }
}

隨後是simp_blkdev_make_request()函數:


首先我們不是把void *dsk_mem改成struct page *dsk_page,而是增加一個
struct page *dsk_page;
變量,因爲在訪問內存時,我們還是需要用到dsk_mem變量的。


然後是從基數中獲取指針的代碼,把原先的
dsk_mem = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) >> SIMP_BLKDEV_DATASEGSHIFT);
改成
dsk_page = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) >> SIMP_BLKDEV_DATASEGSHIFT);
雖然看起來沒什麼太大變化,但我們需要知道,這時基樹返回的指針已經不是直接指向數據所在的內存了。


還有那個判斷是否從基樹中獲取成功的
if (!dsk_mem) {
用腳丫子也能想得出應該改成這樣:
if (!dsk_page) {


還有就是我們需要首先將struct page *dsk_page地址轉換成內存的地址後,才能對這塊內存進行訪問。
這裏我們使用了page_address()函數。
這個函數可以獲得struct page數據結構所對應內存的地址。
這時可能有讀者要問了,如果這個struct page對應的是高端內存,那麼如何返回地址呢?
實際上,這種情況下如果高端內存中的頁面已經被映射到內核的地址空間,那麼函數會返回映射到內核空間中的地址,
而如果沒有映射的話,函數將返回0。
對於我們目前的程序而言,由於使用的是低端內存,因此struct page對應的內存總是處於內核地址空間中的。


對應到代碼中,我們需要在使用dsk_mem之前,也就是
dsk_mem += (dsk_offset + count_done) & ~SIMP_BLKDEV_DATASEGMASK;
這條語句之前,讓dsk_mem指向struct page *dsk_page對應的內存的實際地址。
這是通過如下代碼實現的:
dsk_mem = page_address(dsk_page);
if (!dsk_mem) {
        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                ": get page's address failed: %p\n",
                dsk_page);
        kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
        bio_endio(bio, 0, -EIO);
#else
        bio_endio(bio, -EIO);
#endif

總的來說,修改後的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 << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
                > simp_blkdev_bytes) {
                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                        ": bad request: block=%llu, count=%u\n",
                        (unsigned long long)bio->bi_sector, bio->bi_size);
#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 << SIMP_BLKDEV_SECTORSHIFT;


        bio_for_each_segment(bvec, bio, i) {
                unsigned int count_done, count_current;
                void *iovec_mem;
                struct page *dsk_page;
                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)(SIMP_BLKDEV_DATASEGSIZE
                                - ((dsk_offset + count_done) &
                                ~SIMP_BLKDEV_DATASEGMASK)));


                        dsk_page = radix_tree_lookup(&simp_blkdev_data,
                                (dsk_offset + count_done)
                                >> SIMP_BLKDEV_DATASEGSHIFT);
                        if (!dsk_page) {
                                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                        ": search memory failed: %llu\n",
                                        (dsk_offset + count_done)
                                        >> SIMP_BLKDEV_DATASEGSHIFT);
                                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 = page_address(dsk_page);
                        if (!dsk_mem) {
                                printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                        ": get page's address failed: %p\n",
                                        dsk_page);
                                kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
                                bio_endio(bio, 0, -EIO);
#else
                                bio_endio(bio, -EIO);
#endif
                        }


                        dsk_mem += (dsk_offset + count_done)
                                & ~SIMP_BLKDEV_DATASEGMASK;


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

通過對這3個函數的更改,代碼可以使用struct page *來定位存儲塊設備數據的內存了。
這也爲將來使用高端內存做了一部分準備。


因爲本章修改的代碼在外部功能上沒有發生變動,所以我們就不在這裏嘗試編譯了運行代碼了。
不過感興趣的讀者不妨試一試這段代碼能不能進行編譯和會不會引起死機。


<未完,待續>

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

/*
	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;
	dsk_mem = page_address(page);
	if(!dsk_mem){
		printk(KERN_ERR BLK_DISK_NAME
				": get memory page address failed: %p\n",
				page);
		return -EIO;
	}
	dsk_mem += offset;
	
	if(!dir){
		//read
		memcpy(iovec_men,dsk_mem,len);
	}else{
		//write
		memcpy(dsk_mem,iovec_men,len);
	}
	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:表示當前頁最後能存儲的長度.
		*/
		page = (struct page*)radix_tree_lookup(&blk_dev_data,(disk_offset + count_done) >> BLK_PAGE_SHIFT);
		if(!page){
			printk(KERN_ERR BLK_DISK_NAME
					": search page index failed: %d\n",
					count_offset);
			return -EIO;
		}
		//進行一次數據讀寫
		if(IS_ERR_VALUE(blk_make_once(page,count_offset,iovec_men + count_done,count_current,dir))){
			return -EIO;
		}
		
		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;
	struct page *page = NULL;
	for(i = 0; i < (g_blk_size + BLK_PAGE_SIZE - 1) / BLK_PAGE_SIZE; i++){
		page = radix_tree_lookup(&blk_dev_data,i);
		radix_tree_delete(&blk_dev_data,i);
		__free_pages(page,BLK_PAGE_ORDER);
	}
}

int alloc_diskmem(void)
{
	int ret = 0;
	int i = 0;
	struct page *page = NULL;
	INIT_RADIX_TREE(&blk_dev_data,GFP_KERNEL);
	for(i = 0; i < (g_blk_size + BLK_PAGE_SIZE - 1) / BLK_PAGE_SIZE; i++){
		page = (void*)alloc_pages(GFP_KERNEL | __GFP_ZERO,BLK_PAGE_ORDER);
		if(NULL == page){
			ret = -ENOMEM;
			goto err_get_page;
		}
		ret = radix_tree_insert(&blk_dev_data,i,(void*)page);
		if(IS_ERR_VALUE(ret)){
			goto err_insert;
		}
	}
#ifdef _DEBUG_
	printk(KERN_WARNING "page size = %d\n",i);
#endif	
	return 0;

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

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;
	}
	
	//申請頁面空間
	ret = alloc_diskmem();
	if(IS_ERR_VALUE(ret)){
		goto err_alloc_disk;
	}
	
	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");						   //別名

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

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