1、塊設(shè)備的I/O操作特點(diǎn)
字符設(shè)備與塊設(shè)備的區(qū)別:
塊設(shè)備只能以塊為單位接受輸入和返回輸出,而字符設(shè)備則以字符為單位。
塊設(shè)備對(duì)于I/O請(qǐng)求有對(duì)應(yīng)的緩沖區(qū),因此它們可以選擇以什么順序進(jìn)行響應(yīng),字符設(shè)備無需緩沖區(qū)且直接被讀寫。
字符設(shè)備只能被順序讀寫,而塊設(shè)備可以隨機(jī)讀寫。 但是對(duì)于磁盤等機(jī)械設(shè)備而言,順序的組織塊設(shè)備的訪問可以提高性能
總體而言,塊設(shè)備驅(qū)動(dòng)比字符設(shè)備驅(qū)動(dòng)要復(fù)雜得多,在I/O操作上表現(xiàn)出極大的不同,緩沖、I/O調(diào)度、請(qǐng)求隊(duì)列等都是與塊設(shè)備驅(qū)動(dòng)相關(guān)的概念。
對(duì)于扇區(qū)1、10、3、2的請(qǐng)求被調(diào)整為對(duì)扇區(qū)1、2、3、10的請(qǐng)求。但是對(duì)于SD卡、RAMDISK等非機(jī)械設(shè)備來說則沒必要調(diào)整
2.block_device_operations結(jié)構(gòu)體
1、打開和釋放?
int (*open)(struct inode *inode,struct file *filp); int(*release)(struct inode *inode,struct file *filp); //與字符設(shè)備驅(qū)動(dòng)類似,當(dāng)設(shè)備打開和關(guān)閉的時(shí)候調(diào)用它們
2、io控制
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd, unsigned long arg); //上述函數(shù)是ioctl()系統(tǒng)調(diào)用的實(shí)現(xiàn),塊設(shè)備包含大量的標(biāo)準(zhǔn)請(qǐng)求,這些標(biāo)準(zhǔn)請(qǐng)求由Linux塊設(shè)備層處理,因此大部分塊設(shè)備驅(qū)動(dòng)的ioctl()函數(shù)相當(dāng)短。
3、介質(zhì)改變?
int (*media_changed)(struct gendisk *gd); /*被內(nèi)核調(diào)用來檢查是否驅(qū)動(dòng)器中的介質(zhì)已經(jīng)改變。如果是,則返回一個(gè)非0值。否則返回0.這個(gè)函數(shù)僅適用于支持可移動(dòng)介質(zhì)的驅(qū)動(dòng)器,通常需要在驅(qū)動(dòng)器中增加一個(gè)表示介質(zhì)狀態(tài)是否改變的標(biāo)志變量,非可移動(dòng)設(shè)備的驅(qū)動(dòng)不需要實(shí)現(xiàn)這個(gè)方法。*/
4、使介質(zhì)有效?
int (*revalidate_disk)(struct gendisk *gd);// revalidate_disk()函數(shù)被調(diào)用來響應(yīng)一個(gè)介質(zhì)改變,它給驅(qū)動(dòng)一個(gè)機(jī)會(huì)來進(jìn)行必要的工作以使新介質(zhì)準(zhǔn)備好。
5、獲得驅(qū)動(dòng)器信息
int (*getgeo)(struct block_device *,struct hd_geometry *) ;//該函數(shù)根據(jù)驅(qū)動(dòng)器的幾何信息填充一個(gè)hd_geometry的結(jié)構(gòu)體,包含磁頭、扇區(qū)、柱面等信息。
6、模塊指針?
struct module * owner;// 一個(gè)指向擁有這個(gè)結(jié)構(gòu)體的模塊的指針,它通常被初始化為THIS_MODULE.
3.?gendisk分析
在Linux內(nèi)核中,使用gendisk(通用磁盤)結(jié)構(gòu)體來表示1個(gè)獨(dú)立的磁盤設(shè)備(或分區(qū))。
int major;/* 主設(shè)備號(hào) */int first_minor; /*第1個(gè)次設(shè)備號(hào)*/int minors: //磁盤使用這些成員描述設(shè)備號(hào)。 //最大的次設(shè)備數(shù),如果不能分區(qū),則為1,一個(gè)驅(qū)動(dòng)器至少使用一個(gè)次設(shè)備號(hào)。 //如果驅(qū)動(dòng)器是可被分區(qū)的(大多數(shù)情況下),用戶將要為每個(gè)可能的分區(qū)都分配一個(gè)次設(shè)備號(hào)。minors通常取16,他使得一個(gè)“完整的的磁盤“包含15個(gè)分區(qū)。 某些磁盤驅(qū)動(dòng)程序設(shè)置每個(gè)設(shè)備可使用64個(gè)次設(shè)備號(hào)。char disk_name[32]; //設(shè)置磁盤設(shè)備名字的成員。該名字將顯示在/proc/partitions和sysfs中。struct block_device_operations *fops; //設(shè)置前面所述的各種設(shè)備操作;struct request_queue *queue; //內(nèi)核使用該結(jié)構(gòu)為設(shè)備管理i/o請(qǐng)求;在”請(qǐng)求過程“中詳細(xì)進(jìn)行論述。int flags; //用來描述驅(qū)動(dòng)器狀態(tài)的標(biāo)志(很少使用)。如果用戶設(shè)備包含了可移動(dòng)介質(zhì),其將被設(shè)置為GENHD_FL_REMOVABLE。sector_t capacity; //以512字節(jié)為一個(gè)扇區(qū)時(shí),該驅(qū)動(dòng)器可包含的扇區(qū)數(shù)。void *preivate_data; //塊設(shè)備驅(qū)動(dòng)程序可能使用該成員保存指向其內(nèi)部數(shù)據(jù)的指針。
major、first_minor和minors共同表征了磁盤的主、次設(shè)備號(hào),同一個(gè)磁盤的各個(gè)分區(qū)共享1個(gè)主設(shè)備號(hào),而次設(shè)備號(hào)則不同。
fops為block_device_operations,即上節(jié)描述的塊設(shè)備操作集合。queue是內(nèi)核用來管理這個(gè)設(shè)備的 I/O請(qǐng)求隊(duì)列的指針。
capacity表明設(shè)備的容量,以512個(gè)字節(jié)為單位。private_data可用于指向磁盤的任何私有數(shù)據(jù),用法與字符設(shè)備驅(qū)動(dòng)file結(jié)構(gòu)體的private_data類似。
Linux內(nèi)核提供了一組函數(shù)來操作gendisk,主要包括:
? 分配gendisk
gendisk結(jié)構(gòu)體是一個(gè)動(dòng)態(tài)分配的結(jié)構(gòu)體,它需要特別的內(nèi)核操作來初始化,驅(qū)動(dòng)不能自己分配這個(gè)結(jié)構(gòu)體,而應(yīng)該使用下列函數(shù)來分配gendisk:
struct gendisk *alloc_disk(int minors);
minors 參數(shù)是這個(gè)磁盤使用的次設(shè)備號(hào)的數(shù)量,一般也就是磁盤分區(qū)的數(shù)量,此后minors不能被修改。
? 增加gendisk
gendisk結(jié)構(gòu)體被分配之后,系統(tǒng)還不能使用這個(gè)磁盤,需要調(diào)用如下函數(shù)來注冊(cè)這個(gè)磁盤設(shè)備:
void add_disk(struct gendisk *gd);
特別要注意的是對(duì)add_disk()的調(diào)用必須發(fā)生在驅(qū)動(dòng)程序的初始化工作完成并能響應(yīng)磁盤的請(qǐng)求之后。
? 釋放gendisk
當(dāng)不再需要一個(gè)磁盤時(shí),應(yīng)當(dāng)使用如下函數(shù)釋放gendisk:
void del_gendisk(struct gendisk *gd);
? gendisk引用計(jì)數(shù)
gendisk中包含1個(gè)kobject成員,因此,它是一個(gè)可被引用計(jì)數(shù)的結(jié)構(gòu)體。通過get_disk()和put_disk()函數(shù)可用來操作引用計(jì)數(shù),這個(gè)工作一般不需要驅(qū)動(dòng)親自做。通常對(duì) del_gendisk()的調(diào)用會(huì)去掉gendisk的最終引用計(jì)數(shù),但是這一點(diǎn)并不是一定的。
因此,在del_gendisk()被調(diào)用后,這個(gè)結(jié)構(gòu)體可能繼續(xù)存在。
? 設(shè)置gendisk容量
void set_capacity(struct gendisk *disk, sector_t size);
塊設(shè)備中最小的可尋址單元是扇區(qū),扇區(qū)大小一般是2的整數(shù)倍,最常見的大小是512字節(jié)。扇區(qū)的大小是設(shè)備的物理屬性,扇區(qū)是所有塊設(shè)備的基本單元,塊設(shè)備無法對(duì)比它還小的單元進(jìn)行尋址和操作,不過許多塊設(shè)備能夠一次就傳輸多個(gè)扇區(qū)。雖然大多數(shù)塊設(shè)
備的扇區(qū)大小都是512字節(jié),不過其它大小的扇區(qū)也很常見,比如,很多CD-ROM盤的扇區(qū)都是2K大小。
不管物理設(shè)備的真實(shí)扇區(qū)大小是多少,內(nèi)核與塊設(shè)備驅(qū)動(dòng)交互的扇區(qū)都以512字節(jié)為單位。因此,set_capacity()函數(shù)也以512字節(jié)為單位。
4.塊設(shè)備驅(qū)動(dòng)模塊加載和卸載函數(shù)模板
在塊設(shè)備驅(qū)動(dòng)的模塊加載函數(shù)中通常需要完成如下工作:
① 分配、初始化請(qǐng)求隊(duì)列,綁定請(qǐng)求隊(duì)列和請(qǐng)求函數(shù)。
② 分配、初始化gendisk,給gendisk的major、fops、queue等成員賦值,最后添加gendisk。
③ 注冊(cè)塊設(shè)備驅(qū)動(dòng)。
使用blk_alloc_queue
static int __init xxx_init(void){ //分配gendisk xxx_disks = alloc_disk(1); if (!xxx_disks) { goto out; } /* 塊設(shè)備驅(qū)動(dòng)注冊(cè) 在2.6內(nèi)核中,對(duì) register_blkdev()的調(diào)用完全是可選的,register_blkdev()的功能已隨時(shí)間正在減少,這個(gè)調(diào)用最多只完全2件事: ① 如果需要,分配一個(gè)動(dòng)態(tài)主設(shè)備號(hào)。 ② 在/proc/devices中創(chuàng)建一個(gè)入口。 在將來的內(nèi)核中,register_blkdev()可能會(huì)被去掉。但是目前的大部分驅(qū)動(dòng)仍然調(diào)用它。 */ if (register_blkdev(XXX_MAJOR, "xxx")) { err = - EIO; goto out; } //“請(qǐng)求隊(duì)列”分配 xxx_queue = blk_alloc_queue(GFP_KERNEL); if (!xxx_queue) { goto out_queue; } blk_queue_make_request(xxx_queue, &xxx_make_request); //綁定“制造請(qǐng)求”函數(shù) blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇區(qū)尺寸設(shè)置 //gendisk初始化 xxx_disks->major = XXX_MAJOR; xxx_disks->first_minor = 0; xxx_disks->fops = &xxx_op; xxx_disks->queue = xxx_queue; sprintf(xxx_disks->disk_name, "xxx%d", i); set_capacity(xxx_disks, xxx_size); //xxx_size以512bytes為單位 add_disk(xxx_disks); //添加gendisk return 0; out_queue: unregister_blkdev(XXX_MAJOR, "xxx"); out: put_disk(xxx_disks); blk_cleanup_queue(xxx_queue); return - ENOMEM;}
使用blk_init_queue
static int __init xxx_init(void){ //塊設(shè)備驅(qū)動(dòng)注冊(cè) if (register_blkdev(XXX_MAJOR, "xxx")) { err = - EIO; goto out; } //請(qǐng)求隊(duì)列初始化 xxx_queue = blk_init_queue(xxx_request, xxx_lock); if (!xxx_queue) { goto out_queue; } blk_queue_hardsect_size(xxx_queue, xxx_blocksize); //硬件扇區(qū)尺寸設(shè)置 //gendisk初始化 xxx_disks->major = XXX_MAJOR; xxx_disks->first_minor = 0; xxx_disks->fops = &xxx_op; xxx_disks->queue = xxx_queue; sprintf(xxx_disks->disk_name, "xxx%d", i); set_capacity(xxx_disks, xxx_size *2); add_disk(xxx_disks); //添加gendisk return 0;out_queue: unregister_blkdev(XXX_MAJOR, "xxx");out: put_disk(xxx_disks); blk_cleanup_queue(xxx_queue); return - ENOMEM;}
每個(gè)塊設(shè)備驅(qū)動(dòng)程序的核心是它的請(qǐng)求函數(shù)。
實(shí)際的工作,如設(shè)備的啟動(dòng),都是在這個(gè)函數(shù)中完成。
驅(qū)動(dòng)程序的新能,是這個(gè)操作系統(tǒng)性能的重要組成部分,因此內(nèi)核的塊設(shè)備子系統(tǒng)在編寫的時(shí)候就非常注意性能方面的問題。
塊設(shè)備驅(qū)動(dòng)模塊卸載函數(shù)模板
① 清除請(qǐng)求隊(duì)列。
② 刪除gendisk和對(duì)gendisk的引用。
③ 刪除對(duì)塊設(shè)備的引用,注銷塊設(shè)備驅(qū)動(dòng)。
static void __exit xxx_exit(void){ if (bdev) { invalidate_bdev(xxx_bdev, 1); blkdev_put(xxx_bdev); } del_gendisk(xxx_disks); //刪除gendisk put_disk(xxx_disks); blk_cleanup_queue(xxx_queue[i]); //清除請(qǐng)求隊(duì)列 unregister_blkdev(XXX_MAJOR, "xxx");}
5.塊設(shè)備驅(qū)動(dòng)I/O
request函數(shù)介紹
原型:
void request(request_queue_t *queue);
這個(gè)函數(shù)不能由驅(qū)動(dòng)自己調(diào)用,只有當(dāng)內(nèi)核認(rèn)為是時(shí)候讓驅(qū)動(dòng)處理對(duì)設(shè)備的讀寫等操作時(shí),它才調(diào)用這個(gè)函數(shù)。
請(qǐng)求函數(shù)可以在沒有完成請(qǐng)求隊(duì)列中的所有請(qǐng)求的情況下返回,甚至它1個(gè)請(qǐng)求不完成都可以返回。但是,對(duì)大部分設(shè)備而言,在請(qǐng)求函數(shù)中處理完所有請(qǐng)求后再返回通常是值得推薦的方法。
對(duì)request函數(shù)的調(diào)用是與用戶空間進(jìn)程中的動(dòng)作是完全異步的,因此不能直接對(duì)用戶空間進(jìn)行訪問。
塊設(shè)備驅(qū)動(dòng)請(qǐng)求函數(shù)例程:
static void xxx_request(request_queue_t *q){ struct request *req; while ((req = elv_next_request(q)) != NULL) { struct xxx_dev *dev = req->rq_disk->private_data; if (!blk_fs_request(req)) //不是文件系統(tǒng)請(qǐng)求 { printk(KERN_NOTICE "Skip non-fs request/n"); end_request(req, 0);//通知請(qǐng)求處理失敗 continue; } xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req)); //處理這個(gè)請(qǐng)求 end_request(req, 1); //通知成功完成這個(gè)請(qǐng)求 }}//完成具體的塊設(shè)備I/O操作static void xxx_transfer(struct xxx_dev *dev, unsigned long sector, unsignedlong 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 ; } if (write) { write_dev(offset, buffer, nbytes); //向設(shè)備些nbytes個(gè)字節(jié)的數(shù)據(jù) } else { read_dev(offset, buffer, nbytes); //從設(shè)備讀nbytes個(gè)字節(jié)的數(shù)據(jù) }}void end_request(struct request *req, int uptodate){ /* 當(dāng)設(shè)備已經(jīng)完成1個(gè)I/O請(qǐng)求的部分或者全部扇區(qū)傳輸后, end_that_request_first這個(gè)函數(shù)告知塊設(shè)備層,塊設(shè)備驅(qū)動(dòng)已經(jīng)完成count個(gè)扇區(qū)的傳送 返回值為0表示所有的扇區(qū)已經(jīng)被傳送并且這個(gè)請(qǐng)求完成 */ if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)) { /* 使用塊 I/O 請(qǐng)求的定時(shí)來給系統(tǒng)的隨機(jī)數(shù)池貢獻(xiàn)熵,它不影響塊設(shè)備驅(qū)動(dòng)。 但是,僅當(dāng)磁盤的操作時(shí)間是真正隨機(jī)的時(shí)候(大部分機(jī)械設(shè)備如此),才應(yīng)該調(diào)用它。 */ add_disk_randomness (req->rq_disk); blkdev_dequeue_request (req);//從隊(duì)列中清除這個(gè)請(qǐng)求 end_that_request_last(req);//通知所有正在等待這個(gè)請(qǐng)求完成的對(duì)象請(qǐng)求已經(jīng)完成并回收這個(gè)請(qǐng)求結(jié)構(gòu)體。 } }
請(qǐng)求隊(duì)列
一個(gè)塊設(shè)備請(qǐng)求隊(duì)列是:包含塊設(shè)備I/O請(qǐng)求的序列。
請(qǐng)求隊(duì)列跟蹤未完成的塊設(shè)備的I/O請(qǐng)求,保存了描述設(shè)備所能處理的請(qǐng)求的參數(shù):最大尺寸、在同一個(gè)請(qǐng)求中所包含的獨(dú)立段的數(shù)目、硬件扇區(qū)的大小、對(duì)齊需求等。
請(qǐng)求隊(duì)列實(shí)現(xiàn)了插件接口,以便可以使用多個(gè)I/O調(diào)度器。
I/O調(diào)度器積累了大量的請(qǐng)求,根據(jù)塊索引號(hào)升序(或者降序)排列他們,并按照這個(gè)順序向驅(qū)動(dòng)程序發(fā)送請(qǐng)求。
磁頭從一個(gè)磁盤的末尾移向另一個(gè)磁盤,如同單向電梯一樣,直到每個(gè)請(qǐng)求都得到滿足。
隊(duì)列的創(chuàng)建與刪除
一個(gè)請(qǐng)求隊(duì)列就是一個(gè)動(dòng)態(tài)的數(shù)據(jù)結(jié)構(gòu),該結(jié)構(gòu)必須由塊設(shè)備的I/O子系統(tǒng)創(chuàng)建。
request_queue_t *blk_init_queue(request_fn_proc *request,spinlock_t *lock) ;//該函數(shù)參數(shù)是處理這個(gè)隊(duì)列的request指針和控制訪問隊(duì)列權(quán)限的自旋鎖。void blk_cleanup_queue(request_queue_t *);//刪除隊(duì)列
隊(duì)列中的函數(shù)
返回隊(duì)列中下一個(gè)要處理的請(qǐng)求
struct request *elv_next_request(request_queue_t *queue);
將請(qǐng)求從隊(duì)列中刪除
void blkdev_dequeue_request(struct request *req);
隊(duì)列控制函數(shù)
驅(qū)動(dòng)程序使用塊設(shè)備層到處的一組函數(shù)去控制請(qǐng)求隊(duì)列的操作。
void blk_stop_queue(request_queue_t *queue)void blk_start_queue(request_queue_t *queue) //如果驅(qū)動(dòng)程序進(jìn)入不能處理更多命令的狀態(tài),就會(huì)調(diào)用blk_stop_queue以通知塊設(shè)備層,以暫停調(diào)用request函數(shù)。當(dāng)有能力處理更多請(qǐng)求時(shí),需要調(diào)用blk_start_queue重新開始調(diào)用。void blk_queue_bounce_limit(request_queue_t *queue,u64 dma_addr); //該函數(shù)告訴內(nèi)核驅(qū)動(dòng)程序執(zhí)行DMA所使用的最高物理內(nèi)存。如果一個(gè)請(qǐng)求包含了超越界限的內(nèi)存引用,將使用回彈緩沖區(qū)(bounce buffer)進(jìn)行處理。
請(qǐng)求過程剖析
每個(gè)request結(jié)構(gòu)都代表了一個(gè)塊設(shè)備的I/O請(qǐng)求。
一個(gè)特定的請(qǐng)求可以分布在整個(gè)內(nèi)存中,但多數(shù)是對(duì)相鄰的扇區(qū)進(jìn)行操作。
如果多個(gè)請(qǐng)求都是對(duì)磁盤中的相鄰扇區(qū)進(jìn)行操作,則內(nèi)核將對(duì)他們進(jìn)行合并。
從本質(zhì)上講,一個(gè)request結(jié)構(gòu)是作為一個(gè)bio結(jié)構(gòu)的鏈表實(shí)現(xiàn)的。
bio
bio結(jié)構(gòu) bio結(jié)構(gòu)在中定義,包含了驅(qū)動(dòng)程序作者所要使用的諸多成員。
sector_t bi_sector; //該bio結(jié)構(gòu)所要傳輸?shù)牡谝粋€(gè)扇區(qū)(512字節(jié))unsigned int bi_size; //以字節(jié)為單位所需傳輸?shù)臄?shù)據(jù)大小。unsigned long bi_flags; //bio中一系列的標(biāo)志位;如果是寫請(qǐng)求,最低有效位將被設(shè)置。unsigned short bio_phys_segments;unsigned short bio_hw_segments; //當(dāng)DMA 映射完成時(shí),它們分別表示bio中包含的物理段的數(shù)目和硬件所能操作的數(shù)目。
request結(jié)構(gòu)成員
sector_t hard_sector;unsigned long hard_nr_sectors;unsigned int hard_cur_sectors;// 用于跟蹤那些驅(qū)動(dòng)程序還未完成的扇區(qū)。還未傳輸?shù)牡谝粋€(gè)扇區(qū)保存在hard_sector中,等待傳輸扇區(qū)的總數(shù)量保存在hard_nr_sectors中,當(dāng)前bio中剩余的扇區(qū)數(shù)目包含在hard_cur_sectors中。struct bio *bio;// 該請(qǐng)求的bio結(jié)構(gòu)鏈表。struct list_head queuelist;// 內(nèi)核鏈表結(jié)構(gòu),用來把請(qǐng)求連接到請(qǐng)求隊(duì)列中。
屏障請(qǐng)求
在驅(qū)動(dòng)程序收到請(qǐng)求前,塊設(shè)備層重新組合了請(qǐng)求以提高I/O性能。出于同樣的目的,驅(qū)動(dòng)程序也可以重新組合請(qǐng)求。
但是一些應(yīng)用程序的某些操作,要寫在另外一些操作之前,比如關(guān)系數(shù)據(jù)庫在執(zhí)行一個(gè)關(guān)系數(shù)據(jù)庫內(nèi)容的會(huì)話前,日志信息要寫到驅(qū)動(dòng)器上。
2.6內(nèi)核采用屏障(barrier)請(qǐng)求解決問題:如果一個(gè)請(qǐng)求被設(shè)置了REQ_HARDBARRER標(biāo)志,那么其后請(qǐng)求被初始化前,它必須被寫進(jìn)驅(qū)動(dòng)器。
不可重試請(qǐng)求
當(dāng)?shù)谝淮握?qǐng)求失敗后,塊設(shè)備驅(qū)動(dòng)程序經(jīng)常要重試請(qǐng)求。這樣的性能使得系統(tǒng)更可靠,不會(huì)丟失數(shù)據(jù)。
但是,內(nèi)核在某些情況下標(biāo)記請(qǐng)求是不可重試的。這些請(qǐng)求如果在第一次執(zhí)行失敗后,要盡快拋棄。
?
評(píng)論
查看更多