嵌入式Linux設(shè)備驅(qū)動開發(fā)之:塊設(shè)備驅(qū)動編程
掃描二維碼
隨時隨地手機(jī)看文章
塊設(shè)備通常指一些需要以塊(如512字節(jié))的方式寫入的設(shè)備,如IDE硬盤、SCSI硬盤、光驅(qū)等。它的驅(qū)動程序的編寫過程與字符型設(shè)備驅(qū)動程序的編寫有很大的區(qū)別。
塊設(shè)備驅(qū)動編程接口相對復(fù)雜,不如字符設(shè)備明晰易用。塊設(shè)備驅(qū)動程序?qū)φ麄€系統(tǒng)的性能影響較大,速度和效率是設(shè)計(jì)塊設(shè)備驅(qū)動程要重點(diǎn)考慮的問題。系統(tǒng)中使用緩沖區(qū)與訪問請求的優(yōu)化管理(合并與重新排序)來提高系統(tǒng)性能。
1.編程流程說明塊設(shè)備驅(qū)動程序的編寫流程同字符設(shè)備驅(qū)動程序的編寫流程很類似,也包括了注冊和使用兩部分。但與字符驅(qū)動設(shè)備所不同的是,塊設(shè)備驅(qū)動程序包括一個request請求隊(duì)列。它是當(dāng)內(nèi)核安排一次數(shù)據(jù)傳輸時在列表中的一個請求隊(duì)列,以最大化系統(tǒng)性能為原則進(jìn)行排序。在后面的讀寫操作時會詳細(xì)講解這個函數(shù),圖11.5為塊設(shè)備驅(qū)動程序的流程圖,請讀者注意與字符設(shè)備驅(qū)動程序的區(qū)別。
圖11.5塊設(shè)備驅(qū)動程序流程圖
2.重要數(shù)據(jù)結(jié)構(gòu)每個塊設(shè)備物理實(shí)體由一個gendisk結(jié)構(gòu)體來表示(在</linux/genhd.h>中定義),每個gendisk可以支持多個分區(qū)。
每個gendisk中包含了本物理實(shí)體的全部信息以及操作函數(shù)接口。整個塊設(shè)備的注冊過程是圍繞gendisk來展開的。在驅(qū)動程序中需要初始化的gendisk的一些成員如下所示。
structgendisk
{
intmajor;/*主設(shè)備號*/
intfirst_minor;/*第一個次設(shè)備號*/
intminors;/*次設(shè)備號個數(shù),一個塊設(shè)備至少需要使用一個次設(shè)備號,而且塊設(shè)
備的每個分區(qū)都需要一個次設(shè)備號,因此這個成員等于1,則表明該塊
設(shè)備是不可被分區(qū)的,否則可以包含minors–1個分區(qū)。*/
chardisk_name[32];/*塊設(shè)備名稱,在/proc/partions中顯示*/
structhd_struct**part;/*分區(qū)表*/
structblock_device_operations*fops;/*塊設(shè)備操作接口,與字符設(shè)備的
file_operations結(jié)構(gòu)對應(yīng)*/
structrequest_queue*queue;/*I/O請求隊(duì)列*/
void*private_data;/*指向驅(qū)動程序私有數(shù)據(jù)*/
sector_tcapacity;/*塊設(shè)備可包含的扇區(qū)數(shù)*/
……/*其他省略*/
};
與字符設(shè)備驅(qū)動程序一樣,塊設(shè)備驅(qū)動程序也包含一個在<linux/fs.h>中定義的block_device_operations結(jié)構(gòu),其定義如下所示。
structblock_device_operations
{
int(*open)(structinode*,structfile*);
int(*release)(structinode*,structfile*);
int(*ioctl)(structinode*,structfile*,unsigned,unsignedlong);
long(*unlocked_ioctl)(structfile*,unsigned,unsignedlong);
long(*compat_ioctl)(structfile*,unsigned,unsignedlong);
int(*direct_access)(structblock_device*,sector_t,unsignedlong*);
int(*media_changed)(structgendisk*);
int(*revalidate_disk)(structgendisk*);
int(*getgeo)(structblock_device*,structhd_geometry*);
structmodule*owner;
};
從該結(jié)構(gòu)的定義中,可以看出塊設(shè)備并不提供read()、write()等函數(shù)接口。對塊設(shè)備的讀寫請求都是以異步方式發(fā)送到設(shè)備相關(guān)的request隊(duì)列之中。
3.塊設(shè)備注冊和初始化塊設(shè)備的初始化過程要比字符設(shè)備復(fù)雜,它既需要像字符設(shè)備一樣在加載內(nèi)核時完成一定的工作,還需要在內(nèi)核編譯時增加一些內(nèi)容。塊設(shè)備驅(qū)動程序初始化時,由驅(qū)動程序的init()完成。
塊設(shè)備的初始化過程如圖11.6所示。
圖11.6塊設(shè)備驅(qū)動程序初始化過程
(1)向內(nèi)核注冊。
使用register_blkdev()函數(shù)對設(shè)備進(jìn)行注冊。
intregister_blkdev(unsignedintmajor,constchar*name);
其中參數(shù)major為要注冊的塊設(shè)備的主設(shè)備號,如果其值等于0,則系統(tǒng)動態(tài)分配并返回主設(shè)備號。參數(shù)name為設(shè)備名,在/proc/devices中顯示。如果出錯,則該函數(shù)返回負(fù)值。
與其對應(yīng)的塊設(shè)備的注銷函數(shù)為unregister_blkdev(),其格式如下所示。
intunregister_blkdev(unsignedintmajor,constchar*name);
其參數(shù)必須與注冊函數(shù)中的參數(shù)相同。如果出錯則返回負(fù)值。
(2)申請并初始化請求隊(duì)列。
這一步要調(diào)用blk_init_queue()函數(shù)來申請并初始化請求隊(duì)列,其格式如下所示。
structrequest_queue*blk_init_queue(request_fn_proc*rfn,spinlock_t*lock)
其中參數(shù)rfn是請求隊(duì)列的處理函數(shù)指針,它負(fù)責(zé)執(zhí)行塊設(shè)備的讀、寫請求。參數(shù)lock為自旋鎖,用于控制對所分配的隊(duì)列的訪問。
(3)初始化并注冊gendisk結(jié)構(gòu)。
內(nèi)核提供的gendisk結(jié)構(gòu)相關(guān)函數(shù)如表11-16所示。
表11-16 gendisk結(jié)構(gòu)相關(guān)函數(shù)
函數(shù)格式
說明
structgendisk*alloc_disk(intminors)
動態(tài)分配gendisk結(jié)構(gòu),參數(shù)為次設(shè)備號的個數(shù)
voidadd_disk(structgendisk*disk)
向系統(tǒng)注冊gendisk結(jié)構(gòu)
voiddel_gendisk(structgendisk*disk)
從系統(tǒng)注銷gendisk結(jié)構(gòu)
首先使用alloc_disk()函數(shù)動態(tài)分配gendisk結(jié)構(gòu),接下來,對gendisk結(jié)構(gòu)的主設(shè)備號(major)、次設(shè)備號相關(guān)成員(first_minor和minors)、塊設(shè)備操作函數(shù)(fops)、請求隊(duì)列(queue)、可包含的扇區(qū)數(shù)(capacity)以及設(shè)備名稱(disk_name)等成員進(jìn)行初始化。
在完成對gendisk的分配和初始化之后,調(diào)用add_disk()函數(shù)向系統(tǒng)注冊塊設(shè)備。在卸載gendisk結(jié)構(gòu)的時候,要調(diào)用del_gendisk()函數(shù)。
4.塊設(shè)備請求處理塊設(shè)備驅(qū)動中一般要實(shí)現(xiàn)一個請求隊(duì)列處理函數(shù)來處理隊(duì)列中的請求。從塊設(shè)備的運(yùn)行流程,可知請求處理是塊設(shè)備的基本處理單位,也是最核心的部分。對塊設(shè)備的讀寫操作被封裝到了每一個請求中。
已經(jīng)提過調(diào)用blk_init_queue()函數(shù)來申請并初始化請求隊(duì)列。表11-17列出了一些與請求處理相關(guān)的函數(shù)。
表11-17 請求處理相關(guān)函數(shù)
函數(shù)格式
說明
request_queue_t*blk_alloc_queue(intgfp_mask)
分配請求隊(duì)列
request_queue_t*blk_init_queue
(request_fn_proc*rfn,spinlock_t*lock)
分配并初始化請求隊(duì)列
structrequest*blk_get_request
(request_queue_t*q,intrw,intgfp_mask)
從隊(duì)列中獲取一個請求
voidblk_requeue_request(request_queue_t*q,structrequest*rq)
將請求再次加入隊(duì)列
voidblk_queue_max_sectors
(request_queue_t*q,unsignedshortmax_sectors)
設(shè)置最大訪問扇區(qū)數(shù)
voidblk_queue_max_phys_segments
(request_queue_t*q,unsignedshortmax_segments)
設(shè)置最大物理段數(shù)
voidend_request(structrequest*req,intuptodate)
結(jié)束本次請求處理
voidblk_queue_hardsect_size
(request_queue_t*q,unsignedshortsize)
設(shè)置物理扇區(qū)大小
以上簡單地介紹了塊設(shè)備驅(qū)動編程的最基本的概念和流程。更深入的內(nèi)容不是本書的重點(diǎn),有興趣的讀者可以參考其他書籍。