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