上回最後面介紹了相關數據結構,下面再詳細介紹
塊設備對象結構 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; /* not a kdev_t - it's a search key */
struct inode * bd_inode; /* 分區節點 */
struct super_block * bd_super;
int bd_openers;
struct mutex bd_mutex;/* open/close 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;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
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; /*次設備號的最大數量,沒有分區的設備,此值爲1 */
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; /* 函數set_capacity設置的容量,以扇區爲單位*/
int flags; /*設置驅動器狀態的標誌,如:可移動介質爲
GENHD_FL_REMOVABLE*/
struct device dev; /*從設備驅動模型基類結構device繼承*/
struct kobject *holder_dir;
struct kobject *slave_dir;
struct timer_rand_state *random;
int policy;
atomic_t sync_io; /* RAID */
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; /*命令類型*/
/*各種各樣的扇區計數*/
/*爲提交i/o維護bio橫斷面的狀態信息,hard_*成員是塊層內部使用的,驅動程序不應該改變
它們*/
sector_t sector; /*將提交的下一個扇區*/
sector_t hard_sector; /* 將完成的下一個扇區*/
unsigned long nr_sectors; /* 整個請求還需要傳送的扇區數*/
unsigned long hard_nr_sectors; /* 將完成的扇區數*/
/*在當前bio中還需要傳送的扇區數 */
unsigned int current_nr_sectors;
/*在當前段中將完成的扇區數*/
unsigned int hard_cur_sectors;
struct bio *bio; /*請求中第一個未完成操作的bio*、
struct bio *biotail; /*請求鏈表中末尾的bio*、
struct hlist_node hash; /*融合 hash */
/* rb_node僅用在I/O調度器中,當請求被移到分發隊列中時,
請求將被刪除。因此,讓completion_data與rb_node分享空間*/
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; /*數據段頁內偏移*/
};
塊設備各個結構體間關係