上回最後面介紹了相關資料結構,下面再詳細介紹
塊裝置對象結構 block_device
核心用結構block_device執行個體代表一個塊裝置對象,如:整個硬碟或特定分區。如果該結構代表一個分區,則其成員bd_part指向裝置的分區結構。如果該結構代表裝置,則其成員bd_disk指向裝置的通用硬碟結構gendisk
當使用者打開塊裝置檔案時,核心建立結構block_device執行個體,裝置驅動程式還将建立結構gendisk執行個體,配置設定請求隊列并注冊結構block_device執行個體。
塊裝置對象結構block_device列出如下(在include/linux/fs.h中)
- struct block_device {
- dev_t bd_dev;
- struct inode * bd_inode;
- struct super_block * bd_super;
- int bd_openers;
- struct mutex bd_mutex;
- struct semaphore bd_mount_sem;
- struct list_head bd_inodes;
- void * bd_holder;
- int bd_holders;
- #ifdef CONFIG_SYSFS
- struct list_head bd_holder_list;
- #endif
- struct block_device * bd_contains;
- unsigned bd_block_size;
- struct hd_struct * bd_part;
- unsigned bd_part_count;
- int bd_invalidated;
- struct gendisk * bd_disk;
- struct list_head bd_list;
- struct backing_dev_info *bd_inode_backing_dev_info;
- unsigned long bd_private;
- int bd_fsfreeze_count;
- struct mutex bd_fsfreeze_mutex;
- };
通用硬碟結構 gendisk
結構體gendisk代表了一個通用硬碟(generic hard disk)對象,它存儲了一個硬碟的資訊,包括請求隊列、分區連結清單和塊裝置操作函數集等。塊裝置驅動程式配置設定結構gendisk執行個體,裝載分區表,配置設定請求隊列并填充結構的其他域。
支援分區的塊驅動程式必須包含 <linux/genhd.h> 頭檔案,并聲明一個結構gendisk,核心還維護該結構執行個體的一個全局連結清單gendisk_head,通過函數add_gendisk、del_gendisk和get_gendisk維護該連結清單。
結構gendisk列出如下(在include/linux/genhd.h中):
- struct gendisk {
- int major;
- int first_minor;
- int minors;
- char disk_name[32];
- struct hd_struct **part;
- struct block_device_operations *fops;
- struct request_queue *queue;
- struct blk_scsi_cmd_filter cmd_filter;
- void *private_data;
- sector_t capacity;
- int flags;
- struct device dev;
- struct kobject *holder_dir;
- struct kobject *slave_dir;
- struct timer_rand_state *random;
- int policy;
- atomic_t sync_io;
- unsigned long stamp;
- int in_flight;
- #ifdef CONFIG_SMP
- struct disk_stats *dkstats;
- #else
- struct disk_stats dkstats;
- #endif
- struct work_struct async_notify;
- #ifdef CONFIG_BLK_DEV_INTEGRITY
- struct blk_integrity *integrity;
- #endif
- };
Linux核心提供了一組函數來操作gendisk,主要包括:
配置設定gendisk
struct gendisk *alloc_disk(int minors);
minors 參數是這個磁盤使用的次裝置号的數量,一般也就是磁盤分區的數量,此後minors不能被修改。
增加gendisk
gendisk結構體被配置設定之後,系統還不能使用這個磁盤,需要調用如下函數來注冊這個磁盤裝置:
void add_disk(struct gendisk *gd);
特别要注意的是對add_disk()的調用必須發生在驅動程式的初始化工作完成并能響應磁盤的請求之後。
釋放gendisk
當不再需要一個磁盤時,應當使用如下函數釋放gendisk:
void del_gendisk(struct gendisk *gd);
設定gendisk容量
void set_capacity(struct gendisk *disk, sector_t size);
塊裝置中最小的可尋址單元是扇區,扇區大小一般是2的整數倍,最常見的大小是512位元組。扇區的大小是裝置的實體屬性,扇區是所有塊裝置的基本單元,塊裝置 無法對比它還小的單元進行尋址和操作,不過許多塊裝置能夠一次就傳輸多個扇區。雖然大多數塊裝置的扇區大小都是512位元組,不過其它大小的扇區也很常見, 比如,很多CD-ROM盤的扇區都是2K大小。不管實體裝置的真實扇區大小是多少,核心與塊裝置驅動互動的扇區都以512位元組為機關。是以,set_capacity()函數也以512位元組為機關。
分區結構hd_struct代表了一個分區對象,它存儲了一個硬碟的一個分區的資訊,驅動程式初始化時,從硬碟的分區表中提取分區資訊,存放在分區結構執行個體中。
塊裝置操作函數集結構 block_device_operations
字元裝置通過 file_operations 操作結構使它們的操作對系統可用. 一個類似的結構用在塊裝置上是 struct block_device_operations,
定義在 <linux/fs.h>.
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
就像它們的字元驅動對等體一樣工作的函數; 無論何時裝置被打開和關閉都調用它們. 一個字元驅動可能通過啟動裝置或者鎖住門(為可移出的媒體)來響應一個 open 調用. 如果你将媒體鎖入裝置, 你當然應當在 release 方法中解鎖.
int (*ioctl)(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg);
實作 ioctl 系統調用的方法. 但是, 塊層首先解釋大量的标準請求; 是以大部分的塊驅動 ioctl 方法相當短.
PS:在block_device_operations中沒有實際讀或寫資料的函數. 在塊 I/O 子系統, 這些操作由請求函數處理
請求結構request
結構request代表了挂起的I/O請求,每個請求用一個結構request執行個體描述,存放在請求隊列連結清單中,由電梯算法進行排序,每個請求包含1個或多個結構bio執行個體
- struct request {
- //用于挂在請求隊列連結清單的節點,使用函數blkdev_dequeue_request通路它,而不能直接訪
- 問
- struct list_head queuelist;
- struct list_head donelist;
- struct request_queue *q;
- unsigned int cmd_flags;
- enum rq_cmd_type_bits cmd_type;
- sector_t sector;
- sector_t hard_sector;
- unsigned long nr_sectors;
- unsigned long hard_nr_sectors;
- unsigned int current_nr_sectors;
- unsigned int hard_cur_sectors;
- struct bio *bio;
- union {
- struct rb_node rb_node;
- void *completion_data;
- };
request結構體的主要成員包括:
sector_t hard_sector;
unsigned long hard_nr_sectors;
unsigned int hard_cur_sectors;
上述3個成員辨別還未完成的扇區,hard_sector是第1個尚未傳輸的扇區,hard_nr_sectors是尚待完成的扇區數,hard_cur_sectors是并且目前I/O操作中待完成的扇區數。這些成員隻用于核心塊裝置層,驅動不應當使用它們。
sector_t sector;
unsigned long nr_sectors;
unsigned int current_nr_sectors;
驅動中會經常與這3個成員打交道,這3個成員在核心和驅動互動中發揮着重大作用。它們以512位元組大小為1個扇區,如果硬體的扇區大小不是512位元組,則需要進行相應的調整。例如,如果硬體的扇區大小是2048位元組,則在進行硬體操作之前,需要用4來除起始扇區号。
hard_sector、hard_nr_sectors、hard_cur_sectors與sector、nr_sectors、current_nr_sectors之間可認為是“副本”關系。
struct bio *bio;
bio是這個請求中包含的bio結構體的連結清單,驅動中不宜直接存取這個成員,而應該使用後文将介紹的rq_for_each_bio()。
請求隊列結構request_queue
每個塊裝置都有一個請求隊列,每個請求隊列單獨執行I/O排程,請求隊列是由請求結構執行個體連結成的雙向連結清單,連結清單以及整個隊列的資訊用結構request_queue描述,稱為請求隊列對象結構或請求隊列結構。它存放了關于挂起請求的資訊以及管理請求隊列(如:電梯算法)所需要的資訊。結構成員request_fn是來自裝置驅動程式的請求處理函數。
請求隊列結構request_queue列出如下(在/include/linux/blk_dev.h中)
太長了,此處略,其實也看不懂,- -#
Bio結構
通常1個bio對應1個I/O請求,IO排程算法可将連續的bio合并成1個請求。是以,1個請求可以包含多個bio。
核心中塊I/O操作的基本容器由bio結構體表示,定義 在<linux/bio.h>中,該結構體代表了正在現場的(活動的)以片段(segment)連結清單形式組織的塊I/O操作。一個片段是一小 塊連續的記憶體緩沖區。這樣的好處就是不需要保證單個緩沖區一定要連續。是以通過片段來描述緩沖區,即使一個緩沖區分散在記憶體的多個位置上,bio結構體也 能對核心保證I/O操作的執行,這樣的就叫做聚散I/O.
bio為通用層的主要資料結構,既描述了磁盤的位置,又描述了記憶體的位置,是上層核心vfs與下層驅動的連接配接紐帶
- struct bio {
- sector_t bi_sector;//該bio結構所要傳輸的第一個(512位元組)扇區:磁盤的位置
- struct bio *bi_next; //請求連結清單
- struct block_device *bi_bdev;//相關的塊裝置
- unsigned long bi_flags//狀态和指令标志
- unsigned long bi_rw; //讀寫
- unsigned short bi_vcnt;//bio_vesc偏移的個數
- unsigned short bi_idx; //bi_io_vec的目前索引
- unsigned short bi_phys_segments;//結合後的片段數目
- unsigned short bi_hw_segments;//重映射後的片段數目
- unsigned int bi_size; //I/O計數
- unsigned int bi_hw_front_size;//第一個可合并的段大小;
- unsigned int bi_hw_back_size;//最後一個可合并的段大小
- unsigned int bi_max_vecs; //bio_vecs數目上限
- struct bio_vec *bi_io_vec; //bio_vec連結清單:記憶體的位置
- bio_end_io_t *bi_end_io;//I/O完成方法
- atomic_t bi_cnt; //使用計數
- void *bi_private; //擁有者的私有方法
- bio_destructor_t *bi_destructor; //銷毀方法
- };
記憶體資料段結構bio_vec
結構bio_vec代表了記憶體中的一個資料段,資料段用頁、偏移和長度描
述。I/O需要執行的記憶體位置用段表示,結構bio指向了一個段的數組。
結構bio_vec列出如下(在include/linux/bio.h中):
struct bio_vec {
struct page *bv_page;
unsigned short bv_len;
unsigned short bv_offset;
};