[快速上手Linux設(shè)備驅(qū)動]之塊設(shè)備驅(qū)動流程詳解一walfred已經(jīng)在[快速上手Linux設(shè)備驅(qū)動]之我看字符設(shè)備驅(qū)動一 文中詳細講解了linux下字符設(shè)備驅(qū)動,,并緊接著用四篇文章描述了Linux的設(shè)備模型,分別是總線,、設(shè)備,、驅(qū)動以及是類子系統(tǒng),。為什么要在現(xiàn)在才開始 講解塊設(shè)備驅(qū)動呢,,這里面是有原因,當初walfred自己學(xué)習(xí)時,,是先看的塊設(shè)備驅(qū)動然后才是linux設(shè)備模型,,導(dǎo)致理解上面有了點偏差,,現(xiàn)在我先將 linux設(shè)備模型拋出之后,,再敘述下linux下的塊設(shè)備驅(qū)動。 [快速上手Linux設(shè)備驅(qū)動]之塊設(shè)備驅(qū)動流程分兩篇文章講解,,分別是: 1,、[快速上手Linux設(shè)備驅(qū)動]之塊設(shè)備驅(qū)動流程詳解一 即是本文,主要講解塊設(shè)備去字符設(shè)備的區(qū)別以及塊設(shè)備驅(qū)動中牽涉到的幾個重要的結(jié)構(gòu)體,。 2,、[快速上手Linux設(shè)備驅(qū)動]之塊設(shè)備驅(qū)動流程詳解二 講述了一個塊設(shè)備驅(qū)動的模板 1塊設(shè)備與字符設(shè)備的區(qū)別 1.1從字面上理解,塊設(shè)備和字符設(shè)備最大的區(qū)別在于讀寫數(shù)據(jù)的基本單元不同,。塊設(shè)備讀寫數(shù)據(jù)的基本單元為塊,,例如磁盤通常為一個sector(扇區(qū)),而字符設(shè)備的基本單元為字節(jié),。所以Linux中塊設(shè)備驅(qū)動往往為磁盤設(shè)備的驅(qū)動,,但是由于磁盤設(shè)備的 IO性能與CPU相比很差,因此,,塊設(shè)備的數(shù)據(jù)流往往會引入文件系統(tǒng)的Cache機制,。 1.2從實現(xiàn)角度來看,Linux為塊設(shè)備和字符設(shè)備提供了兩套機制,。字符設(shè)備實現(xiàn)的比較簡 單,,內(nèi)核例程和用戶態(tài)API一一對應(yīng),用戶層的Read函數(shù)直接對應(yīng)了內(nèi)核中的Read例程,,這種映射關(guān)系由字符設(shè)備的file_operations維 護,。塊設(shè)備接口相對于字符設(shè)備復(fù)雜,read,、write API沒有直接到塊設(shè)備層,,而是直接到文件系統(tǒng)層,然后再由文件系統(tǒng)層發(fā)起讀寫請求,。 2相關(guān)結(jié)構(gòu)體 2.1 block_device_operations 與字符設(shè)備驅(qū)動程序一樣,,塊設(shè)備驅(qū)動程序也包含一個在<linux/fs.h>中定義的block_device_operations結(jié)構(gòu),其定義如下所示,。 struct block_device_operations { int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); long (*compat_ioctl) (struct file *, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, unsigned long *); int (*media_changed) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); struct module *owner; }; 從該結(jié)構(gòu)的定義中,,可以看出塊設(shè)備并不提供read()、write()等函數(shù)接口,。對塊設(shè)備的讀寫請求都是以異步方式發(fā)送到設(shè)備相關(guān)的request 隊列之中,。 關(guān)于block_device_operations,,walfred曾在[快速上手Linux設(shè)備驅(qū)動]之一切皆是文件思想一文中有詳細敘述。 2.2 gendisk 一個塊設(shè)備物理實體由一個gendisk結(jié)構(gòu)體來表示(在</linux/genhd.h>中定義),,每個gendisk可以支持多個分區(qū),。 每個gendisk中包含了本物理實體的全部信息以及操作函數(shù)接口,。整個塊設(shè)備的注冊過程是圍繞gendisk來展開的,。在驅(qū)動程序中需要初始化的gendisk的一些成員如下所示。 struct gendisk { int major; /* 主設(shè)備號 */ int first_minor; /* 第一個次設(shè)備號 */ int minors; /* 次設(shè)備號個數(shù),,一個塊設(shè)備至少需要使用一個次設(shè)備號,而且塊設(shè) 備的每個分區(qū)都需要一個次設(shè)備號,,因此這個成員等于1,,則表明該塊 設(shè)備是不可被分區(qū)的,,否則可以包含minors – 1 個分區(qū),。*/ char disk_name[32]; /* 塊設(shè)備名稱,,在/proc/partions中顯示 */ struct hd_struct **part; /* 分區(qū)表 */ struct block_device_operations *fops; /* 塊設(shè)備操作接口,,與字符設(shè)備的 file_operations結(jié)構(gòu)對應(yīng)*/ struct request_queue *queue; /* I/O請求隊列 */ void *private_data; /* 指向驅(qū)動程序私有數(shù)據(jù) */ sector_t capacity; /* 塊設(shè)備可包含的扇區(qū)數(shù) */ …… /* 其他省略 */ }; 2.3 request_queue 和 request request和request_queue結(jié)構(gòu)體:Linux塊設(shè)備驅(qū)動中,使用request結(jié)構(gòu)體來表征等待進行的IO請求;并用request_queue來表征一個塊IO請求隊列.兩個結(jié)構(gòu)體的定義如下: request結(jié)構(gòu)體 struct request{ struct list_head queuelist; unsigned long flags; sector_t sector;/*要傳輸?shù)南乱粋€扇區(qū)*/ unsigned long nr_sectors;/*要傳送的扇區(qū)數(shù)目*/ unsigned int current_nr_sector;/*當前要傳送的扇區(qū)*/ sector_t hard_sector;/*要完成的下一個扇區(qū)*/ unsigned long hard_nr_sectors;/*要被完成的扇區(qū)數(shù)目*/ unsigned int hard_cur_sectors;/*當前要被完成的扇區(qū)數(shù)目*/ struct bio* bio;/*請求的bio結(jié)構(gòu)體的鏈表*/ struct bio* biotail;/*請求的bio結(jié)構(gòu)體的鏈表尾*/
/*請求在屋里內(nèi)存中占據(jù)的不連續(xù)的段的數(shù)目*/ unsigned short nr_phys_segments; unsigned short nr_hw_segments; int tag; char* buffer;/*傳送的緩沖區(qū),內(nèi)核的虛擬地址*/ int ref_count;/*引用計數(shù)*/ ... }; 說明: request結(jié)構(gòu)體的主要成員包括: sector_t hard_sector;/*要完成的下一個扇區(qū)*/ unsigned long hard_nr_sectors;/*要被完成的扇區(qū)數(shù)目*/ unsigned int hard_cur_sectors;/*當前要被完成的扇區(qū)數(shù)目*/ /* * 上述三個成員依次是第一個尚未傳輸?shù)纳葏^(qū),尚待完成的扇區(qū)數(shù),當前IO操作中待完成的扇區(qū)數(shù) * 但驅(qū)動中一般不會用到他們.而是下面的一組成員. */ sector_t sector;/*要傳輸?shù)南乱粋€扇區(qū)*/ unsigned long nr_sectors;/*要傳送的扇區(qū)數(shù)目*/ unsigned int current_nr_sector;/*當前要傳送的扇區(qū)*/ /* * 這三個成員,以字節(jié)為單位.如果硬件的扇區(qū)大小不是512字節(jié).如字節(jié),則在開始對硬件進行操作之 * 前,應(yīng)先用4來除起始扇區(qū)號.前三個成員,與后三個成員的關(guān)系可以理解為"副本". */
關(guān)于unsigned short nr_phys_segments:該成員表示相鄰的頁被合并后,這個請求在物理內(nèi)存中的段的數(shù)目.如果該設(shè)備支持SG(分散/聚合,scatter/gather),可根據(jù)該字段申請sizeof(scatterlist*) nr_phys_segments的內(nèi)存,并使用下面的函數(shù)進行DMA映射: int blk_rq_map_sg(request_queue_t* q, struct request* rq, struct scatterlist *sg); 該函數(shù)與dma_map_sg()類似,返回scatterlist列表入口的數(shù)量. 關(guān)于struct list_head queuelist:該成員用于鏈接這個請求到請求隊列的鏈表結(jié)構(gòu),函數(shù)blkdev_ dequeue_request()可用于從隊列中移除請求.宏rq_data_dir(struct request* req)可獲得數(shù)據(jù)傳送方向.返回0表示從設(shè)備讀取,否則表示寫向設(shè)備. 2.4 request_queue請求隊列 struct request_queue{ ... /*自旋鎖,保護隊列結(jié)構(gòu)體*/ spinlock_t __queue_lock; spinlock_t* queue_lock; struct kobject kobj;/*隊列kobject*/ /*隊列設(shè)置*/ unsigned long nr_requests;/*最大的請求數(shù)量*/ unsigned int nr_congestion_on; unsigned int nr_congestion_off; unsigned int nr_batching; unsigned short max_sectors;/*最大扇區(qū)數(shù)*/ unsigned short max_hw_sectors; unsigned short max_phys_sectors;/*最大的段數(shù)*/ unsigned short max_hw_segments; unsigned short hardsect_size;/*硬件扇區(qū)尺寸*/ unsigned int max_segment_size;/*最大的段尺寸*/ unsigned long seg_boundary_mask;/*段邊界掩碼*/ unsigned int dma_alignment;/*DMA傳送內(nèi)存對齊限制*/ struct blk_queue_tag* queue_tags; atomic_t refcnt;/*引用計數(shù)*/ unsigned int in_flight; unsigned int sg_timeout; unsigned int sg_reserved_size; int node; struct list_head drain_list; struct request* flush_rq; unsigned char ordered; }; 說明:請求隊列跟蹤等候的塊IO請求,它存儲用于描述這個設(shè)備能夠支持的請求的類型信息,他們的最大大小,多少不同的段可以進入一個請求,硬件扇區(qū)大小,對齊要求等參數(shù).其結(jié)果是:如果請求隊列被配置正確了,它不會交給該設(shè)備一個不能處理的請求. 請求隊列還要實現(xiàn)一個插入接口,這個接口允許使用多個IO調(diào)度器,IO調(diào)度器以最優(yōu)性能的方式向驅(qū)動提交IO請求.大部分IO調(diào)度器是積累批量的IO請求,并將其排列為遞增/遞減的塊索引順序后,提交給驅(qū)動.另外,IO調(diào)度器還負責合并鄰近的請求,當一個新的IO請求被提交給調(diào)度器后,它會在隊列里搜尋包含鄰近的扇區(qū)的請求.如果找到一個,并且請求合理,調(diào)度器會將這兩個請求合并. 2.5塊I/O 通常一個bio對應(yīng)一個IO請求.IO調(diào)度算法可將連續(xù)的bio合并成一個請求.所以一個請求包含多個bio. struct bio{ sector_t bi_sector;/*要傳送的第一個扇區(qū)*/ struct bio* bi_next;/*下一個bio*/ struct block_device* bi_bdev; unsigned long bi_flags; /*如果是一個寫請求,最低有效位被置位,可使用bio_data_dir(bio)宏來獲取讀寫方向*/ unsigned long bi_rw;/*地位表示R/W方向,高位表示優(yōu)先級*/ unsigned short bi_vcnt;/*bio_vec數(shù)量*/ unsigned short bi_idx; /*當前bvl_vec索引*/ unsigned short bi_phys_segments;/*不相鄰的物理段的數(shù)目*/ unsigned short bi_hw_segments;/*物理合并和DMA remap合并后不相鄰的物理扇區(qū)*/ unsigned int bi_size; /*被傳送的數(shù)據(jù)大小(byte),用bio_sector(bio)獲取扇區(qū)為單位的大小*/ /*為了明了最大的hw尺寸,考慮bio中第一個和最后一個虛擬的可合并的段的尺寸*/ unsigned int bi_hw_front_size; unsigned int bi_hw_back_size; unsigned int bi_max_vecs;/*能持有的最大bvl_vecs數(shù)*/ struct bio_vec* bio_io_vec;/*實際的vec列表*/ bio_end_io_t* bio_end_io; atomic_t bi_cnt; void* bi_private; bio_destructor_t* bi_destructor; }; //結(jié)構(gòu)體包含三個成員 struct bio_vec{ struct page* bv_page;//頁指針 unsigned int bv_len;//傳送的字節(jié)數(shù) unsigned int bv_offset;//偏移位置 };
/*一般不直接訪問bio的bio_vec成員,而使用bio_for_each_segment()宏進行操作. *該宏循環(huán)遍歷整個bio中的每個段. */ #define __bio_for_each_segment(bvl, bio, i, start_idx)\ for( bvl = bio_iovec_idx((bio),(start_idx)),i = (start_idx);\ i <(bio)->bi_vcnt;\ bvl++, i++\ ) #define bio_for_each_segment(bvl, bio, i)\ __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx) 在內(nèi)核中,提供了一組函數(shù)(宏)用于操作bio: int bio_data_dir(struct bio* bio); 該函數(shù)用于獲得數(shù)據(jù)傳送方向. struct page* bio_page(struct bio* bio); 該函數(shù)用于獲得目前的頁指針. int bio_offset(struct bio* bio); 該函數(shù)返回操作對應(yīng)的當前頁的頁內(nèi)偏移,通常塊IO操作本身就是頁對齊的. int bio_cur_sectors(struct bio* bio); 該函數(shù)返回當前bio_vec要傳輸?shù)纳葏^(qū)數(shù). char* bio_data(struct bio* bio); 該函數(shù)返回數(shù)據(jù)緩沖區(qū)的內(nèi)核虛擬地址. char* bvec_kmap_irq(struct bio_vec* bvec, unsigned long* offset); 該函數(shù)也返回一個內(nèi)核虛擬地址此地址可用于存取被給定的bio_vec入口指向的數(shù)據(jù)緩沖區(qū).同時會屏蔽中斷并返回一個原子kmap,因此,在此函數(shù)調(diào)用之前,驅(qū)動不應(yīng)該是睡眠狀態(tài). void bvec_kunmap_irq(char* buffer, unsigned long flags); 該函數(shù)撤銷函數(shù)bvec_kmap_irq()創(chuàng)建的內(nèi)存映射. char* bio_kmap_irq(struct bio* bio, unsigned long* flags); 該函數(shù)是對bvec_kmap_irq函數(shù)的封裝,它返回給定的比偶的當前bio_vec入口的映射. char* __bio_kmap_atomic(struct bio* bio, int i, enum km_type type); 該函數(shù)是通過kmap_atomic()獲得返回給定bio的第i個緩沖區(qū)的虛擬地址. void __bio_kunmap_atomic(char* addr, enum km_type type); 該函數(shù)返還由函數(shù)__bio_kmap_atomic()獲得的內(nèi)核虛擬地址給系統(tǒng). void bio_get(struct bio* bio); void bio_put(struct bio* bio); 上面兩個函數(shù)分別完成對bio的引用和引用釋放. 下圖可以體現(xiàn)出bio/request/request_queue/bio_vec四個結(jié)構(gòu)體之間的關(guān)系.
1
|
|