一、重要知識點
1.塊設備和字符設備的區別
?????? a.字符設備可訪問字節大小數據,塊設備只能訪問固定大小的整塊數據(一般為512字節)。
?????? b.塊設備支持隨機訪問,字符設備只能順序訪問。
2.塊設備子系統體系架構
?????? 如圖
從上到下依次為VFS虛擬文件系統、各種類型的磁盤系統、通用塊設備層、I/O調度層(優化訪問上層的請求(讀寫請求))、塊設備驅動層、塊設備硬件層。
我們編寫驅動程序要完成的是調用I/O調度層提供的相關接口對塊設備硬件層進行讀寫及相關操作。
3.塊設備驅動程序注冊
?????? 塊設備驅動程序使用
int register_blk_dev(unsigned int major, const char*name)向內核注冊。如果major為0,則內核為止分配一個主設備號。在內核2.6中,對register_blk_dev的調用時完全可選的,該接口只做了兩件事:一是動態分配主設備號,二是在/proc/devices中創建一個入口項。大多數驅動仍會調用,因為這是一個傳統。
4.注冊磁盤
雖然register_blk能夠獲得主設備號,當它并不能讓系統使用任何磁盤,因此為了管理獨立的磁盤,必須使用另外一個單獨的注冊接口
void add_disk(struct gendist *gd)
下面我們再來看看參數struct gendisk結構
5.磁盤描述結構struct gendisk
內核使用gendisk結構來表示一個獨立的磁盤設備
struct gendisk
{
?????? int major;????? //主設備號
?????? intfirst_minor;??? //第一個次設備號
?????? intminors;??? //最大次設備數,如果不能分區則為1
?????? chardisk_name[32];?? //設備名稱
?????? structhd_struct **part;???? //磁盤上的分區信息
?????? structblock_device_operations *fops;? //塊設備操作結構體
?????? structrequest_queue *queue;?? //請求隊列
?????? void*private_data;??? //私有數據
?????? sector_tcapacity; ???? //扇區數,512字節為1扇區
?????? …………
}
我們再來看塊設備的操作結構體struct block_device_operations
6.塊設備操作結構體struct block_device_operations
struct 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);
?????? int?? (*media_changed)(struct gendisk *)
?????? int (*revalidate_disk)(struct gendisk *)
?????? int (*getgeo)(structblock_device *, struct hd_geometry*);
?????? structmodule *owner;
}
int?? (*open)(structinode *, ?struct file*);當系統執行mount、創建分區、在分區上創建文件系統,運行文件系統檢查程序等時被調用。
int(*release)(struct inode*, struct file *);當系統執行umount等其他關閉設備操作時被調用。
int (*ioctl)(structinode*,?????? struct file *, ?unsigned, unsigned long);用來提供一些特殊的操作,比如說查詢磁盤物理信息等。
int?? (*media_changed)(structgendisk *)
int (*revalidate_disk)(structgendisk *)
這兩個用來支持可移動介質。上層調用media_change以檢查介質是否被改變如改變將返回非0值。
在介質改變后,上層將調用revalidate_disk來重新對新的介質進行一些初始化工作。
int (*getgeo)(struct block_device *, structhd_geometry*);用來填充驅動器信息。
在這里我們就發現塊設備和字符設備驅動的區別了,該操作結構體中沒有讀寫函數。因為塊設備的讀寫操作是與I/O調度層的I/O請求綁定在一起的,一旦I/O調度層有I/O請求就會調用塊設備的讀寫操作函數。下面開始介紹塊設備如何響應I/O請求。
7.I/O請求
當內核以文件系統,虛擬子系統或者調用形式從塊設備輸入、輸出塊數據是,它將使用一個bio結構,用來描述這個操作。該結構會被傳遞給I/O調度層,I/O調度層會把它合并到一個已經存在的request結構中,或者根據需要再創建一個request結構中。為什么要這樣做呢?因為內核為了使提高塊設備的讀寫效率,它會將對相鄰的扇區進行操作的多個請求(bio)合并成一個request。同樣為了提高塊設備的讀寫效率,I/O調度層又將每個request進行一些排序處理組成一個隊列(request_que_t),使驅動以某種順序去讀取request_que_t的每一個request,然后進行塊設備的實際讀寫操作。綜上,bio是最基本的請求,然后內核會將對相鄰扇區訪問的bio組成一個request,接著再把request按照某種調度算法排序組成一個隊列request_que_t。我們驅動程序要實現的就是提取每一個quest,然后獲取其中的信息進行讀寫操作。
但是有一個問題,并不是所有塊設備都像磁盤設備那樣扇區之類的結構,比如說flash,ram盤之類的,對這一類的設備進行上述的I/O調度反而會使效率降低,所有內核又提供了實現I/O請求的另外一種方式,就是繞過請求隊列,也就是繞過request和request_que_t直接對bio結構進行處理。
下面我們分別來介紹實現I/O請求響應的兩種方式。
8.響應I/O請求實現方式一:request隊列方式
request數據結構
struct request
{
struct list_head queuelist; //形成request鏈表的鏈表結構
sector_t sector;??? //要操作的首個扇區
unsigned long nr_sectors; //要操作的扇區數
struct bio *bio;??? //請求的bio鏈表頭?
struct bio *biotail;????? //請求的bio結構體的鏈表尾
……
}
操作請求隊列的函數
初始化請求隊列
struct request_queue *blk_init_queue(request_fn_proc*rfn, spinlock_t *lock)
rfn為請求隊列的響應函數,這樣就將驅動響應函數和I/O請求綁定到了一起。
lock是訪問隊列權限的自旋鎖。
將該函數的返回值賦給gendisk結構的queue成員,這樣就I/O調度層就會把組織好的request形成的隊列填充到queue里面,然后調用rfn來響應對該塊設備的I/O請求。rfn的原型為
typedef void (request_fn_proc) (request_que_t *q),它只有一個參數就是request_que_t隊列。
清除請求隊列
void blk_cleanup_queue(request_queue_t *q)
當塊設備驅動模塊卸載時調用此函數。
返回隊列中下一個要處理的的請求(request):
?????? struct request *elv_next_request(request_queue_t *queue)
?????? 并刪除一個請求
?????? void blkdev_dequeue_request(struct request *req)
9.響應I/O請求實現方式二:直接響應bio方式
bio結構的核心是一個名為bi_io_vec數組,它是由下面的結構組成的:
struct bio_vec {
struct page *bv_page;
?????? unsignedint bv_len;
?????? unsignedint bv_offset;
}
它表示了一個映射的物理頁的信息。內核使用bio_for_each_segment(bvec,bio, segno)來遍歷每個bio_vec結構。bvec是指當前的dio_vec入口, segno是段號。
?????? 驅動是程序使用blk_alloc_queue函數分配一個請求隊列來告訴塊設備子系統,I/O請求響應的是使用bio方式。
?????? request_queue_t *blk_alloc_queue(int flags)
?????? 該函數與blk_init_queue的不同之處在于它并未真正實現一個保存的請求隊列。flag是一系列標志用來為隊列分配內存。通常是GFP_KERNEL。一旦擁有了隊列,將它與make_request將響應函數傳遞給blk_queue_make_request:
?????? void blk_queue_make_request(request_queue_t *queue,mak_request_fn *func);
?????? 請求響應函數的原型為
?????? typedef int (make_request_fn) (request *q, struct bio *bio)
?????? 可以看出內核傳遞了一個bio結構給I/O請求響應函數,func可以讀取bio的信息進行塊設備的讀寫操作。
二、驅動代碼分析
該驅動將一段內存模擬成一個塊設備驅動,并使用bio方式實現I/O請求的響應
?
?
#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>?? #include<linux/version.h>?? ??? ??? #defineSIMP_BLKDEV_DEVICEMAJOR???????COMPAQ_SMART2_MAJOR?? #defineSIMP_BLKDEV_DISKNAME???????"simp_blkdev"?? #define?SIMP_BLKDEV_BYTES????????(16*1024*1024)?? ??? static?struct?request_queue*simp_blkdev_queue;?? static?struct?gendisk*simp_blkdev_disk;?? unsigned?charsimp_blkdev_data[SIMP_BLKDEV_BYTES];?? ??? static?intsimp_blkdev_make_request(struct?request_queue?*q,?struct?bio?*bio)?? {?? ????????struct?bio_vec?*bvec;?? ????????int?i;?? ????????void?*dsk_mem;?? ??? ??????????????//判斷要訪問的數據是否大于塊設備最大容量,如果是則調用bio_endio通知內核完成請求。?? ????????if?((bio->bi_sector?<<?9)?+bio->bi_size?>?SIMP_BLKDEV_BYTES)?{?? ????????????????printk(KERN_ERRSIMP_BLKDEV_DISKNAME?? ????????????????????????":?bad?request:block=%llu,?count=%u\n",?? ????????????????????????(unsigned?longlong)bio->bi_sector,?bio->bi_size);?? #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鏈表中的每一個bio_vec元素,然后判斷是讀還是寫操作進行數據傳輸,傳輸完成后調用bio_endio通知內核完成請求。?? ????????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_ERRSIMP_BLKDEV_DISKNAME?? ????????????????????????????????":?unknownvalue?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;?? }?? ??? ??? struct?block_device_operationssimp_blkdev_fops?=?{?? ????????.owner????????????????=?THIS_MODULE,?? };?? ??? static?int?__initsimp_blkdev_init(void)?? {?? ????????int?ret;?? ??????????????? ??????????????//分配響應隊列?? ????????simp_blkdev_queue?=blk_alloc_queue(GFP_KERNEL);?? ????????if?(!simp_blkdev_queue)?{?? ????????????????ret?=?-ENOMEM;?? ????????????????goto?err_alloc_queue;?? ????????}?? ???????? ??????????????//將內核響應隊列和I/O請求響應函數綁定?? ???????blk_queue_make_request(simp_blkdev_queue,?simp_blkdev_make_request);?? ??? ??????????????//分配一個gendisk結構?? ????????simp_blkdev_disk?=?alloc_disk(1);?? ????????if?(!simp_blkdev_disk)?{?? ????????????????ret?=?-ENOMEM;?? ????????????????goto?err_alloc_disk;?? ????????}?? ??? ??????????????//初始化gendisk結構?? ????????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;//初始化I/O請求隊列?? ????????set_capacity(simp_blkdev_disk,SIMP_BLKDEV_BYTES>>9);?? ????????add_disk(simp_blkdev_disk);//向內核添加一個gendisk對象?? ??? ????????return?0;?? ??? err_alloc_disk:?? ????????blk_cleanup_queue(simp_blkdev_queue);?? err_alloc_queue:?? ????????return?ret;?? }?? ??? ??? static?void?__exitsimp_blkdev_exit(void)?? {?? ????????del_gendisk(simp_blkdev_disk);//注銷gendisk對象?? ????????put_disk(simp_blkdev_disk);//減小gendisk引用計數?? ????????blk_cleanup_queue(simp_blkdev_queue);//清楚請求隊列?? }?? ??? module_init(simp_blkdev_init);?? module_exit(simp_blkdev_exit);?? #include
#include#include #include #include /* printk() */#include /* kmalloc() */#include /* everything... */#include /* error codes */#include #include /* size_t */#include /* O_ACCMODE */#include /* HDIO_GETGEO */#include#include#include #include#include /*invalidate_bdev */#include #include#defineSIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR#defineSIMP_BLKDEV_DISKNAME "simp_blkdev"#define SIMP_BLKDEV_BYTES (16*1024*1024)static struct request_queue*simp_blkdev_queue;static struct gendisk*simp_blkdev_disk;unsigned charsimp_blkdev_data[SIMP_BLKDEV_BYTES];static intsimp_blkdev_make_request(struct request_queue *q, struct bio *bio){struct bio_vec *bvec;int i;void *dsk_mem;//判斷要訪問的數據是否大于塊設備最大容量,如果是則調用bio_endio通知內核完成請求。if ((bio->bi_sector << 9) +bio->bi_size > SIMP_BLKDEV_BYTES) {printk(KERN_ERRSIMP_BLKDEV_DISKNAME": bad request:block=%llu, count=%u\n",(unsigned longlong)bio->bi_sector, bio->bi_size);#if LINUX_VERSION_CODE bi_sector << 9);//遍歷bio鏈表中的每一個bio_vec元素,然后判斷是讀還是寫操作進行數據傳輸,傳輸完成后調用bio_endio通知內核完成請求。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_ERRSIMP_BLKDEV_DISKNAME": unknownvalue of bio_rw: %lu\n",bio_rw(bio));#if LINUX_VERSION_CODE bv_len;}#if LINUX_VERSION_CODE bi_size, 0);#elsebio_endio(bio, 0);#endifreturn 0;}struct block_device_operationssimp_blkdev_fops = {.owner = THIS_MODULE,};static int __initsimp_blkdev_init(void){int ret;//分配響應隊列simp_blkdev_queue =blk_alloc_queue(GFP_KERNEL);if (!simp_blkdev_queue) {ret = -ENOMEM;goto err_alloc_queue;}//將內核響應隊列和I/O請求響應函數綁定blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);//分配一個gendisk結構simp_blkdev_disk = alloc_disk(1);if (!simp_blkdev_disk) {ret = -ENOMEM;goto err_alloc_disk;}//初始化gendisk結構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;//初始化I/O請求隊列set_capacity(simp_blkdev_disk,SIMP_BLKDEV_BYTES>>9);add_disk(simp_blkdev_disk);//向內核添加一個gendisk對象return 0;err_alloc_disk:blk_cleanup_queue(simp_blkdev_queue);err_alloc_queue:return ret;}static void __exitsimp_blkdev_exit(void){del_gendisk(simp_blkdev_disk);//注銷gendisk對象put_disk(simp_blkdev_disk);//減小gendisk引用計數blk_cleanup_queue(simp_blkdev_queue);//清楚請求隊列}module_init(simp_blkdev_init);module_exit(simp_blkdev_exit);
評論
查看更多