一、塊設備簡介 塊設備驅動是存儲設備驅動,塊設備驅動相比字元設備驅動的主要區別如下: ①、塊設備只能以塊為單位進行讀寫訪問,塊是 linux 虛擬文件系統(VFS)基本的數據傳輸單位。字元設備是以位元組為單位進行數據傳輸的,不需要緩衝。 ②、塊設備在結構上是可以進行隨機訪問的,對於這些設備的讀寫都是按 ...
一、塊設備簡介
塊設備驅動是存儲設備驅動,塊設備驅動相比字元設備驅動的主要區別如下:
①、塊設備只能以塊為單位進行讀寫訪問,塊是 linux 虛擬文件系統(VFS)基本的數據傳輸單位。字元設備是以位元組為單位進行數據傳輸的,不需要緩衝。
②、塊設備在結構上是可以進行隨機訪問的,對於這些設備的讀寫都是按塊進行的,塊設備使用緩衝區來暫時存放數據,等到條件成熟以後再一次性將緩衝區中的數據寫入塊設備中。
二、塊設備驅動框架
1、註冊註銷塊設備
int register_blkdev(unsigned int major, const char *name) void unregister_blkdev(unsigned int major, const char *name)
major: 要註銷的塊設備主設備號。 name: 要註銷的塊設備名字。
2、申請和刪除磁碟設備
struct gendisk *alloc_disk(int minors)
minors: 次設備號數量, 也就是 gendisk 對應的分區數量。
void del_gendisk(struct gendisk *gp)
gp: 要刪除的 gendisk。
3、將 gendisk 添加到內核
void add_disk(struct gendisk *disk)
disk: 要添加到內核的 gendisk。
4、設置 gendisk 容量
void set_capacity(struct gendisk *disk, sector_t size)
disk: 要設置容量的 gendisk。 size: 磁碟容量大小,註意這裡是扇區數量。塊設備中最小的可定址單元是扇區,一個扇區一般是 512 位元組,有些設備的物理扇區可能不是 512 位元組。不管物理扇區是多少,內核和塊設備驅動之間的扇區都是 512 位元組。所以 set_capacity 函數設置的大小就是塊設備實際容量除以512 位元組得到的扇區數量。比如一個 2MB 的磁碟,其扇區數量就是(210241024)/512=4096。
5、調整 gendisk 引用計數
內核會通過 get_disk 和 put_disk 這兩個函數來調整 gendisk 的引用計數,根據名字就可以知道, get_disk 是增加 gendisk 的引用計數, put_disk 是減少 gendisk 的引用計數。
truct kobject *get_disk(struct gendisk *disk) void put_disk(struct gendisk *disk)
6、塊設備操作集
struct block_device_operations { int (*open) (struct block_device *, fmode_t); void (*release) (struct gendisk *, fmode_t); int (*rw_page)(struct block_device *, sector_t, struct page *,int rw); int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long); long (*direct_access)(struct block_device *, sector_t,void **, unsigned long *pfn, long size); unsigned int (*check_events) (struct gendisk *disk,unsigned int clearing); /* ->media_changed() is DEPRECATED, use ->check_events() instead */ int (*media_changed) (struct gendisk *); void (*unlock_native_capacity) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); /* this callback is with swap_lock and sometimes page table lock held */ void (*swap_slot_free_notify) (struct block_device *,unsigned long); struct module *owner; };
塊設備數據讀寫過程:
(1)、請求隊列 request_queue
①、初始化請求隊列
我們首先需要申請並初始化一個 request_queue,然後在初始化 gendisk 的時候將這個request_queue 地址賦值給 gendisk 的 queue 成員變數。使用 blk_init_queue 函數來完成request_queue 的申請與初始化。
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
rfn: 請求處理函數指針,每個 request_queue 都要有一個請求處理函數,請求處理函數request_fn_proc
void (request_fn_proc) (struct request_queue *q)
lock: 自旋鎖指針,需要驅動編寫人員定義一個自旋鎖,然後傳遞進來。,請求隊列會使用這個自旋鎖。
②、刪除請求隊列
當卸載塊設備驅動的時候我們還需要刪除掉前面申請到的 request_queue,刪除請求隊列使用函數 blk_cleanup_queue
void blk_cleanup_queue(struct request_queue *q)
q: 需要刪除的請求隊列。
③、分配請求隊列並綁定製造請求函數
blk_init_queue 函數完成了請求隊列的申請以及請求處理函數的綁定,這個一般用於像機械硬碟這樣的存儲設備,需要 I/O 調度器來優化數據讀寫過程。但是對於 EMMC、 SD 卡這樣的非機械設備,可以進行完全隨機訪問,所以就不需要複雜的 I/O 調度器了。對於非機械設備我們可以先申請 request_queue,然後將申請到的 request_queue 與“製造請求”函數綁定在一起。
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
gfp_mask: 記憶體分配掩碼
2、 請求 request
請求隊列(request_queue)裡面包含的就是一系列的請求(request), request 是一個結構體, 需要從 request_queue 中取出一個一個的 request,然後再從每個 request 裡面取出 bio,最後根據 bio 的描述講數據寫入到塊設備,或者從塊設備中讀取數據。
①、 獲取請求
request *blk_peek_request(struct request_queue *q)
q: 指定 request_queue。 返回值: request_queue 中下一個要處理的請求(request),如果沒有要處理的請求就返回NULL。
②、開啟請求
void blk_start_request(struct request *req)
③、一步到位處理請求
blk_fetch_request 函數來一次性完成請求的獲取和開啟
struct request *blk_fetch_request(struct request_queue *q) { struct request *rq; rq = blk_peek_request(q); if (rq) blk_start_request(rq); return rq; }
3、 bio 結構
每個 request 裡面會有多個 bio, bio 保存著最終要讀寫的數據、地址等信息。上層應用程式對於塊設備的讀寫會被構造成一個或多個 bio 結構, bio 結構描述了要讀寫的起始扇區、要讀寫的扇區數量、是讀取還是寫入、頁偏移、數據長度等等信息。上層會將 bio 提交給 I/O 調度器,I/O 調度器會將這些 bio 構造成 request 結構,而一個物理存儲設備對應一個 request_queue,request_queue 裡面順序存放著一系列的 request。新產生的 bio 可能被合併到 request_queue 里現有的 request 中,也可能產生新的 request,然後插入到 request_queue 中合適的位置,這一切都是由 I/O 調度器來完成的。