Linux块设备驱动总结
生活随笔
收集整理的這篇文章主要介紹了
Linux块设备驱动总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
《Linux設備驅動程序》第十六章 塊設備驅動程序讀書筆記
簡介
一個塊設備驅動程序主要通過傳輸固定大小的隨機數據來訪問設備
Linux內核視塊設備為與字符設備相異的基本設備類型
Linux塊設備驅動程序接口使得塊設備可以發揮其最大的功效,但是其復雜程序又是編程者必須面對的一
個問題
一個數據塊指的是固定大小的數據,而大小的值由內核確定
數據塊的大小通常是4096個字節,但是可以根據體系結構和所使用的文件系統進行改變
與數據塊對應的是扇區,它是由底層硬件決定大小的一個塊,內核所處理的設備扇區大小是512字節
如果要使用不同的硬件扇區大小,用戶必須對內核的扇區數做相應的修改
注冊
注冊塊設備驅動程序
<linux/fs.h>
int register_blkdev(unsigned int major, const char *name);
如果需要的話分配一個動態的主設備號
在/proc/devices中創建一個入口項
int unregister_blkdev(unsigned int major, const char *name);
注冊磁盤
struct block_device_operations
int (*open) (struct inode *inode, struct file *filp);
int (*release) (struct inode *inode, struct file *filp);
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
int (*media_changed) (struct gendisk *gd);
int (*revalidate_disk) (struct gendisk *gd);
struct module *owner;
gendisk結構
<linux/genhd.h>
struct gendisk
int major;
int first_minor;
int minors;
常取16
char disk_name[32]
顯示在/proc/partitions和sysfs中
struct block_device_operations *fops;
struct request_queue *queue;
int flags;
sector_t capacity;
void *private_data;
struct gendisk *alloc_disk(int minors);
void del_gendisk(struct gendisk *gd);
void add_disk(struct gendisk *gd);
塊設備操作
open和release函數
對于那些操作實際硬件設備的驅動程序,open和release函數可以設置驅動程序和硬件的狀態。這些操作
包括使磁盤開始或者停止旋轉,鎖住可移動介質的倉門以及分配DMA緩存等
有一些操作能夠讓塊設備在用戶空間內被直接打開,這些操作包括給磁盤分區,或者在分區上創建文件
系統,或者運行文件系統檢查程序
對可移動介質的支持
調用media_changed函數以檢查介質是否被改變
在介質改變后將調用revalideate函數
ioctl函數
高層的塊設備子系統在驅動程序獲得ioctl命令前,已經截取了大量的命令
實際上在一個現代驅動程序中,許多ioctl命令根本就不用實現
請求處理
每個塊設備驅動程序的核心是它的請求函數
request函數介紹
void request(request_queue_t *queue);
當內核需要驅動程序處理讀取、寫入以及其他對設備的操作時,就會調用該函數
每個設備都有一個請求隊列
dev->queue = blk_init_queue(test_request, &dev->lock);
對request函數的調用是與用戶空間進程中的動作完全異步的
一個簡單的request函數
struct request * elv_next_request(request_queue_t queue);
void end_request(struct request *req, int succeeded);
struct request
sector_t secotr;
unsigned long nr_sectors;
char *buffer
rq_data_dir(struct request *req);
請求隊列
一個塊設備請求隊列可以這樣描述:包含塊設備I/O請求的序列
請求隊列跟蹤未完成的塊設備的I/O請求
請求隊列還實現了插件接口
I/O調度器還負責合并鄰近的請求
請求隊列擁有request_queue或request_queue_t結構類型
<linux/blkdev.h>
隊列的創建與刪除
request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
void blk_cleanup_queue(request_queue_t *queue);
隊列函數
struct request *elv_next_request(request_queue_t *queue);
void blkdev_dequeue_request(struct request *req);
void elv_requeue_request(request_queue_t *queue, struct request *req);
隊列控制函數
void blk_stop_queue(request_queue_t *queue);
void blk_start_queue(request_queue_t *queue);
void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);
void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);
void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);
void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);
void blk_queue_max_segment_size(request_queue_t *queue, unsigned short max);
void blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
void blk_queue_dma_alignment(request_queue_t *queue, int mask);
void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
請求過程剖析
從本質上講,一個request結構是作為一個bio結構的鏈表實現的
bio結構
bio結構包含了驅動程序執行請求的全部信息,而不必與初始化這個請求的用戶空間的進程相關聯
<linux/bio.h>
struct bio
sector_t bi_sector;
unsigned int bi_size;
以字節為單位所需要傳輸的數據大小
unsigned long bi_flags;
unsigned short bio_phys_segments;
unsigned short bio_hw_segments;
struct bio_vec *bi_io_vec
struct bio_vec
struct page *vb_page;
unsigned int bv_len;
unsigned int bv_offset;
example
int segno;
struct bio_vec *bvec;
bio_for_each_segment(bvec, bio, segno)
{
/* 使用該段進行一定的操作 */
}
char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);
void __bio_kunmap_atomic(char *buffer, enum km_type type):
struct page *bio_page(struct bio *bio);
int bio_offset(struct bio *bio);
int bio_cur_sectors(struct bio *bio);
char *bio_data(struct bio *bio);
char *bio_kmap_irq(struct bio *bio, unsigned long *flags);
void bio_kunmap_irq(char *buffer, unsigned long *flags);
request結構成員
struct request
sector_t hard_sector;
unsigned long hard_nr_sectors;
unsigned int hard_cur_sectors;
struct bio *bio;
char *buffer;
unsigned short nr_phys_segments;
struct list_head queuelist;
屏障請求
在驅動程序接收到請求前,塊設備層重新組合了請求以提高I/O性能
出于同樣的目的,驅動程序也可以重新組合請求
但在無限制重新組合請求時面臨了一個問題:一些應用程序的某些操作,要在另外一些操作開始前完成
2.6版本的塊設備層使用屏障(barrier)請求來解決這個問題
如果一個請求被設置了REQ_HARDBARRER標志,那么在其他后續請求被初始化前,它必須被寫入驅動器
void blk_queue_ordered(request_queue_t *queue, int flag);
int blk_barrier_rq(sruct request *req);
如果返回一個非零值,該請求是一個屏障請求
不可重試請求
int blk_noretry_request(struct request *req);
請求完成函數
int end_that_request_first(struct request *req, int success, int count);
void end_that_request_last(struct request *req);
example
void end_request(struct request *req, int uptodate)
{
if (!end_that_request(req, uptodate, req->hard_cur_sectors)
{
add_disk_randomness(req->rq_disk);
blkdev_dequeue_request(req);
end_that_request_last(req);
}
}
使用bio
example
struct request *req
struct bio *bio;
rq_for_each_bio(bio, req)
{
/* 使用該bio結構進行一定的操作 */
}
塊設備請求和DMA
int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);
clear_bit(QUEUE_FLAG_CLEAR, &queue->queue_flags);
不使用請求隊列
typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);
void bio_endio(struct bio *bio, unsigned int bytes, int error);
request_queue_t *blk_alloc_queue(int flags);
并未真正地建立一個保存請求的隊列
void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);
drivers/block/ll_rw_block.c
其他一些細節
命令預處理
typedef int (prep_rq_fn) (request_queue_t *queue, struct request *req);
該函數要能返回下面的值之一
BLKPREP_OK
BLKPREP_KILL
BLKPREP_DEFER
void blk_queue_prep_rq(request_queue_t *queue, prep_rq_fn *func);
標記命令隊列
同時擁有多個活動請求的硬件通常支持某種形式的標記命令隊列(Tagged Command Queueing, TCQ)
TCQ只是為每個請求添加一個整數(標記)的技術,這樣當驅動器完成它們中的一個請求后,它就可以告
訴驅動程序完成的是哪個
int blk_queue_int_tags(request_queue_t *queue, int depth, struct blk_queue_tag *tags);
int blk_queue_resize_tags(request_queue_t *queue, int new_depth);
int blk_queue_start_tag(request_queue_t *queue, struct request *req);
void blk_queue_end_tag(request_queue_t *queue, struct request *req);
struct request *blk_queue_find_tag(request_queue_t *queue, int tag);
void blk_queue_invalidate_tags(request_queue_t *queue);
========
Linux設備驅動--塊設備(一)之概念和框架
基本概念?
?塊設備(blockdevice)
--- 是一種具有一定結構的隨機存取設備,對這種設備的讀寫是按塊進行的,他使用緩沖區來存放暫時
的數據,待條件成熟后,從緩存一次性寫入設備或者從設備一次性讀到緩沖區。
字符設備(Character device)
---是一個順序的數據流設備,對這種設備的讀寫是按字符進行的,而且這些字符是連續地形成一個數據
流。他不具備緩沖區,所以對這種設備的讀寫是實時的。
?
扇區(Sectors):任何塊設備硬件對數據處理的基本單位。通常,1個扇區的大小為512byte。(對設備而
言)
塊 ?(Blocks):由Linux制定對內核或文件系統等數據處理的基本單位。通常,1個塊由1個或多個扇區組
成。(對Linux操作系統而言)
段(Segments):由若干個相鄰的塊組成。是Linux內存管理機制中一個內存頁或者內存頁的一部分。
頁、段、塊、扇區之間的關系圖如下:
?
塊設備驅動整體框架
?塊設備的應用在Linux中是一個完整的子系統。
在Linux中,驅動對塊設備的輸入或輸出(I/O)操作,都會向塊設備發出一個請求,在驅動中用request結
構體描述。但對于一些磁盤設備而言請求的速度很慢,這時候內核就提供一種隊列的機制把這些I/O請求
添加到隊列中(即:請求隊列),在驅動中用request_queue結構體描述。在向塊設備提交這些請求前內
核會先執行請求的合并和排序預操作,以提高訪問的效率,然后再由內核中的I/O調度程序子系統來負責
提交 ?I/O 請求, ?調度程序將磁盤資源分配給系統中所有掛起的塊 I/O ?請求,其工作是管理塊設備
的請求隊列,決定隊列中的請求的排列順序以及什么時候派發請求到設備。
由通用塊層(Generic Block Layer)負責維持一個I/O請求在上層文件系統與底層物理磁盤之間的關系。
在通用塊層中,通常用一個bio結構體來對應一個I/O請求。
Linux提供了一個gendisk數據結構體,用來表示一個獨立的磁盤設備或分區,用于對底層物理磁盤進行
訪問。在gendisk中有一個類似字符設備中file_operations的硬件操作結構指針,是
block_device_operations結構體。
當多個請求提交給塊設備時,執行效率依賴于請求的順序。如果所有的請求是同一個方向(如:寫數據
),執行效率是最大的。內核在調用塊設備驅動程序例程處理請求之前,先收集I/O請求并將請求排序,
然后,將連續扇區操作的多個請求進行合并以提高執行效率(內核算法會自己做,不用你管),對I/O請
求排序的算法稱為電梯算法(elevator algorithm)。電梯算法在I/O調度層完成。內核提供了不同類型
的電梯算法,電梯算法有
1 noop(實現簡單的FIFO,基本的直接合并與排序),
2 anticipatory(延遲I/O請求,進行臨界區的優化排序),
3 Deadline(針對anticipatory缺點進行改善,降低延遲時間),
4 Cfq(均勻分配I/O帶寬,公平機制)
PS:其實IO調度層(包括請求合并排序算法)是不需要用戶管的,內核已經做好
相關數據結構
block_device: ? ? ?描述一個分區或整個磁盤對內核的一個塊設備實例?
gendisk: ? ? ? ? ? ? ? 描述一個通用硬盤(generic hard disk)對象。
hd_struct: ? ? ? ? ? ? 描述分區應有的分區信息?
bio: ? ? ? ? ? ? ? ? ? ? ? ?描述塊數據傳送時怎樣完成填充或讀取塊給driver
request: ? ? ? ? ? ? ? ?描述向內核請求一個列表準備做隊列處理。?
request_queue: ?描述內核申請request資源建立請求鏈表并填寫BIO形成隊列。
========
Linux設備驅動--塊設備(二)之相關結構體
上回最后面介紹了相關數據結構,下面再詳細介紹
塊設備對象結構 block_device
內核用結構block_device實例代表一個塊設備對象,如:整個硬盤或特定分區。如果該結構代表一個分
區,則其成員bd_part指向設備的分區結構。如果該結構代表設備,則其成員bd_disk指向設備的通用硬
盤結構gendisk
當用戶打開塊設備文件時,內核創建結構block_device實例,設備驅動程序還將創建結構gendisk實例,
分配請求隊列并注冊結構block_device實例。
塊設備對象結構block_device列出如下(在include/linux/fs.h中)
[cpp] view plain copy print?
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中):
[cpp] view plain copy print?
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; ?/*數據段頁內偏移*/
};
塊設備各個結構體間關系
========
Linux設備驅動--塊設備(三)之程序設計
?塊設備驅動注冊與注銷
塊設備驅動中的第1個工作通常是注冊它們自己到內核,完成這個任務的函數是 register_blkdev(),其
原型為:
int register_blkdev(unsigned int major, const char *name);
major 參數是塊設備要使用的主設備號,name為設備名,它會在/proc/devices中被顯示。 如果major為
0,內核會自動分配一個新的主設備號register_blkdev()函數的返回值就是這個主設備號。如果返回1個
負值,表明發生了一個錯誤。
與register_blkdev()對應的注銷函數是unregister_blkdev(),其原型為:
int unregister_blkdev(unsigned int major, const char *name);
這里,傳遞給register_blkdev()的參數必須與傳遞給register_blkdev()的參數匹配,否則這個函數返
回-EINVAL。
塊設備的請求隊列操作
標準的請求處理程序能排序請求,并合并相鄰的請求,如果一個塊設備希望使用標準的請求處理程序,
那它必須調用函數blk_init_queue來初始化請求隊列。當處理在隊列上的請求時,必須持有隊列自旋鎖
。初始化請求隊列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
該函數的第1個參數是請求處理函數的指針,第2個參數是控制訪問隊列權限的自旋鎖,這個函數會發生
內存分配的行為,故它可能會失敗,函數調用成
功時,它返回指向初始化請求隊列的指針,否則,返回NULL。這個函數一般在塊設備驅動的模塊加載函
數中調用。清除請求隊列
void blk_cleanup_queue(request_queue_t * q);
這個函數完成將請求隊列返回給系統的任務,一般在塊設備驅動模塊卸載函數中調用。
?
提取請求
struct request *elv_next_request(request_queue_t *queue);?
上述函數用于返回下一個要處理的請求(由 I/O 調度器決定),如果沒有請求則返回NULL。
去除請求
void blkdev_dequeue_request(struct request *req);?
上述函數從隊列中去除1個請求。如果驅動中同時從同一個隊列中操作了多個請求,它必須以這樣的方式
將它們從隊列中去除。
?
分配“請求隊列”
request_queue_t *blk_alloc_queue(int gfp_mask);
對于FLASH、RAM盤等完全隨機訪問的非機械設備,并不需要進行復雜的I/O調度,這個時候,應該使用上
述函數分配1個“請求隊列”,并使用如下函數來綁定“請求隊列”和“制造請求”函數。
void blk_queue_make_request(request_queue_t * q,?
make_request_fn * mfn);
void blk_queue_hardsect_size(request_queue_t *queue,?
unsigned short max);?
該函數用于告知內核塊設備硬件扇區的大小,所有由內核產生的請求都是這個大小的倍數并且被正確對
界。但是,內核塊設備層和驅動之間的通信還是以512字節扇區為單位進行。
?
步驟:
在塊設備驅動的模塊加載函數中通常需要完成如下工作:
① 分配、初始化請求隊列,綁定請求隊列和請求函數。
② 分配、初始化gendisk,給gendisk的major、fops、queue等成
員賦值,最后添加gendisk。
③ 注冊塊設備驅動。
在塊設備驅動的模塊卸載函數中通常需要與模塊加載函數相反的工作:
① 清除請求隊列。
② 刪除gendisk和對gendisk的引用。
③ 刪除對塊設備的引用,注銷塊設備驅動。
總結:
塊設備的I/O操作方式與字符設備存在較大的不同,因而引入了
request_queue、request、bio等一系列數據結構。在整個塊設備的I/O操作中,貫穿于始終的就是“請
求”,字符設備的I/O操作則是直接進行不繞彎,
塊設備的I/O操作會排隊和整合。
驅動的任務是處理請求,對請求的排隊和整合由I/O調度算法解決,因此,塊設備驅動的核心就是請求處
理函數或“制造請求”函數。
盡管在塊設備驅動中仍然存在block_device_operations結構體及其成員函數,但其不再包含讀寫一類的
成員函數,而只是包含打開、釋放及I/O控制等
與具體讀寫無關的函數。塊設備驅動的結構相當復雜的,但幸運的是,塊設備不像字符設備那么包羅萬
象,它通常就是存儲設備,而且驅動的主體已經
由Linux內核提供,針對一個特定的硬件系統,驅動工程師所涉及到的工作往往只是編寫少量的與硬件直
接交互的代碼。
#include <linux/init.h> ? ?
#include <linux/module.h> ? ?
#include <linux/kernel.h> ? ?
#include <linux/fs.h> ?
#include <asm/uaccess.h> ?
#include <linux/spinlock.h> ?
#include <linux/sched.h> ?
#include <linux/types.h> ?
#include <linux/fcntl.h> ?
#include <linux/hdreg.h> ?
#include <linux/genhd.h> ?
#include <linux/blkdev.h> ?
??
#define MAXBUF 1024 ??
??
??
#define BLK_MAJOR 253 ?
??
char blk_dev_name[]="blk_dev"; ?
static char flash[1024*16]; ?
??
??
int major; ?
spinlock_t lock; ?
struct gendisk *gd; ?
??
??
??
/*塊設備數據傳輸*/ ?
static void blk_transfer(unsigned long sector, unsigned long nsect, char *buffer, int?
write) ?
{ ?
? ? int read = !write; ?
? ? if(read) ?
? ? { ?
? ? ? ? memcpy(buffer, flash+sector*512, nsect*512); ?
? ? } ?
? ? else ?
? ? { ?
? ? ? ? memcpy(flash+sector*512, buffer, nsect*512); ?
? ? } ?
} ?
??
/*塊設備請求處理函數*/ ?
static void blk_request_func(struct request_queue *q) ?
{ ?
? ? struct request *req; ?
? ? while((req = elv_next_request(q)) != NULL) ? ?
? ? { ?
? ? ? ? if(!blk_fs_request(req)) ?
? ? ? ? { ?
? ? ? ? ? ? end_request(req, 0); ?
? ? ? ? ? ? continue; ?
? ? ? ? } ?
? ? ? ? ??
? ? ? ? blk_transfer(req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req)); ?
? ? ? ? /*rq_data_dir從request獲得數據傳送的方向*/ ?
? ? ? ? /*req->current_nr_sectors 在當前段中將完成的扇區數*/ ?
? ? ? ? /*req->sector 將提交的下一個扇區*/ ?
? ? ? ? end_request(req, 1); ?
? ? } ?
} ?
??
/*strcut block_device_operations*/ ?
static ?int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long?
arg) ?
{ ?
? ? ? ?return -ENOTTY; ?
} ?
??
static int blk_open (struct block_device *dev , fmode_t no) ?
{ ?
? ? printk("blk mount succeed\n"); ?
? ? return 0; ?
} ?
static int blk_release(struct gendisk *gd , fmode_t no) ?
{ ?
? ? printk("blk umount succeed\n"); ?
? ? return 0; ?
} ?
struct block_device_operations blk_ops= ?
{ ?
? ? .owner = THIS_MODULE, ?
? ? .open = blk_open, ?
? ? .release = blk_release, ?
? ? .ioctl = blk_ioctl, ?
}; ?
??
//----------------------------------------------- ?
??
static int __init block_module_init(void) ?
{ ?
? ? ??
? ? ??
? ? if(!register_blkdev(BLK_MAJOR, blk_dev_name)) //注冊一個塊設備 ?
? ? { ?
? ? ? ? major = BLK_MAJOR; ? ?
? ? ? ? printk("regiser blk dev succeed\n"); ?
? ? } ?
? ? else ?
? ? { ?
? ? ? ? return -EBUSY; ?
? ? } ?
? ? gd = alloc_disk(1); ?//分配一個gendisk,分區是一個 ?
? ? spin_lock_init(&lock); //初始化一個自旋鎖 ?
? ? gd->major = major; ?
? ? gd->first_minor = 0; ? //第一個次設備號 ?
? ? gd->fops = &blk_ops; ? //關聯操作函數 ?
??
? ? gd->queue = blk_init_queue(blk_request_func, &lock); //初始化請求隊列并關聯到gendisk ?
??
? ? snprintf(gd->disk_name, 32, "blk%c", 'a'); ? ?
? ? blk_queue_hardsect_size(gd->queue, 512); ?//設置扇區大小512字節 ?
? ? set_capacity(gd, 32); ?//設置塊設備大小 512*32=16K ?
? ? add_disk(gd); ?
? ? printk("gendisk init success!\n"); ?
? ? return 0; ?
} ?
static void __exit block_module_exit(void) ?
{ ?
? ? blk_cleanup_queue(gd->queue); ?
? ? del_gendisk(gd); ??
? ? unregister_blkdev(BLK_MAJOR, blk_dev_name); ?
? ? printk("block module exit succeed!\n"); ?
} ?
??
module_init(block_module_init); ?
module_exit(block_module_exit); ?
??
MODULE_LICENSE("GPL"); ?
MODULE_AUTHOR("gec");?
========
linux之塊設備驅動
? ? Linux操作系統有兩類主要的設備文件:
1.字符設備:以字節為單位進行順序I/O操作的設備,無需緩沖區且被直接讀寫。
2.塊設備:只能以塊單位接收輸入返回,對于I/O請求有對應的緩沖區,可以隨機訪問,塊設備的訪問位
置必須能夠在介質的不同區間前后移動。在塊設備中,最小的可尋址單元是扇區,扇區的大小一般是2的
整數倍,常見的大小為512個字節。
? ?上圖是一個塊設備操作的分層實現圖
1.當一個進程被Read時,內核會通過VFS層去讀取要讀的文件塊有沒有被cache了,這個cache由一個
buffer_head結構讀取。如果要讀取的文件塊還沒有被cache,則就要從文件系統中去讀取,通過一個
address_space結構來引用,如果調用文件系統讀取函數去讀取一個扇區的數據。當它從磁盤讀出數據時
,將數據頁連入到cache中,當下一次在讀取時,就不需要從磁盤去讀取了。Read完后將請求初始化成一
個bio結構,并提交給通用塊層。
2.它通過submit_bio()去完成,通用層在調用相應的設備IO調度器,這個調度器的調度算法,將這個bio
合并到已經存在的request中,或者創建一個新的request,并將創建的插入到請求隊列中,最后就剩下
塊設備驅動層來完成后面的所有工作。
內核中塊得I/O操作的是由bio結構表示的
點擊(此處)折疊或打開
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_vec的偏移個數 */
? ? unsigned short ? ? ? ?bi_idx; ? ? ? ?/* bvl_vec */
? ? unsigned short ? ? ? ?bi_phys_segments;
? ? /* Number of segments after physical and DMA remapping
? ? ?* hardware coalescing is performed.
? ? ?*/
? ? unsigned short ? ? ? ?bi_hw_segments;
? ? unsigned int ? ? ? ?bi_size; ? ?/* residual I/O count */
? ? /*
? ? ?* To keep track of the max hw size, we account for the
? ? ?* sizes of the first and last virtually mergeable segments
? ? ?* in this bio
? ? ?*/
? ? unsigned int ? ? ? ?bi_hw_front_size;
? ? unsigned int ? ? ? ?bi_hw_back_size;
? ? unsigned int ? ? ? ?bi_max_vecs; ? ?/* max bvl_vecs we can hold */
? ? struct bio_vec ? ? ? ?*bi_io_vec; ? ?/* the actual vec list */
? ? bio_end_io_t ? ? ? ?*bi_end_io;
? ? atomic_t ? ? ? ?bi_cnt; ? ? ? ?/* pin count */
? ? void ? ? ? ? ? ?*bi_private;
? ? bio_destructor_t ? ?*bi_destructor; ? ?/* destructor */
};
此結構體的目的主要是正在執行的I/O操作,其中的bi_io_vecs、bi_vcnt、bi_idx三者都可以相互找到
。bio_vec描述一個特定的片段,片段所在的物理頁,塊在物理頁中的偏移頁,整個bio_io_vec結構表示
一個完整的緩沖區。
點擊(此處)折疊或打開
struct bio_vec {
? ? struct page ? ?*bv_page;
? ? unsigned int ? ?bv_len;
? ? unsigned int ? ?bv_offset;
};
當一個塊被調用內存時,要儲存在一個緩沖區,每個緩沖區與一個塊對應,所以每一個緩沖區獨有一個
對應的描述符,該描述符用buffer_head結構表示
點擊(此處)折疊或打開
struct buffer_head {
? ? unsigned long b_state; ? ? ? ?/* buffer state bitmap (see above) */
? ? struct buffer_head *b_this_page;/* circular list of page's buffers */
? ? struct page *b_page; ? ? ? ?/* the page this bh is mapped to */
? ? sector_t b_blocknr; ? ? ? ?/* start block number */
? ? size_t b_size; ? ? ? ? ? ?/* size of mapping */
? ? char *b_data; ? ? ? ? ? ?/* pointer to data within the page */
? ? struct block_device *b_bdev;
? ? bh_end_io_t *b_end_io; ? ? ? ?/* I/O completion */
? ? ?void *b_private; ? ? ? ?/* reserved for b_end_io */
? ? struct list_head b_assoc_buffers; /* associated with another mapping */
? ? struct address_space *b_assoc_map; ? ?/* mapping this buffer is
? ? ? ? ? ? ? ? ? ? ? ? ?associated with */
? ? atomic_t b_count; ? ? ? ?/* users using this buffer_head */
};
下面來看看塊設備的核心ll_rw_block函數
點擊(此處)折疊或打開
void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
{
? ? int i;
? ? for (i = 0; i < nr; i ) {
? ? ? ? struct buffer_head *bh = bhs[i];
? ? ? ? if (!trylock_buffer(bh))
? ? ? ? ? ? continue;
? ? ? ? if (rw == WRITE) {
? ? ? ? ? ? if (test_clear_buffer_dirty(bh)) {
? ? ? ? ? ? ? ? bh->b_end_io = end_buffer_write_sync;
? ? ? ? ? ? ? ? get_bh(bh);
? ? ? ? ? ? ? ? submit_bh(WRITE, bh);
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? } else {
? ? ? ? ? ? if (!buffer_uptodate(bh)) {
? ? ? ? ? ? ? ? bh->b_end_io = end_buffer_read_sync;
? ? ? ? ? ? ? ? get_bh(bh);
? ? ? ? ? ? ? ? submit_bh(rw, bh);
? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? unlock_buffer(bh);
? ? }
}
請求塊設備驅動將多個物理塊讀出或者寫到塊設備,塊設備的讀寫都是在塊緩沖區中進行。
點擊(此處)折疊或打開
int submit_bh(int rw, struct buffer_head * bh)
{
? ? struct bio *bio;
? ? int ret = 0;
? ? BUG_ON(!buffer_locked(bh));
? ? BUG_ON(!buffer_mapped(bh));
? ? BUG_ON(!bh->b_end_io);
? ? BUG_ON(buffer_delay(bh));
? ? BUG_ON(buffer_unwritten(bh));
? ? /*
? ? ?* Only clear out a write error when rewriting
? ? ?*/
? ? if (test_set_buffer_req(bh) && (rw & WRITE))
? ? ? ? clear_buffer_write_io_error(bh);
? ? /*
? ? ?* from here on down, it's all bio -- do the initial mapping,
? ? ?* submit_bio -> generic_make_request may further map this bio around
? ? ?*/
? ? bio = bio_alloc(GFP_NOIO, 1);
? ? bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);
? ? bio->bi_bdev = bh->b_bdev;
? ? bio->bi_io_vec[0].bv_page = bh->b_page;
? ? bio->bi_io_vec[0].bv_len = bh->b_size;
? ? bio->bi_io_vec[0].bv_offset = bh_offset(bh);
? ? bio->bi_vcnt = 1;
? ? bio->bi_idx = 0;
? ? bio->bi_size = bh->b_size;
? ? bio->bi_end_io = end_bio_bh_io_sync;
? ? bio->bi_private = bh;
? ? bio_get(bio);
? ? submit_bio(rw, bio);
? ? if (bio_flagged(bio, BIO_EOPNOTSUPP))
? ? ? ? ret = -EOPNOTSUPP;
? ? bio_put(bio);
? ? return ret;
這個函數主要是調用submit_bio,最終調用generic_make_request去完成將bio傳遞給驅動去處理。
點擊(此處)折疊或打開
void generic_make_request(struct bio *bio)
{
? ? struct bio_list bio_list_on_stack;
? ? if (!generic_make_request_checks(bio))
? ? ? ? return;
? ? if (current->bio_list) {
? ? ? ? bio_list_add(current->bio_list, bio);
? ? ? ? return;
? ? }
? ? BUG_ON(bio->bi_next);
? ? bio_list_init(&bio_list_on_stack);
? ? current->bio_list = &bio_list_on_stack;
? ? do {
? ? ? ? struct request_queue *q = bdev_get_queue(bio->bi_bdev);
? ? ? ? q->make_request_fn(q, bio);
? ? ? ? bio = bio_list_pop(current->bio_list);
? ? } while (bio);
? ? current->bio_list = NULL; /* deactivate */
}
這個函數主要是取出塊設備相應的隊列中的每個設備,在調用塊設備驅動的make_request,如果沒有指
定make_request就調用內核默認的__make_request,這個函數主要作用就是調用I/O調度算法將bio合并
,或插入到隊列中合適的位置中去。
整個流程為:
那么request_fn指向那個函數呢?在內核中搜搜request_fn,發現
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
? ? return blk_init_queue_node(rfn, lock, -1);
}
EXPORT_SYMBOL(blk_init_queue);
request_queue_t *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{
? ? request_queue_t *q = blk_alloc_queue_node(GFP_KERNEL, node_id);
? ? if (!q)
? ? ? ? return NULL;
? ? q->node = node_id;
? ? if (blk_init_free_list(q)) {
? ? ? ? kmem_cache_free(requestq_cachep, q);
? ? ? ? return NULL;
? ? }
? ? if (!lock) {
? ? ? ? spin_lock_init(&q->__queue_lock);
? ? ? ? lock = &q->__queue_lock;
? ? }
? ? q->request_fn ? ? ? ?= rfn;
? ? q->prep_rq_fn ? ? ? ?= NULL;
? ? q->unplug_fn ? ? ? ?= generic_unplug_device;
? ? q->queue_flags ? ? ? ?= (1 << QUEUE_FLAG_CLUSTER);
? ? q->queue_lock ? ? ? ?= lock;
? ? blk_queue_segment_boundary(q, 0xffffffff);
? ? blk_queue_make_request(q, __make_request);
? ? blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);
? ? blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
? ? blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);
? ? q->sg_reserved_size = INT_MAX;
? ? /*
? ? ?* all done
? ? ?*/
? ? if (!elevator_init(q, NULL)) {
? ? ? ? blk_queue_congestion_threshold(q);
? ? ? ? return q;
? ? }
? ? blk_put_queue(q);
? ? return NULL;
}
原來request_queue的make_request_fn指向__make_request()函數,這個函數復雜I/O調度,并對bio做
些合并等。下面來看看__make_request()做了些什么?
由上面可以分析得出,其中有一個很重要的結構體
struct request {
? ? struct list_head queuelist;//連接這個請求到請求隊列.?
//追蹤請求硬件完成的扇區的成員. 第一個尚未被傳送的扇區被存儲到 hard_sector, 已經傳送的扇區
總數在 ha//rd_nr_sectors, 并且在當前 bio 中剩余的扇區數是 hard_cur_sectors. 這些成員打算只
用在塊子系統; 驅動//不應當使用它們.
? ? struct request_queue *q;
? ? sector_t hard_sector;?
? ? unsigned long hard_nr_sectors;?
? ? unsigned int hard_cur_sectors;
? ? struct bio *bio;//bio 是給這個請求的 bio 結構的鏈表. 你不應當直接存取這個成員; 使用?
rq_for_each_bio(后面描述) 代替.
? ? unsigned short nr_phys_segments;//被這個請求在物理內存中占用的獨特段的數目, 在鄰近頁已
被合并后
? ? char *buffer;//隨著深入理解,可見到這個成員僅僅是在當前 bio 上調用 bio_data 的結果.
};
request_queue只是一個請求隊列,通過可以找到requeue,然后通過bio結構體對應的page讀取物理內存
中的信息。
下面看看內核使用的塊設備的例子
static int jsfd_init(void)
{
? ? static DEFINE_SPINLOCK(lock);
? ? struct jsflash *jsf;
? ? struct jsfd_part *jdp;
? ? int err;
? ? int i;
? ? if (jsf0.base == 0)
? ? ? ? return -ENXIO;
? ? err = -ENOMEM;
//1. 分配gendisk: alloc_disk
? ? for (i = 0; i < JSF_MAX; i++) {
? ? ? ? struct gendisk *disk = alloc_disk(1);
? ? ? ? if (!disk)
? ? ? ? ? ? goto out;
? ? ? ? jsfd_disk[i] = disk;
? ? }
//2. 設置,分配/設置隊列: request_queue_t ?// 它提供讀寫能力
? ? if (register_blkdev(JSFD_MAJOR, "jsfd")) {
? ? ? ? err = -EIO;
? ? ? ? goto out;
? ? }
? ? jsf_queue = blk_init_queue(jsfd_do_request, &lock);
? ? if (!jsf_queue) {
? ? ? ? err = -ENOMEM;
? ? ? ? unregister_blkdev(JSFD_MAJOR, "jsfd");
? ? ? ? goto out;
? ? }
//3. 設置gendisk其他信息 ? ? ? ? ? ? // 它提供屬性: 比如容量
? ? for (i = 0; i < JSF_MAX; i++) {
? ? ? ? struct gendisk *disk = jsfd_disk[i];
? ? ? ? if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
? ? ? ? jsf = &jsf0; ? ?/* actually, &jsfv[i >> JSF_PART_BITS] */
? ? ? ? jdp = &jsf->dv[i&JSF_PART_MASK];
? ? ? ? disk->major = JSFD_MAJOR;
? ? ? ? disk->first_minor = i;
? ? ? ? sprintf(disk->disk_name, "jsfd%d", i);
? ? ? ? disk->fops = &jsfd_fops;
? ? ? ? set_capacity(disk, jdp->dsize >> 9);
? ? ? ? disk->private_data = jdp;
? ? ? ? disk->queue = jsf_queue;
//4 注冊: add_disk
? ? ? ? add_disk(disk);
? ? ? ? set_disk_ro(disk, 1);
? ? }
? ? return 0;
out:
? ? while (i--)
? ? ? ? put_disk(jsfd_disk[i]);
? ? return err;
}
========
Linux 塊設備驅動 實例
?任務:用一片虛擬地址連續的內存空間模擬一個塊設備,并為其寫一個驅動
/*
?* Sample disk driver, from the beginning.
?*/
#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h> ? ?/* printk() */
#include <linux/slab.h> ? ? ? ?/* kmalloc() */
#include <linux/fs.h> ? ? ? ?/* everything... */
#include <linux/errno.h> ? ?/* error codes */
#include <linux/timer.h>
#include <linux/types.h> ? ?/* size_t */
#include <linux/fcntl.h> ? ?/* O_ACCMODE */
#include <linux/hdreg.h> ? ?/* HDIO_GETGEO */
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/buffer_head.h> ? ?/* invalidate_bdev */
#include <linux/bio.h>
MODULE_LICENSE("Dual BSD/GPL");
static int sbull_major = 0;
module_param(sbull_major, int, 0);
static int hardsect_size = 512;
module_param(hardsect_size, int, 0);
static int nsectors = 25600; ? ?/* How big the drive is */
module_param(nsectors, int, 0);
static int ndevices = 1;
module_param(ndevices, int, 0);
/*
?* The different "request modes" we can use.
?*/
enum {
? ? RM_SIMPLE ?= 0, ? ?/* The extra-simple request function */
? ? RM_FULL ? ?= 1, ? ?/* The full-blown version */
? ? RM_NOQUEUE = 2, ? ?/* Use make_request */
};
//static int request_mode = RM_FULL;
static int request_mode = RM_SIMPLE;
//static int request_mode = RM_SIMPLE;
module_param(request_mode, int, 0);
/*
?* Minor number and partition management.
?*/
#define SBULL_MINORS ? ?16
#define MINOR_SHIFT ? ?4
#define DEVNUM(kdevnum) ? ?(MINOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT
/*
?* We can tweak our hardware sector size, but the kernel talks to us
?* in terms of small sectors, always.
?*/
#define KERNEL_SECTOR_SIZE ? ?512
/*
?* After this much idle time, the driver will simulate a media change.
?*/
#define INVALIDATE_DELAY ? ?60*HZ
/*
?* The internal representation of our device.
?*/
struct sbull_dev {
? ? ? ? int size; ? ? ? ? ? ? ? ? ? ? ? /* Device size in sectors */
? ? ? ? // data 是本程序模擬的塊設備,是一片連續的虛擬空間
? ? ? ? // 在初始化函數里分配的虛擬地址連續的內存空間
? ? ? ? u8 *data; ? ? ? ? ? ? ? ? ? ? ? /* The data array */
? ? ? ? short users; ? ? ? ? ? ? ? ? ? ?/* How many users */
? ? ? ? short media_change; ? ? ? ? ? ? /* Flag a media change? */
? ? ? ? spinlock_t lock; ? ? ? ? ? ? ? ?/* For mutual exclusion */
? ? ? ? struct request_queue *queue; ? ?/* The device request queue */
? ? ? ? struct gendisk *gd; ? ? ? ? ? ? /* The gendisk structure */
? ? ? ? struct timer_list timer; ? ? ? ?/* For simulated media changes */
};
static struct sbull_dev *Devices = NULL;
/*
?* Handle an I/O request.
?*/
static void sbull_transfer(struct sbull_dev *dev, unsigned long sector,
? ? ? ? unsigned long nsect, char *buffer, int write)
{
? ? unsigned long offset = sector*KERNEL_SECTOR_SIZE; ? ? // 需要讀寫的扇區的偏移地址
? ? unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE; ? ? ? ?// 需要讀寫的字節數
? ??
? ? if ((offset + nbytes) > dev->size) { ? ? ?// 判斷輸入參數是否合法,是否超出邊界
? ? ? ? printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);
? ? ? ? return;
? ? }
? ? // 實際的讀寫操作
? ? // 由于本程序是用一片連續的內存空間模擬塊設備
? ? // 所以這里對硬件(內存空間)的讀寫操作,就是復制內存
? ? // 在具體點,就是下面的memcpy
? ? // 具體的項目,需修改為具體的接口函數
? ? if (write)
? ? ? ? // 寫
? ? ? ? memcpy(dev->data + offset, buffer, nbytes);
? ? else
? ? ? ? // 讀
? ? ? ? memcpy(buffer, dev->data + offset, nbytes);
}
/*The simple form of the request function.*/
static void sbull_request(struct request_queue *q)
{
? ? struct request *req;
? ? // 服務完隊列上的所有請求
? ? while ((req = elv_next_request(q)) != NULL) { ?// elv_next_request :從隊列上去一個下來
? ? ? ? struct sbull_dev *dev = req->rq_disk->private_data;
? ? ? ? if (! blk_fs_request(req)) {
? ? ? ? ? ? printk (KERN_NOTICE "Skip non-fs request\n");
? ? ? ? ? ? end_request(req, 0);
? ? ? ? ? ? continue;
? ? ? ? }
? ? ? ? sbull_transfer(dev, req->sector, req->current_nr_sectors,
? ? ? ? ? ? ? ? req->buffer, rq_data_dir(req));
? ? ? ? end_request(req, 1);
? ? }
}
/*
?* Transfer a single BIO.
?*/
static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
{
? ? int i;
? ? struct bio_vec *bvec;
? ? sector_t sector = bio->bi_sector;
? ? /* Do each segment independently. */
? ? bio_for_each_segment(bvec, bio, i) {
? ? ? ? char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
? ? ? ? sbull_transfer(dev, sector, bio_cur_sectors(bio),
? ? ? ? ? ? ? ? buffer, bio_data_dir(bio) == WRITE);
? ? ? ? sector += bio_cur_sectors(bio);
? ? ? ? __bio_kunmap_atomic(bio, KM_USER0);
? ? }
? ? return 0; /* Always "succeed" */
}
/*
?* Transfer a full request.
?*/
static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
{
? ? struct bio *bio;
? ? int nsect = 0;
? ? // steps through each bio that makes up a request.
? ? // 遍歷
? ? __rq_for_each_bio(bio, req) {
? ? ? ? sbull_xfer_bio(dev, bio);
? ? ? ? nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
? ? }
? ? return nsect;
}
/*
?* Smarter request function that "handles clustering".
?*/
static void sbull_full_request(struct request_queue *q)
{
? ? struct request *req;
? ? int sectors_xferred;
? ? struct sbull_dev *dev = q->queuedata;
? ? printk("<0>""in %s\n",__FUNCTION__);
? ? while ((req = elv_next_request(q)) != NULL) {
? ? ? ? if (! blk_fs_request(req)) {
? ? ? ? ? ? printk (KERN_NOTICE "Skip non-fs request\n");
? ? ? ? ? ? end_request(req, 0);
? ? ? ? ? ? continue;
? ? ? ? }
? ? ? ? sectors_xferred = sbull_xfer_request(dev, req);
? ? ? ? __blk_end_request(req,0,sectors_xferred<<9);//add by lht for 2.6.27
? ? }
}
?
//The direct make request version
static int sbull_make_request(struct request_queue *q, struct bio *bio)
{
? ? struct sbull_dev *dev = q->queuedata;
? ? int status;
? ? status = sbull_xfer_bio(dev, bio);
? ? //bio_endio(bio, bio->bi_size, status);
? ? bio_endio(bio, status);
? ? return 0;
}
/*
?* Open and close.
?*/
static int sbull_open(struct inode *inode, struct file *filp)
{
? ? struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
? ? //printk("<0>" "fdfjdlksjfdlkj\n"); ? ?
? ? del_timer_sync(&dev->timer);
? ? filp->private_data = dev;
? ? spin_lock(&dev->lock);
? ? if (! dev->users)?
? ? ? ? check_disk_change(inode->i_bdev);
? ? dev->users++;
? ? spin_unlock(&dev->lock);
? ? return 0;
}
static int sbull_release(struct inode *inode, struct file *filp)
{
? ? struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
? ? spin_lock(&dev->lock);
? ? dev->users--;
? ? if (!dev->users) {
? ? ? ? dev->timer.expires = jiffies + INVALIDATE_DELAY;
? ? ? ? add_timer(&dev->timer);
? ? }
? ? spin_unlock(&dev->lock);
? ? return 0;
}
/*
?* Look for a (simulated) media change.
?*/
int sbull_media_changed(struct gendisk *gd)
{
? ? struct sbull_dev *dev = gd->private_data;
? ??
? ? return dev->media_change;
}
/*
?* Revalidate. ?WE DO NOT TAKE THE LOCK HERE, for fear of deadlocking
?* with open. ?That needs to be reevaluated.
?*/
int sbull_revalidate(struct gendisk *gd)
{
? ? struct sbull_dev *dev = gd->private_data;
? ??
? ? if (dev->media_change) {
? ? ? ? dev->media_change = 0;
? ? ? ? memset (dev->data, 0, dev->size);
? ? }
? ? return 0;
}
/*
?* The "invalidate" function runs out of the device timer; it sets
?* a flag to simulate the removal of the media.
?*/
void sbull_invalidate(unsigned long ldev)
{
? ? struct sbull_dev *dev = (struct sbull_dev *) ldev;
? ? spin_lock(&dev->lock);
? ? if (dev->users || !dev->data)?
? ? ? ? printk (KERN_WARNING "sbull: timer sanity check failed\n");
? ? else
? ? ? ? dev->media_change = 1;
? ? spin_unlock(&dev->lock);
}
/*
?* The ioctl() implementation
?*/
int sbull_ioctl (struct inode *inode, struct file *filp,
? ? ? ? ? ? ? ? ?unsigned int cmd, unsigned long arg)
{
? ? long size;
? ? struct hd_geometry geo;
? ? struct sbull_dev *dev = filp->private_data;
? ? switch(cmd) {
? ? ? ? case HDIO_GETGEO:
? ? ? ? ? ? /*
? ? ? ? ?* Get geometry: since we are a virtual device, we have to make
? ? ? ? ?* up something plausible. ?So we claim 16 sectors, four heads,
? ? ? ? ?* and calculate the corresponding number of cylinders. ?We set the
? ? ? ? ?* start of data at sector four.
? ? ? ? ?*/
? ? ? ? //printk("<0>""-------------size=%d\n",size);
? ? ? ? /****************for early version************/
? ? ? ? //size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);
? ? ? ? //printk("<0>""-------------size=%d\n",size);
? ? ? ? //geo.cylinders = (size & ~0x3f) >> 6;
? ? ? ? //geo.cylinders=2000;
? ? ? ? //geo.heads = 4;
? ? ? ? //geo.sectors = 16;
? ? ? ? //geo.sectors=2560;
? ? ? ? //geo.start = 0;
? ? ? ? //if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))
? ? ? ? // ? ?return -EFAULT;
? ? ? ? return 0;
? ? }
? ? return -ENOTTY; /* unknown command */
}
static int sbull_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
? ? unsigned long size;
? ? struct sbull_dev *pdev = bdev->bd_disk->private_data;
? ? size = pdev->size;
? ? geo->cylinders = (size & ~0x3f) >> 6;
? ? geo->heads ? ?= 4;
? ? geo->sectors = 16;
? ? geo->start = 0;
? ? return 0;
}
/*
?* The device operations structure.
?*/
static struct block_device_operations sbull_ops = {
? ? .owner ? ? ? ? ? = THIS_MODULE,
? ? .open ? ? ? ? ? ? ?= sbull_open,
? ? .release ? ? ?= sbull_release,
? ? .media_changed ? = sbull_media_changed,
? ? .revalidate_disk = sbull_revalidate,
? ? .ioctl ? ? ? ? ? ? = sbull_ioctl,
? ? .getgeo ? ? ? ? ? ?= sbull_getgeo,
};
/*
?* Set up our internal device.
?*/
// 初始化設備結構體 static struct sbull_dev *Devices中的成員
static void setup_device(struct sbull_dev *dev, int which)
{
? ? /*
? ? ?* Get some memory.
? ? ?*/
? ? memset (dev, 0, sizeof (struct sbull_dev));
? ? dev->size = nsectors*hardsect_size;
? ? // 分配一片虛擬地址連續的內存空間,作為塊設備。
? ? dev->data = vmalloc(dev->size); ??
? ? if (dev->data == NULL) {
? ? ? ? printk (KERN_NOTICE "vmalloc failure.\n");
? ? ? ? return;
? ? }
? ? spin_lock_init(&dev->lock);
? ??
? ? /*
? ? ?* The timer which "invalidates" the device.
? ? ?*/
? ? init_timer(&dev->timer);
? ? dev->timer.data = (unsigned long) dev;
? ? dev->timer.function = sbull_invalidate;
? ??
? ? /*
? ? ?* The I/O queue, depending on whether we are using our own
? ? ?* make_request function or not.
? ? ?*/
? ? switch (request_mode) {
? ? ? ? case RM_NOQUEUE:
? ? ? ? dev->queue = blk_alloc_queue(GFP_KERNEL);
? ? ? ? if (dev->queue == NULL)
? ? ? ? ? ? goto out_vfree;
? ? ? ? blk_queue_make_request(dev->queue, sbull_make_request);
? ? ? ? break;
? ? ? ? case RM_FULL:
? ? ? ? dev->queue = blk_init_queue(sbull_full_request, &dev->lock);
? ? ? ? if (dev->queue == NULL)
? ? ? ? ? ? goto out_vfree;
? ? ? ? break;
? ? ? ? default:
? ? ? ? printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode);
? ? ? ? ? ? /* fall into.. */
? ??
? ? ? ? case RM_SIMPLE:
? ? ? ? dev->queue = blk_init_queue(sbull_request, &dev->lock);
? ? ? ? if (dev->queue == NULL)
? ? ? ? ? ? goto out_vfree;
? ? ? ? break;
? ? }
? ? blk_queue_hardsect_size(dev->queue, hardsect_size);
? ? dev->queue->queuedata = dev;
? ? /*
? ? ?* And the gendisk structure.
? ? ?*/
? ? dev->gd = alloc_disk(SBULL_MINORS);
? ? if (! dev->gd) {
? ? ? ? printk (KERN_NOTICE "alloc_disk failure\n");
? ? ? ? goto out_vfree;
? ? }
? ? dev->gd->major = sbull_major;
? ? dev->gd->first_minor = which*SBULL_MINORS;
? ? dev->gd->fops = &sbull_ops;
? ? dev->gd->queue = dev->queue;
? ? dev->gd->private_data = dev;
? ? snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
? ? set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
? ? add_disk(dev->gd);
? ? return;
? out_vfree:
? ? if (dev->data)
? ? ? ? vfree(dev->data);
}
?
static int __init sbull_init(void)
{
? ? int i;
? ? /*
? ? ?* Get registered.
? ? ?*/
? ? // ? ?printk("<0>" "add by lht\n");
? ? sbull_major = register_blkdev(sbull_major, "sbull");
? ? if (sbull_major <= 0) {
? ? ? ? printk(KERN_WARNING "sbull: unable to get major number\n");
? ? ? ? return -EBUSY;
? ? }
? ? /*
? ? ?* Allocate the device array, and initialize each one.
? ? ?*/
? ? Devices = kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL);
? ? if (Devices == NULL)
? ? ? ? goto out_unregister;
? ? for (i = 0; i < ndevices; i++)?
? ? ? ? setup_device(Devices + i, i);
? ??
? ? return 0;
? out_unregister:
? ? unregister_blkdev(sbull_major, "sbd");
? ? return -ENOMEM;
}
static void sbull_exit(void)
{
? ? int i;
? ? for (i = 0; i < ndevices; i++) {
? ? ? ? struct sbull_dev *dev = Devices + i;
? ? ? ? del_timer_sync(&dev->timer);
? ? ? ? if (dev->gd) {
? ? ? ? ? ? del_gendisk(dev->gd);
? ? ? ? ? ? put_disk(dev->gd);
? ? ? ? }
? ? ? ? if (dev->queue) {
? ? ? ? ? ? if (request_mode == RM_NOQUEUE)
? ? ? ? ? ? // ? ?blk_put_queue(dev->queue);
? ? ? ? ? ? kobject_put(&(dev->queue)->kobj);
? ? ? ? ? ? else
? ? ? ? ? ? ? ? blk_cleanup_queue(dev->queue);
? ? ? ? }
? ? ? ? if (dev->data)
? ? ? ? ? ? vfree(dev->data);
? ? }
? ? unregister_blkdev(sbull_major, "sbull");
? ? kfree(Devices);
}
? ??
module_init(sbull_init);
module_exit(sbull_exit);
?
測試方法:
# Makefile
ifeq ($(KERNELRELEASE),)
#KERNELDIR ?= /home/lht/kernel2.6/linux-2.6.14
KERNELDIR ?= /lib/modules/$(shell uname -r)/build M=$(PWD) modules
PWD := $(shell pwd)
modules:
? ? ? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
? ? ? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
? ? ? ? rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
? ? obj-m := sbull.o
endif
將模塊插入內核(2.6.27)
root@linuxidc:/source/workplace/test/sbull_linuxidc# insmod sbull.ko?
用lsmod查看模塊是否成功插入內核
root@linuxidc:/source/workplace/test/sbull_linuxidc# lsmod | grep sbu
sbull ? ? ? ? ? ? ? ? ?13452 ?0?
出現上面結果,說明成功了
用ls查看/dev下是否有sbull設備
root@linuxidc:/source/workplace/test/sbull_linuxidc# ls /dev | grep sbu
sbulla
出現上面結果,說明有了,如果沒有,用命令
mknod /dev/sbulla b 254 0
手動創建
至此,已經有一個塊設備了
下面用fdisk對虛擬塊設備分區
root@linuxidc:/source/workplace/test/sbull_linuxidc# fdisk /dev/sbulla?
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x14d0973f.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
Command (m for help): n ? ? ? ?這里選擇n,新建
Command action
? ?e ? extended
? ?p ? primary partition (1-4) ?這里選p,主分區
p
Partition number (1-4): 1 ? 這里選1,第一個分區
First cylinder (1-400, default 1): 1
Last cylinder, +cylinders or +size{K,M,G} (1-400, default 400):?
Using default value 400
Command (m for help): w ?這里選w,保存并推出
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
接著將其格式化為ext2
root@linuxidc:/source/workplace/test/sbull_linuxidc# mkfs.ext2 /dev/sbulla1?
mke2fs 1.41.3 (12-Oct-2008)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
3200 inodes, 12792 blocks
639 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=13107200
2 block groups
8192 blocks per group, 8192 fragments per group
1600 inodes per group
Superblock backups stored on blocks:?
? ? ? ? 8193
Writing inode tables: done ? ? ? ? ? ? ? ? ? ? ? ? ? ?
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 29 mounts or
180 days, whichever comes first. ?Use tune2fs -c or -i to override.
新建一個文件夾,作為此處模擬的塊設備的掛載點
root@linuxidc:/source/workplace/test/sbull_linuxidc# ls /mnt/
hgfs ?initrd
root@linuxidc:/source/workplace/test/sbull_linuxidc# mkdir /mnt/sbulla1
掛載
root@linuxidc:/source/workplace/test/sbull_linuxidc# mount /dev/sbulla1 /mnt/sbulla1
進入目錄,新建一個文件,測試一下
root@linuxidc:/source/workplace/test/sbull_linuxidc# cd /mnt/sbulla1/
root@linuxidc:/mnt/sbulla1# ls
lost+found
root@linuxidc:/mnt/sbulla1# echo hi > hello.c
root@linuxidc:/mnt/sbulla1# ls
hello.c ?lost+found
root@linuxidc:/mnt/sbulla1# cat hello.c?
hi
root@linuxidc:/mnt/sbulla1#
========
linux下的塊設備驅動(一)
塊設備的驅動比字符設備的難,這是因為塊設備的驅動和內核的聯系進一步增大,但是同時塊設備的訪問的幾個基本結構和字符還是有相似之處的。
有一句話必須記住:對于存儲設備(硬盤~~帶有機械的操作)而言,調整讀寫的順序作用巨大,因為讀
寫連續的扇區比分離的扇區快。
但是同時:SD卡和U盤這類設備沒有機械上的限制,所以像上面說的進行連續扇區的調整顯得就沒有必要
了。
?
先說一下對于硬盤這類設備的簡單的驅動。
在linux的內核中,使用gendisk結構來表示一個獨立的磁盤設備或者分區。這個結構中包含了磁盤的主
設備號,次設備號以及設備名稱。
在國嵌給的歷程中,對gendisk這個結構體的填充是在simp_blkdev_init函數中完成的。在對gendisk這
個結構填充之前要對其進行分配空間。具體代碼如下:
simp_blkdev_disk = alloc_disk(1);
? ? ? ? if (!simp_blkdev_disk) {
? ? ? ? ? ? ? ? ret = -ENOMEM;
? ? ? ? ? ? ? ? goto err_alloc_disk;
? ? ? ? }
這里的alloc_disk函數是在內核中實現的,它后面的參數1代表的是使用次設備號的數量,這個數量是不
能被修改的。
在分配好了關于gendisk的空間以后就開始對gendisk里面的成員進行填充。具體代碼如下:
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME); //宏定義simp_blkdev
? ? ? ? simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR; //主設備號
? ? ? ? simp_blkdev_disk->first_minor = 0; //次設備號
? ? ? ? simp_blkdev_disk->fops = &simp_blkdev_fops; //主要結構
? ? ? ? simp_blkdev_disk->queue = simp_blkdev_queue;
? ? ? ? set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9); //宏定義(16*1024*1024),實際
上就是這個結構體。
在填充好gendisk這個結構以后向內核中 注冊這個磁盤設備。具體代碼如下:
add_disk(simp_blkdev_disk);
在LDD中說,想內核中注冊設備的必須在gendisk這個結構體已經填充好了以后,我們以前的字符設備的
時候也是這么做的,不知道為什么LDD在這里強調了這個。
當不需要一個磁盤的時候要釋放gendisk,釋放部分的代碼在函數simp_blkdev_exit中實現的。具體的釋
放代碼如下:
del_gendisk(simp_blkdev_disk);
在simp_blkdev_exit中同時還有put_disk(simp_blkdev_disk),這個是用來進行操作gendisk的引用計數
。simp_blkdev_exit還實現了blk_cleanup_queue清除請求隊列的這個函數。終于說到請求隊列了。
?
在說等待隊列之前先要明確幾個概念:
①用戶希望對硬盤數據做的事情叫做請求,這個請求和IO請求是一樣的,所以IO請求來自于上層。
②每一個IO請求對應內核中的一個bio結構。
③IO調度算法可以將連續的bio(也就是用戶的對硬盤數據的相鄰簇的請求)合并成一個request。
④多個request就是一個請求隊列,這個請求隊列的作用就是驅動程序響應用戶的需求的隊列。
?請求隊列在國嵌的程序中的simp_blkdev_queue
下面先說一下硬盤這類帶有機械的存儲設備的驅動。
這類驅動中用戶的IO請求對應于硬盤上的簇可能是連續的,可能是不連續的,連續的當然好,如果要是
不連續的,那么IO調度器就會對這些BIO進行排序(例如老謝說的電梯調度算法),合并成一個request
,然后再接收請求,再合并成一個request,多個request之后那么我們的請求隊列就形成了,然后就可
以向驅動程序提交了。
在硬盤這類的存儲設備中,請求隊列的初始化代碼如下:
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
老謝說,在這種情況下首先調用的是內核中的make_requst函數,然后再調用自己定義的
simp_blkdev_do_request。追了一下內核代碼,會發現make_requst內核代碼如下所示:
static int make_request(struct request_queue *q, struct bio * bio)
?
具體的就不貼了,不過可以知道這個make_request的作用就是使用IO調度器對多個bio的訪問順序進行了
優化調整合并為一個request。也就是在執行完成了這個函數之后才去正式的執行內核的請求隊列。
合并后的request其實還是一個結構,這個結構用來表征IO的請求,這個結構在內核中有具體的定義。
請求是一個結構,同時請求隊列也是一個結構,這個請求隊列在內核中的結構定義如下:
struct request_queue
{
/*
* Together with queue_head for cacheline sharing
*/
struct list_head queue_head;
struct request *last_merge;
struct elevator_queue *elevator;
/*
* the queue request freelist, one for reads and one for writes
*/
struct request_list rq;
request_fn_proc *request_fn;
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
unplug_fn *unplug_fn;
merge_bvec_fn *merge_bvec_fn;
prepare_flush_fn *prepare_flush_fn;
softirq_done_fn *softirq_done_fn;
rq_timed_out_fn *rq_timed_out_fn;
dma_drain_needed_fn *dma_drain_needed;
lld_busy_fn *lld_busy_fn;
/*
* Dispatch queue sorting
*/
sector_t end_sector;
struct request *boundary_rq;
/*
* Auto-unplugging state
*/
struct timer_list unplug_timer;
int unplug_thresh; /* After this many requests */
unsigned long unplug_delay; /* After this many jiffies */
struct work_struct unplug_work;
struct backing_dev_info backing_dev_info;
/*
* The queue owner gets to use this for whatever they like.
* ll_rw_blk doesn't touch it.
*/
void *queuedata;
/*
* queue needs bounce pages for pages above this limit
*/
gfp_t bounce_gfp;
/*
* various queue flags, see QUEUE_* below
*/
unsigned long queue_flags;
/*
* protects queue structures from reentrancy. ->__queue_lock should
* _never_ be used directly, it is queue private. always use
* ->queue_lock.
*/
spinlock_t __queue_lock;
spinlock_t *queue_lock;
/*
* queue kobject
*/
struct kobject kobj;
/*
* queue settings
*/
unsigned long nr_requests; /* Max # of requests */
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned int nr_batching;
void *dma_drain_buffer;
unsigned int dma_drain_size;
unsigned int dma_pad_mask;
unsigned int dma_alignment;
struct blk_queue_tag *queue_tags;
struct list_head tag_busy_list;
unsigned int nr_sorted;
unsigned int in_flight[2];
unsigned int rq_timeout;
struct timer_list timeout;
struct list_head timeout_list;
struct queue_limits limits;
/*
* sg stuff
*/
unsigned int sg_timeout;
unsigned int sg_reserved_size;
int node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
struct blk_trace *blk_trace;
#endif
/*
* reserved for flush operations
*/
unsigned int ordered, next_ordered, ordseq;
int orderr, ordcolor;
struct request pre_flush_rq, bar_rq, post_flush_rq;
struct request *orig_bar_rq;
struct mutex sysfs_lock;
#if defined(CONFIG_BLK_DEV_BSG)
struct bsg_class_device bsg_dev;
#endif
};
LDD說,請求隊列實現了一個插入接口,這個接口允許使用多個IO調度器,大部分IO調度器批量累計IO請
求,并將它們排列為遞增或者遞減的順序提交給驅動。
多個連續的bio會合并成為一個request,多個request就成為了一個請求隊列,這樣bio的是直接的也是
最基本的請求,bio這個結構的定義如下:
struct bio { sector_t ? ? ? ? ? ?bi_sector;
? ? ? ?struct bio ? ? ? ? ?*bi_next; ? ?/* request queue link */
? ? ? ?struct block_device *bi_bdev; /* target device */
? ? ? ?unsigned long ? ? ? bi_flags; ? ?/* status, command, etc */ unsigned long ? ? ??
bi_rw; ? ? ? /* low bits: r/w, high: priority */
? ? ? ?unsigned int bi_vcnt; ? ? /* how may bio_vec's */
? ? ? ?unsigned int bi_idx; /* current index into bio_vec array */
? ? ? ?unsigned int bi_size; ? ? /* total size in bytes */
? ? ? ?unsigned short bi_phys_segments; /* segments after physaddr coalesce*/ unsigned?
short bi_hw_segments; /* segments after DMA remapping */ unsigned int bi_max; ? ? /* max?
bio_vecs we can hold used as index into pool */ struct bio_vec ? *bi_io_vec; ?/* the actual?
vec list */
? ? ? ?bio_end_io_t *bi_end_io; ?/* bi_end_io (bio) */
? ? ? ?atomic_t bi_cnt; ? ? /* pin count: free when it hits zero */ void ? ? ? ? ??
? *bi_private;
? ? ? ?bio_destructor_t *bi_destructor; /* bi_destructor (bio) */ };
需要注意的是,在bio這個結構中最重要的是bio.vec這個結構。同時還有許多操作bio的宏,這些都是內
核給實現好了的。
請求隊列的實現:
首先使用 while ((req = elv_next_request(q)) != NULL)進行循環檢測,看看到底傳來的IO請求是個
什么。
然后進行讀寫區域的判定:
if ((req->sector + req->current_nr_sectors) << 9
? ? ? ? ? ? ? ? ? ? ? ? > SIMP_BLKDEV_BYTES) {
? ? ? ? ? ? ? ? ? ? ? ? printk(KERN_ERR SIMP_BLKDEV_DISKNAME
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ": bad request: block=%llu, count=%u\n",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (unsigned long long)req->sector,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? req->current_nr_sectors);
//結束本次請求。
? ? ? ? ? ? ? ? ? ? ? ? end_request(req, 0);
? ? ? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? }
在進行讀寫區域的判定的時候涉及到了很多linux的編程習慣。
sector表示要訪問的第一個扇區。
current_nr_sectors表示預計訪問扇區的數目。
這里的左移九位其實就是乘以512。
這樣((req->sector + req->current_nr_sectors) << 9就計算出可以預計要訪問的扇區的大小。進行了
一次判斷。
如果上面的判斷沒有超出范圍,那么就可以對請求的這一部分塊設備進行操作了。
simp_blkdev_disk = alloc_disk(1);
? ? ? ? if (!simp_blkdev_disk) {
? ? ? ? ? ? ? ? ret = -ENOMEM;
? ? ? ? ? ? ? ? goto err_alloc_disk;
? ? ? ? }
? ? ? ? strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
? ? ? ? simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
? ? ? ? simp_blkdev_disk->first_minor = 0;
? ? ? ? simp_blkdev_disk->fops = &simp_blkdev_fops;
? ? ? ? simp_blkdev_disk->queue = simp_blkdev_queue;
? ? ? ? set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
? ? ? ? add_disk(simp_blkdev_disk);
? ? ? ? return 0;
關于gendisk結構的內存分配和成員的填充和硬盤類塊設備是一樣的。
由于SD卡和U盤屬于一類非機械類的設備,所以我們不需要那么復雜的調度算法,也就是不需要把io請求
進行排序,所以我們需要自己為自己分配一個請求隊列。具體代碼如下:
?
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
需要注意一下的是在硬盤類塊設備的驅動中這個函數的原型是blk_init_queue
(simp_blkdev_do_request, NULL);
在這種情況下其實并沒有調用內核的make_request(這個函數的功能上文說過),也就是說我們接下來
要綁定的simp_blkdev_make_request這個函數和make_request是同一級別的函數。這里就沒有涉及到算
法調度的問題了。
然后需要進行的工作是:綁定制造請求函數和請求隊列。具體代碼如下:
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
把gendisk的結構成員都添加好了以后就可以執行add_disk(simp_blkdev_disk);這個函數把這塊分區添
加進內核了。
整體列出制造請求部分的函數。
//這個條件是在判斷當前正在運行的內核版本。
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
? ? ? ? ? ? ? ? bio_endio(bio, 0, -EIO);
#else
? ? ? ? ? ? ? ? bio_endio(bio, -EIO);
#endif
? ? ? ? ? ? ? ? return 0;
? ? ? ? }
? ? ? ? dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
//遍歷
? ? ? ? bio_for_each_segment(bvec, bio, i) {
? ? ? ? ? ? ? ? void *iovec_mem;
? ? ? ? ? ? ? ? switch (bio_rw(bio)) {
? ? ? ? ? ? ? ? case READ:
? ? ? ? ? ? ? ? case READA:
? ? ? ? ? ? ? ? ? ? ? ? iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
? ? ? ? ? ? ? ? ? ? ? ? memcpy(iovec_mem, dsk_mem, bvec->bv_len);
? ? ? ? ? ? ? ? ? ? ? ? kunmap(bvec->bv_page);
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? case WRITE:
? ? ? ? ? ? ? ? ? ? ? ? iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
? ? ? ? ? ? ? ? ? ? ? ? memcpy(dsk_mem, iovec_mem, bvec->bv_len);
? ? ? ? ? ? ? ? ? ? ? ? kunmap(bvec->bv_page);
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? ? ? printk(KERN_ERR SIMP_BLKDEV_DISKNAME
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ": unknown value of bio_rw: %lu\n",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
? ? ? ? ? ? ? ? ? ? ? ? bio_endio(bio, 0, -EIO);
#else
? ? ? ? ? ? ? ? ? ? ? ? bio_endio(bio, -EIO);
#endif
? ? ? ? ? ? ? ? ? ? ? ? return 0;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? dsk_mem += bvec->bv_len;
? ? ? ? }
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
? ? ? ? bio_endio(bio, bio->bi_size, 0);
#else
? ? ? ? bio_endio(bio, 0);
#endif
? ? ? ? return 0;
}
========
?Linux塊設備驅動程序原理
?1.4 ?塊設備驅動程序
1.4.1 ?Linux塊設備驅動程序原理(1)
顧名思義,塊設備驅動程序就是支持以塊的方式進行讀寫的設備。塊設備和字符設備最大的區別在于讀
寫數據的基本單元不同。塊設備讀寫數據的基本單元為塊,例如磁盤通常為一個sector,而字符設備的
基本單元為字節。從實現角度來看,字符設備的實現比較簡單,內核例程和用戶態API一一對應,這種映
射關系由字符設備的file_operations維護。塊設備接口則相對復雜,讀寫API沒有直接到塊設備層,而
是直接到文件系統層,然后再由文件系統層發起讀寫請求。
block_device結構代表了內核中的一個塊設備。它可以表示整個磁盤或一個特定的分區。當這個結構代
表一個分區時,它的bd_contains成員指向包含這個分區的設備,bd_part成員指向設備的分區結構。當
這個結構代表一個塊設備時,bd_disk成員指向設備的gendisk結構。
struct block_device { ?
? ? dev_t ? ? ? ? ? bd_dev; ?
? ? struct inode * ?bd_inode; ? /*分區結點*/ ?
? ? int ? ? ? ? bd_openers; ?
? ? struct semaphore ? ?bd_sem; /*打開/關閉鎖*/ ?
? ? struct semaphore ? ?bd_mount_sem; ? /* 加載互斥鎖*/ ?
? ? struct list_head ? ?bd_inodes; ?
? ? void * ? ? ?bd_holder; ?
? ? int ? ? ? ? bd_holders; ?
? ? 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; ?
}; ?
gendisk是一個單獨的磁盤驅動器的內核表示。內核還使用gendisk來表示分區。
struct gendisk { ?
? ? int major; ? ? ? ? ?//主設備號 ?
? ? int first_minor; ? ??
? ? int minors; ? ? ? ? //最大的次設備號數量,如果設備不能分區,該值為1 ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ??
? ? char disk_name[32]; //主設備名 ?
? ? struct hd_struct **part; ? ?//分區信息,有minors個 ?
? ? struct block_device_operations *fops;//設備操作 ?
? ? struct request_queue *queue; ? ?//設備管理I/O請求 ?
? ? void *private_data; ?
? ? sector_t capacity; ?
? ? int flags; ?
? ? char devfs_name[64]; ?
? ? int number; ?
? ? struct device *driverfs_dev; ?
? ? struct kobject kobj; ?
? ? struct timer_rand_state *random; ?
? ? int policy; ?
? ? atomic_t sync_io; ? ??
? ? unsigned long stamp, stamp_idle; ?
? ? int in_flight; ?
#ifdef ?CONFIG_SMP ?
? ? struct disk_stats *dkstats; ?
#else ?
? ? struct disk_stats dkstats; ?
#endif ?
}; ?
gendisk結構的操作函數包括以下幾個:
struct gendisk *alloc_disk(int minors); ? ? //分配磁盤 ?
void add_disk(struct gendisk *disk); ? ? ? ?//增加磁盤信息 ?
void unlink_gendisk(struct gendisk *disk) ? //刪除磁盤信息 ?
void delete_partition(struct gendisk *disk, int part); ?//刪除分區 ?
void add_partition(struct gendisk *disk, int part, sector_t start, sector_t len, int flags)
;//添加分區 ?
1.4.1 ?Linux塊設備驅動程序原理(2)
block_device_operations結構是塊設備對應的操作接口,是連接抽象的塊設備操作與具體塊設備操作之
間的樞紐。
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; ?
}; ?
block_device_operations并不能完全提供文件操作全部的API,實際上只提供了open、release等函數,
其他的文件操作依賴于def_blk_fops:
const struct file_operations def_blk_fops = { ?
? ? .open ? = blkdev_open, ?
? ? .release ? ?= blkdev_close, ?
? ? .llseek = block_llseek, ?
? ? .read ? ? ? = do_sync_read, ?
? ? .write ?= do_sync_write, ?
? ? .aio_read ? = generic_file_aio_read, ?
? ? .aio_write= generic_file_aio_write_nolock, ?
? ? .mmap ? = generic_file_mmap, ?
? ? .fsync ?= block_fsync, ?
? ? .unlocked_ioctl = block_ioctl, ?
#ifdef CONFIG_COMPAT ?
? ? .compat_ioctl ? = compat_blkdev_ioctl, ?
#endif ?
? ? .splice_read ? ? ? ?= generic_file_splice_read, ?
? ? .splice_write ? = generic_file_splice_write, ?
}; ?
系統對塊設備進行讀寫操作時,通過塊設備通用的讀寫操作函數將一個請求保存在該設備的操作請求隊
列(request queue)中,然后調用這個塊設備的底層處理函數,對請求隊列中的操作請求進行逐一執行
。request_queue結構描述了塊設備的請求隊列,該結構定義如下:
struct request_queue ?
{ ?
? ? struct list_head ? ?queue_head; ?
? ? struct request ? ? ?*last_merge; ?
? ? elevator_t ? ? ?elevator; ?
? ? /*請求隊列列表*/ ?
? ? struct request_list ? ? rq; ?
? ? request_fn_proc ? ? *request_fn; ?
? ? merge_request_fn ? ?*back_merge_fn; ?
? ? merge_request_fn ? ?*front_merge_fn; ?
? ? merge_requests_fn ? *merge_requests_fn; ?
? ? make_request_fn ? ? *make_request_fn; ?
? ? prep_rq_fn ? ? ? ? ?*prep_rq_fn; ?
? ? unplug_fn ? ? ? ? ? *unplug_fn; ?
? ? merge_bvec_fn ? ? ? *merge_bvec_fn; ?
? ? activity_fn ? ? ? ? *activity_fn; ?
? ? /*自動卸載狀態*/ ?
? ? struct timer_list ? unplug_timer; ?
? ? int ? ? ? ? unplug_thresh; ? ?
? ? unsigned long ? ? ? unplug_delay; ? /*自動卸載延時*/ ?
? ? struct work_struct ?unplug_work; ?
? ? struct backing_dev_info backing_dev_info; ?
? ? void ? ? ? ? ? ? ? ?*queuedata; ?
? ? void ? ? ? ? ? ? ? ?*activity_data; ?
? ? unsigned long ? ? ? bounce_pfn; ?
? ? int ? ? ? ? ? ? bounce_gfp; ?
? ? unsigned long ? ? ? queue_flags;//各種隊列標志 ?
? ? /*保護隊列結構,避免重入*/ ?
? ? spinlock_t ? ? ? ? ?*queue_lock; ?
? ? /* 請求的核心結構*/ ?
? ? struct kobject kobj; ?
? ? /*請求的配置*/ ?
? ? unsigned long ? ? ? nr_requests; ? ?/* 請求的最大數*/ ?
? ? unsigned int ? ? ? ?nr_congestion_on; ?
? ? unsigned int ? ? ? ?nr_congestion_off; ?
? ? unsigned short ? ? ?max_sectors; ?
? ? unsigned short ? ? ?max_phys_segments; ?
? ? unsigned short ? ? ?max_hw_segments; ?
? ? unsigned short ? ? ?hardsect_size; ?
? ? unsigned int ? ? ? ?max_segment_size; ?
? ? unsigned long ? ? ? seg_boundary_mask; ?
? ? unsigned int ? ? ? ?dma_alignment; ?
? ? struct blk_queue_tag ? ?*queue_tags; ?
? ? atomic_t ? ? ? ?refcnt; ?
? ? unsigned int ? ? ? ?in_flight; ?
? ? /*sg 參數配置*/ ?
? ? unsigned int ? ? ? ?sg_timeout; ?
? ? unsigned int ? ? ? ?sg_reserved_size; ?
}; ?
請求隊列相關的處理函數包括:
//創建隊列時提供了一個自旋鎖。 ?
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock); ?
//獲得隊列中第一個未完成的請求。 ?
struct request *elv_next_request(request_queue_t *q); ?
void end_request(struct request *req, int uptodate);//請求完成 ?
void blk_stop_queue(request_queue_t *queue); //停止請求 ?
void blk_start_queue(request_queue_t *queue); //開始請求 ?
void blk_cleanup_queue(request_queue_t *);//清除請求隊列?
1.4.2 ?簡單的塊設備驅動程序實例
向內核注冊和注銷一個塊設備可使用如下函數:
int register_blkdev(unsigned int major, const char *name); ?
int unregister_blkdev(unsigned int major, const char *name);?
例1.10 ?簡單的塊設備驅動程序實例
代碼見光盤\src\1drivermodel\1-10block。核心代碼如下所示:
static struct request_queue *Queue; ?
//自定義塊設備結構 ?
static struct simpleblockdevice ??
{ ?
? ? unsigned long size; ?
? ? spinlock_t lock; ?
? ? u8 *data; ?
? ? struct gendisk *gd; ?
} Device; ?
//處理I/O請求 ?
static void simpleblocktransfer(struct simpleblockdevice *dev, unsigned long sector, ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? unsigned long nsect, char *buffer, int write) ?
{ ?
? ? unsigned long offset = sector*hardsect_size; ?
? ? unsigned long nbytes = nsect*hardsect_size; ?
? ? //判斷I/O請求是否超出范圍 ?
? ? if ((offset + nbytes) > dev->size) ?
? ? { ?
? ? ? ? printk (KERN_NOTICE "sbd: Beyond-end write (%ld %ld)\n", offset, nbytes); ?
? ? ? ? return; ?
? ? } ?
? ? if (write) ?
? ? ? ? memcpy(dev->data + offset, buffer, nbytes); ?
? ? else ?
? ? ? ? memcpy(buffer, dev->data + offset, nbytes); ?
} ?
//簡單請求處理 ?
static void simpleblockrequest(struct request_queue *q) ?
{ ?
? ? struct request *req; ?
? ? //獲取下一個請求 ?
? ? while ((req = elv_next_request(q)) != NULL) ??
? ? { ?
? ? ? ? if (! blk_fs_request(req)) ??
? ? ? ? { ?
? ? ? ? ? ? printk (KERN_NOTICE "Skip non-CMD request\n"); ?
? ? ? ? ? ? end_request(req, 0); ?
? ? ? ? ? ? continue; ?
? ? ? ? } ?
? ? ? ? simpleblocktransfer(&Device, req->sector, req->current_nr_sectors, ?
? ? ? ? ? ? req->buffer, rq_data_dir(req)); ?
? ? ? ? end_request(req, 1); ?
? ? } ?
} ?
//簡單的塊設備ioctl函數 ?
int simpleblockioctl (struct inode *inode, struct file *filp,unsigned int cmd, unsigned?
long arg) ?
{ ?
? ? long size; ?
? ? struct hd_geometry geo; ?
? ? switch(cmd) ??
? ? { ?
? ? //獲取磁盤信息 ?
? ? case HDIO_GETGEO: ?
? ? ? ? size = Device.size*(hardsect_size/KERNEL_SECTOR_SIZE); ?
? ? ? ? geo.cylinders = (size & ~0x3f) >> 6; ?
? ? ? ? geo.heads = 4; ?
? ? ? ? geo.sectors = 16; ?
? ? ? ? geo.start = 4; ?
? ? ? ? if (copy_to_user((void *) arg, &geo, sizeof(geo))) ?
? ? ? ? ? ? return -EFAULT; ?
? ? ? ? return 0; ?
? ? } ?
? ? return -ENOTTY; /* 未知命令 */ ?
} ?
//設備操作結構 ?
static struct block_device_operations simpleblockops = { ?
? ? .owner ? ? ? ? ? = THIS_MODULE, ?
? ? .ioctl ? ? ? = simpleblockioctl?
}; ?
static int __init simpleblockinit(void) ?
{ ?
? ? Device.size = nsectors*hardsect_size; ?
? ? spin_lock_init(&Device.lock); ?
? ? Device.data = vmalloc(Device.size); ?
? ? if (Device.data == NULL) ?
? ? ? ? return -ENOMEM; ?
? ? //初始化請求隊列,配置處理函數為sbd_request ?
? ? Queue = blk_init_queue(simpleblockrequest, &Device.lock); ?
? ? if (Queue == NULL) ?
? ? ? ? goto out; ?
? ? blk_queue_hardsect_size(Queue, hardsect_size); ?
? ? //注冊塊設備 ?
? ? major_num = register_blkdev(major_num, "sbd"); ?
? ? if (major_num <= 0) { ?
? ? ? ? printk(KERN_WARNING "sbd: unable to get major number\n"); ?
? ? ? ? goto out; ?
? ? } ?
? ? Device.gd = alloc_disk(16); ?
? ? if (! Device.gd) ?
? ? ? ? goto out_unregister; ?
? ? Device.gd->major = major_num; ?
? ? Device.gd->first_minor = 0; ?
? ? Device.gd->fops = &simpleblockops; ?
? ? Device.gd->private_data = &Device; ?
? ? strcpy (Device.gd->disk_name, "sbd0"); ?
? ? //配置容量 ?
? ? set_capacity(Device.gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE)); ?
? ? Device.gd->queue = Queue; ?
? ? add_disk(Device.gd); ?
? ? return 0; ?
out_unregister: ?
? ? unregister_blkdev(major_num, "sbd"); ?
out: ?
? ? vfree(Device.data); ?
? ? return -ENOMEM; ?
} ?
static void __exit simpleblockexit(void) ?
{ ?
? ? del_gendisk(Device.gd); ?
? ? put_disk(Device.gd); ?
? ? unregister_blkdev(major_num, "sbd"); ?
? ? blk_cleanup_queue(Queue); ?
? ? vfree(Device.data); ?
} ?
module_init(simpleblockinit); ?
module_exit(simpleblockexit); ?
運行結果如下:
[root@/home]#cat /proc/filesystems ?
nodev ? sysfs ?
nodev ? rootfs ?
nodev ? bdev ?
nodev ? proc ?
nodev ? binfmt_misc ?
nodev ? debugfs ?
nodev ? securityfs ?
nodev ? sockfs ?
nodev ? usbfs ?
nodev ? pipefs ?
nodev ? anon_inodefs ?
nodev ? futexfs ?
nodev ? tmpfs ?
nodev ? inotifyfs ?
? ? ? ? ext3 ?
? ? ? ? cramfs ?
nodev ? ramfs ?
? ? ? ? msdos ?
? ? ? ? vfat ?
? ? ? ? iso9660 ?
nodev ? nfs ?
nodev ? nfs4 ?
nodev ? mqueue ?
nodev ? rpc_pipefs ?
[root@/home]#insmod demo.ko ?
?sbd0: unknown partition table ?
[root@/home]#mknod /dev/sbd b 253 0 ?
[root@/home]#./mkfs.ext3 /dev/sbd ?
mke2fs 1.40.9 (27-Apr-2008) ?
Filesystem label= ?
OS type: Linux ?
Block size=1024 (log=0) ?
Fragment size=1024 (log=0) ?
1280 inodes, 5120 blocks ?
256 blocks (5.00%) reserved for the super user ?
First data block=1?
Maximum filesystem blocks=5242880?
1 block group ?
8192 blocks per group, 8192 fragments per group ?
1280 inodes per group ?
?
Writing inode tables: done ?
Creating journal (1024 blocks): done ?
Writing superblocks and filesystem accounting information: done ?
?
This filesystem will be automatically checked every 39 mounts or ?
180 days, whichever comes first. ?Use tune2fs -c or -i to override. ?
[root@/home]#mount -t ext3 /dev/sbd /mnt/u ?
kjournald starting. ?Commit interval 5 seconds ?
EXT3 FS on sbd0, internal journal ?
EXT3-fs: mounted filesystem with ordered data mode. ?
[root@/home]#df ?
Filesystem ? ? ? ? ? 1k-blocks ? ? ?Used Available Use% Mounted on ?
rootfs ? ? ? ? ? ? ? ? 2063504 ? 1191136 ? ?767548 ?61% / ?
/dev/root ? ? ? ? ? ? ?2063504 ? 1191136 ? ?767548 ?61% / ?
/dev/sbd ? ? ? ? ? ? ? ? ?4955 ? ? ?1063 ? ? ?3636 ?23% /mnt/u ?
[root@/home]#cd /mnt/u ?
[root@/mnt/u]#ls ?
lost+found ?
========
總結
以上是生活随笔為你收集整理的Linux块设备驱动总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux安全学习总结
- 下一篇: linux内核技术文章