第二章
本教程修改自趙磊的網上的一系列教程.本人覺得該系列教程寫的非常不錯.以風趣幽默的語言將塊驅動寫的非常詳細,對於入門教程,應該屬於一份經典了. 本人在這對此係列教程對細微的修改,僅針對Linux 2.6.36版本.並編譯運行成功. 該教程所有版權仍歸作者趙磊所有,本人只做適當修改.
第2章
+---------------------------------------------------+
| 寫一個塊設備驅動
+---------------------------------------------------+
| 作者:趙磊
| email: [email protected]
+---------------------------------------------------+
| 文章版權歸原作者所有。
| 大家可以自由轉載這篇文章,但原版權信息必須保留。
| 如需用於商業用途,請務必與原作者聯繫,若因未取得
| 授權而收起的版權爭議,由侵權者自行負責。
+---------------------------------------------------+
上一章不但實現了一個最簡單的塊設備驅動程序,而且可能也成功地嚇退了不少準備繼續看下去的讀者。
因爲第一章看起來好像太難了。
不過讀者也不要過於埋怨作者,因爲大多數情況下第一次都不是什麼好的體驗......
對於堅持到這裏的讀者,這一章中,我們準備了一些簡單的內容來犒勞大家。
關於塊設備與I/O調度器的關係,我們在上一章中已經有所提及。
I/O調度器可以通過合併請求、重排塊設備操作順序等方式提高塊設備訪問的順序。
就好像吃街邊的大排檔,如果點一個冷門的品種,可能會等更長的時間,
而如果點的恰好與旁邊桌子上剛點的相同,那麼會很快上來,因爲廚師八成索性一起炒了。
然而I/O調度器和塊設備的情況卻有一些微妙的區別,大概可以類比成人家點了個西紅柿雞蛋湯你接着就點了個西紅柿炒蛋。
聰明的廚師一定會先做你的菜,因爲隨後可以直接往鍋里加水煮湯,可憐比你先來的人喝的卻是你的刷鍋水。
兩個菜一鍋煮表現在塊設備上可以類比成先後訪問塊設備的同一個位置,這倒是與I/O調度器無關,有空學習linux緩存策略時可以想想這種情況。
一個女孩子換了好多件衣服問我漂不漂亮,而我的評價只要一眼就能拿出來。
對方總覺得衣服要牌子好、面料好、搭配合理、要符合個人的氣質、要有文化,而我的標準卻簡單的多:越薄越好。
所謂臭氣相投,我寫的塊設備驅動程序對I/O調度器的要求大概也是如此。
究其原因倒不是因爲塊設備驅動程序好色,而是這個所謂塊設備中的數據都是在內存中的。
這也意味着我們的“塊設備”讀寫迅速、並且不存在磁盤之類設備通常面臨的尋道時間。
因此對這個“塊設備”而言,一個複雜的I/O調度器不但發揮不了絲毫作用,反而其本身將白白耗掉不少內存和CPU。
同樣的情況還出現在固態硬盤、U盤、記憶棒之類驅動中。將來固態硬盤流行之時,大概就是I/O調度器消亡之日了。
這裏我們試圖給我們的塊設備驅動選擇一個最簡單的I/O調度器。
目前linux中包含anticipatory、cfq、deadline和noop這4個I/O調度器。
2.6.18之前的linux默認使用anticipatory,而之後的默認使用cfq。
關於這4個調度器的原理和特性我們不打算在這裏介紹,原因是相關的介紹滿網都是。
但我們還是不能避免在這裏提及一下noop調度器,因爲我們馬上要用到它。
noop顧名思義,是一個基本上不幹事的調度器。它基本不對請求進行什麼附加的處理,僅僅假惺惺地告訴通用塊設備層:我處理完了。
但與吃空餉的公僕不同,noop的存在還是有不少進步意義的。至少我們現在就需要一個不要沒事添亂的I/O調度器。
選擇一個指定的I/O調度器需要這個函數:
int elevator_init(struct request_queue *q, char *name);
q是請求隊列的指針,name是需要設定的I/O調度器的名稱。
如果name爲NULL,那麼內核會首先嚐試選擇啓動參數"elevator="中指定的調度器,
不成功的話就去選擇編譯內核時指定的默認調度器,
如果運氣太背還是不成功,就去選擇"noop"調度器。
不要問我怎麼知道的,一切皆在RTFSC(Read The Fucking Source Code Fucking)。
對於我們的代碼,就是在g_blkdev_queue = blk_init_queue(blkdev_do_request,NULL);後面加上:
elevator_init(g_blkdev_queue, "noop")
但問題是在blk_init_queue()函數中系統已經幫我們申請一個了,因此這裏我們需要費點周折,把老的那個送回去。
所以我們的代碼應該是:
initialization_function()函數開頭處:
struct elevator_queue *old_e;
blk_init_queue()函數之後:
old_e = g_blkdev_queue->elevator;
if (IS_ERR_VALUE(elevator_init(g_blkdev_queue, "noop")))
printk(KERN_WARNING "Switch elevator failed, using default\n");
else
elevator_exit(old_e);
爲方便閱讀並提高本文在google磁盤中的佔用率,我們給出修改後的整個initialization_function()函數:
static int __init initialization_function(void)
{
int ret = 0;
struct elevator_queue *old_e;
printk(KERN_WARNING "register_blkdev\n");
MAJOR_NR = register_blkdev(0, BLK_DISK_NAME);
if(MAJOR_NR < 0){
return -1;
}
printk(KERN_WARNING "blk_init_queue\n");
g_blkdev_queue = blk_init_queue(blkdev_do_request,NULL);
if(NULL == g_blkdev_queue){
ret = -ENOMEM;
goto err_init_queue;
}
old_e = g_blkdev_queue->elevator;
if (IS_ERR_VALUE(elevator_init(g_blkdev_queue, "noop")))
printk(KERN_WARNING "Switch elevator failed, using default\n");
else
elevator_exit(old_e);
printk(KERN_WARNING "alloc_disk\n");
g_blkdev_disk = alloc_disk(1);
if(NULL == g_blkdev_disk){
ret = -ENOMEM;
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, SIMP_BLKDEV_BYTES>>9);
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_init_queue:
unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);
return ret;
}
本章的改動很小,我們現在測試一下這段代碼:
首先我們像原先那樣編譯模塊並加載:
# 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
#
然後看一看咱們的這個塊設備現在使用的I/O調度器:
# cat /sys/block/simp_blkdev/queue/scheduler
[noop] anticipatory deadline cfq
#
看樣子是成功了。
哦,上一章中忘了看老程序的調度器信息了,這裏補上老程序的情況:
# cat /sys/block/simp_blkdev/queue/scheduler
noop anticipatory deadline [cfq]
#
OK,我們完成簡單的一章,並且用事實說明了作者並沒有在開頭撒謊。
當然,作者也會力圖讓接下來的章節同樣比小說易讀。
<未完,待續>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/genhd.h> //add_disk
#include <linux/blkdev.h> //struct block_device_operations
#define _DEBUG_
#define BLK_DISK_NAME "block_name"
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
#define SIMP_BLKDEV_BYTES (16*1024*1024)
static int MAJOR_NR = 0;
static struct gendisk *g_blkdev_disk;
static struct request_queue *g_blkdev_queue;
unsigned char blkdev_data[SIMP_BLKDEV_BYTES];
struct block_device_operations fop = {
.owner = THIS_MODULE,
};
static void blkdev_do_request(struct request_queue *q)
{
struct request *req;
req = blk_fetch_request(q);
while ( NULL != req ) {
int err = 0;
if ((blk_rq_pos(req) + blk_rq_cur_sectors(req)) << 9
> SIMP_BLKDEV_BYTES) {
printk(KERN_ERR BLK_DISK_NAME
": bad request: block=%llu, count=%u\n",
(unsigned long long)blk_rq_pos(req),
blk_rq_cur_sectors(req));
err = -EIO;
goto done;
}
switch (rq_data_dir(req)) {
case READ:
memcpy(req->buffer,
blkdev_data + (blk_rq_pos(req) << 9),
blk_rq_cur_sectors(req) << 9);
break;
case WRITE:
memcpy(blkdev_data + (blk_rq_pos(req) << 9),
req->buffer,
blk_rq_cur_sectors(req) << 9);
break;
default:
break;
}
done:
if(!__blk_end_request_cur(req, err))
req = blk_fetch_request(q);
}
}
static int __init initialization_function(void)
{
int ret = 0;
struct elevator_queue *old_e;
printk(KERN_WARNING "register_blkdev\n");
MAJOR_NR = register_blkdev(0, BLK_DISK_NAME);
if(MAJOR_NR < 0){
return -1;
}
printk(KERN_WARNING "blk_init_queue\n");
g_blkdev_queue = blk_init_queue(blkdev_do_request,NULL);
if(NULL == g_blkdev_queue){
ret = -ENOMEM;
goto err_init_queue;
}
old_e = g_blkdev_queue->elevator;
if (IS_ERR_VALUE(elevator_init(g_blkdev_queue, "noop")))
printk(KERN_WARNING "Switch elevator failed, using default\n");
else
elevator_exit(old_e);
printk(KERN_WARNING "alloc_disk\n");
g_blkdev_disk = alloc_disk(1);
if(NULL == g_blkdev_disk){
ret = -ENOMEM;
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, SIMP_BLKDEV_BYTES>>9);
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_init_queue:
unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);
return ret;
}
static void __exit cleanup_function(void)
{
del_gendisk(g_blkdev_disk); //->add_disk
put_disk(g_blkdev_disk); //->alloc_disk
blk_cleanup_queue(g_blkdev_queue); //->blk_init_queue
unregister_blkdev(MAJOR_NR, BLK_DISK_NAME);//->register_blkdev
}
module_init(initialization_function);
module_exit(cleanup_function);
MODULE_AUTHOR("LvApp");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("A simple block module");
MODULE_ALIAS("block");
本人是在參考教程之後修改的教程內容.如有不同.可能有遺漏沒有修改.造成對讀者的迷惑,在此致歉~~