1. 程式人生 > >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);