塊設備驅(qū)動初步 2010-05-02 19:55 單擊,,返回主頁,,查看更多內(nèi)容
本文的素材以及源代碼(稍有改動)均來源于《Linux Device Driver》,因此本文可視為該書塊設備驅(qū)動相關章節(jié)的閱讀理解,。 一,、體驗塊設備驅(qū)動(單擊下載驅(qū)動源碼) 本驅(qū)動模擬了一個硬盤,。想想你去中關村(不過我更喜歡去成都@世界)買了一個硬盤,,迫不及待地安裝在你的Linux機器上,你怎么樣才能使用這個新硬盤呢,?當然要先把它驅(qū)動起來,。 1、make生成sbull.ko后加載驅(qū)動:sudo insmod sbull.ko 2,、對硬盤分區(qū) 執(zhí)行suod fdisk /dev/sbulla 輸入x,,進入高級菜單 輸入h,change number of heads 為4 輸入c,,change number of cylinders 為4 輸入r,,退回主菜單 順次輸入n、p,、1,、1、4,,創(chuàng)建一個主分區(qū)占有cylinders 1-4 輸入w,,保存并退出 3、輸入 sudo mkfs.ext2 /dev/sbulla1 在硬盤分區(qū)上格式化ext2文件系統(tǒng) 4,、掛載新硬盤上的分區(qū) 輸入 mkdir testdir創(chuàng)建空目錄 輸入mount –t ext2 /dev/sbulla1 ./testdir 之后,,你就可以通過testdir目錄來訪問新硬盤分區(qū)了。 二,、塊設備驅(qū)動框架介紹 1,、驅(qū)動接口簡介 上層接口 用戶接口 塊設備 b:/dev/sda、/dev/sda2,、 /dev/ram0 用戶空間程序 mkfs,、mount、fdisk,; 直接調(diào)用open,、release,、ioctl接口 讀寫時,不直接調(diào)用讀寫接口,,而是將讀寫request提交給OS的block layer層 操作系統(tǒng)接口 block layer I/O scheduler決定需要執(zhí)行磁盤I/O時,,調(diào)用驅(qū)動的讀寫接口 抽象物理設備為 cylinder、header,、sector組成,,視其為編號從0開始的flat型sector集合。(注1) 總假定物理設備一個sector大小為512byte(注2) 下層接口 抽象物理設備為編號從0開始的flat型sector集合,,但轉(zhuǎn)換sector大小為物理設備的實際值,,通過物理寄存器的讀寫,將數(shù)據(jù)寫入指定的sector位置 由物理設備的中斷(讀寫完成)來喚醒用戶進程(或內(nèi)核線程) 注1: 借助minor number辨別partition編號(也可能是整個磁盤),,再借助分區(qū)表決定分區(qū)的起始sector號 注2: #define KERNEL_SECTOR_SHIFT 9 #define KERNEL_SECTOR_SIZE (1 << KERNEL_SECTOR_SHIFT) 2,、塊設備結(jié)構體 塊設備結(jié)構體 struct gendisk 是塊設備驅(qū)動中最重要的數(shù)據(jù)結(jié)構,它在操作系統(tǒng)和驅(qū)動中代表一個物理磁盤,。其主要字段有: major:磁盤對應的主設備號,,出現(xiàn)在/proc/devices中。register_blkdev(sbull_major, "sbull"); first_minor:磁盤對應的第1個次設備號,。dev->gd->first_minor = which*SBULL_MINORS,; minors :磁盤擁有的次設備號的總數(shù)。dev->gd = alloc_disk(SBULL_MINORS); disk_name:磁盤的名稱,。出現(xiàn)在/proc/partitions中 fops:塊設備驅(qū)動中的功能函數(shù),。包括:open\release\media_change\revalidate_disk\ioctl等 queue:OS回調(diào)讀寫函數(shù)時使用的request queue隊列(它綁定了讀寫函數(shù)) private_data:私有數(shù)據(jù),常用于存放包裹設備結(jié)構體 capacity:512字節(jié)大小的sector的總數(shù)量 set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE)); 3,、塊設備結(jié)構體注冊 申請major number sbull_major = register_blkdev(sbull_major, "sbull"); //sbull出現(xiàn)在/proc/devices中 分配(初始生成)塊設備結(jié)構體 dev->gd = alloc_disk(SBULL_MINORS); //同時也關聯(lián)設備號數(shù)量 將塊設備結(jié)構體與設備號,、設備操作函數(shù)、讀寫隊列關聯(lián) dev->gd->major = sbull_major; //關聯(lián)主設備號 dev->gd->first_minor = which*SBULL_MINORS; //關聯(lián)次設備號 dev->gd->fops = &sbull_ops; //關聯(lián)功能函數(shù) dev->queue = blk_init_queue(sbull_request, &dev->lock); dev->gd->queue = dev->queue; //關聯(lián)讀寫隊列 將塊設備結(jié)構體注冊進OS add_disk(dev->gd); 4,、塊設備結(jié)構體注銷 將塊設備結(jié)構體從OS中注銷 del_gendisk(dev->gd); 銷毀塊設備結(jié)構體(移除對kobject的最后ref.,,釋放結(jié)構體內(nèi)存) put_disk(dev->gd); 釋放major number unregister_blkdev(sbull_major, "sbull"); 5、塊設備的簡單讀寫-原理 用戶程序或內(nèi)核組件提出讀寫request時,,會將該request提交給block layer層 block layer層構造request結(jié)構,,并將其鏈入塊設備結(jié)構體綁定的request queue中 在適當?shù)臅r候,block layer層回調(diào)request queue中綁定的驅(qū)動中的讀寫函數(shù),,并將request queue的指針作為參數(shù)傳給驅(qū)動中的讀寫函數(shù) 驅(qū)動中 的讀寫函數(shù)根據(jù)傳給它的request queue的指針,,從request queue中取出一個request,根據(jù)request中指定的方向(讀或?qū)懀?、?shù)據(jù)在內(nèi)存中的位置,、在設備上的位置(起始sector編號)、傳輸數(shù) 據(jù)量的大?。╯ector數(shù)量),,完成物理讀寫 驅(qū)動通知block layer層實際完成的讀寫量,,block layer層據(jù)此更新其內(nèi)部各個數(shù)據(jù)結(jié)構;并告知驅(qū)動是否整個request已經(jīng)處理完成,,若是,,驅(qū)動則負責將request從request queue中摘下,并釋放request結(jié)構的內(nèi)存,,喚醒等待該request完成的所有進程 6,、讀寫隊列結(jié)構體-注冊與注銷 分配(初始生成)讀寫隊列結(jié)構體,并與讀寫函數(shù)綁定 dev->queue = blk_init_queue(sbull_request, &dev->lock); block layer在回調(diào)讀寫函數(shù)sbull_request時,,會先獲得自旋鎖dev->lock,,這樣block layer就可以與驅(qū)動的其它函數(shù)共享相同的臨界區(qū) blk_queue_hardsect_size(dev->queue, hardsect_size); 設置實際設備的扇區(qū)大小,這樣block layer層就會根據(jù)實際設備能夠處理的扇區(qū)大小來構造request結(jié)構體,,從而不會出現(xiàn)實際設備處理不了的request dev->queue->queuedata = http://blog.soso.com/qz.q/dev 將塊設備結(jié)構體與讀寫隊列關聯(lián) dev->gd->queue = dev->queue; 將讀寫隊列結(jié)構體間接注冊進OS add_disk(dev->gd); 將讀寫隊列結(jié)構體間接從OS中注銷 del_gendisk(dev->gd); 銷毀讀寫隊列結(jié)構體(移除對kobject的最后ref.,,釋放結(jié)構體內(nèi)存) blk_cleanup_queue(dev->queue); 7、塊設備的簡單讀寫-實現(xiàn) 108 static void sbull_request(request_queue_t *q) 109 { 110 struct request *req; 112 while ((req = elv_next_request(q)) != NULL) { //從request queue中取出一個request,循環(huán)直到request queue中的所有request被傳送完 //因為讀寫隊列與塊設備結(jié)構體已關聯(lián),,所以block layer層在將讀寫請求鏈入request queue時,,能將rq_disk字段指向塊設備結(jié)構體 113 struct sbull_dev *dev = req->rq_disk->private_data; 114 if (! blk_fs_request(req)) { 116 end_request(req, 0); 117 continue; 118 } 123 sbull_transfer(dev, req->sector, req->current_nr_sectors, //根據(jù)request中指定的方向(rq_data_dir)、數(shù)據(jù)在內(nèi)存中的位置( req->buffer ),、 124 req->buffer, rq_data_dir(req)); //在設備上的位置( req->sector ),、傳輸數(shù)據(jù)量的大?。?req->current_nr_sectors ),,完成物理讀寫 125 end_request(req, 1); //通知block layer層;將request從request queue中摘下;釋放request結(jié)構的內(nèi)存,喚醒等待該request完成的所有進程 126 } 127 } void end_request(struct request *req, int uptodate) { //驅(qū)動通知block layer層實際完成的讀寫量,,block layer層據(jù)此更新其內(nèi)部各個數(shù)據(jù)結(jié)構,;并告知驅(qū)動是否整個request已經(jīng)處理完成 if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)) { add_disk_randomness(req->rq_disk); blkdev_dequeue_request(req); //若是,驅(qū)動則負責將request從request queue中摘下 end_that_request_last(req); //釋放request結(jié)構的內(nèi)存,,喚醒等待該request完成的所有進程 } } 8,、簡單讀寫的不足 一次只命令硬件傳輸1個數(shù)據(jù)塊segment(內(nèi)存中不超過1page的連續(xù)單元),其只是request中的一小部分而已 雖然簡單讀寫采用while循環(huán)請求elv_next_request,,但block層會認為硬件可能由于某種原因不能一次性完成一個完整request的傳輸,,因此相鄰2次elv_next_request極有可能返回的是不同的request block layer盡了很大努力,實施電梯調(diào)度算法(drivers/block/ll_rw_block.c and elevator.c ),,使得一個request結(jié)構體中包含多個在內(nèi)存中離散,,但在物理設備上卻連續(xù)的數(shù)據(jù)塊。 先后2次elv_next_request的簡單讀寫,,使得磁頭必須尋道,,產(chǎn)生較大延遲 簡單讀寫忽視block layer層的工作,對同一個request結(jié)構體中包含的多個segment在物理設備上連續(xù),,不予理睬,,實在是暴殄天物 9,、請求隊列 一個塊設備的I/O請求的序列 跟蹤未完成的塊I/O請求 允許使用多I/O調(diào)度器,以最大化性能的方式提交I/O請求給你的驅(qū)動,,I/O調(diào)度器還負責合并鄰近的請求 請求隊列的實現(xiàn) drivers/block/Ll_rw_block.c和elevator.c 10,、塊設備的高效讀寫-原理與實現(xiàn) 一個request結(jié)構體中包含多個在內(nèi)存中離散,但在物理設備上卻連續(xù)的數(shù)據(jù)塊,,由bio和bio_vec結(jié)構體來表示 337 static void setup_device(struct sbull_dev *dev, int which) 362 switch (request_mode) { 370 case RM_FULL: 371 dev->queue = blk_init_queue(sbull_full_request, &dev->lock); 374 break; 385 } 387 dev->queue->queuedata = http://blog.soso.com/qz.q/dev; 409 } 175 static void sbull_full_request(request_queue_t *q) 176 { 177 struct request *req; 178 int sectors_xferred; 179 struct sbull_dev *dev = q->queuedata; 181 while ((req = elv_next_request(q)) != NULL) { //每次取出讀寫隊列中的一個request 187 sectors_xferred = sbull_xfer_request(dev, req); 188 if (! end_that_request_first(req, 1, sectors_xferred)) { 189 blkdev_dequeue_request(req); 191 end_that_request_last(req, 1); 192 } 193 } 194 } #define rq_for_each_bio(_bio, rq) \ if ((rq->bio)) \ for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next) 157 static int sbull_xfer_request(struct sbull_dev *dev, struct request *req) 158 { 159 struct bio *bio; 160 int nsect = 0; 162 rq_for_each_bio(bio, req) { //while循環(huán),,每次取出req中的一個bio 163 sbull_xfer_bio(dev, bio); 164 nsect += bio->bi_size/KERNEL_SECTOR_SIZE; //bi_size記錄一個bio中數(shù)據(jù)的總字節(jié)數(shù) 165 } 167 return nsect; 168 } #define bio_for_each_segment(bvl, bio, i) \ __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx) #define __bio_for_each_segment(bvl, bio, i, start_idx) \ for (bvl = bio_iovec_idx((bio), (start_idx)), i = (start_idx); \ i < (bio)->bi_vcnt; \ bvl++, i++) 133 static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio) 134 { 135 int i; 136 struct bio_vec *bvec; 137 sector_t sector = bio->bi_sector; //bi_sector記錄bio中首字節(jié)應位于硬件的哪個扇區(qū)。bio中的所有segment在硬件上的sector位置全部連續(xù) 139 /* Do each segment independently. */ 140 bio_for_each_segment(bvec, bio, i) { //while循環(huán),,每次取出bio中的一個bio_vec來傳輸 141 char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); //獲取bv_page的內(nèi)核virtual address 143 sbull_transfer(dev, sector, bio_cur_sectors(bio), //bio_cur_sectors(bio)求出bio當前segment的大?。╯ector數(shù)目) 144 buffer, bio_data_dir(bio) == WRITE); 145 sector += bio_cur_sectors(bio); //累進數(shù)據(jù)在硬件上的位置(sector) 147 __bio_kunmap_atomic(bio, KM_USER0); 149 } 151 return 0; /* Always "succeed" */ 152 } 11、塊設備的其它操作接口fops(open,、release,、media_change、revalidate_disk,、ioctl) 1) open與release(本驅(qū)動可以模擬光盤從光驅(qū)中更換) 216 static int sbull_open(struct inode *inode, struct file *filp) 217 { 218 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; //inode->i_bdev->bd_disk指向關聯(lián)的gendisk structure 223 if (! dev->users) 224 check_disk_change(inode->i_bdev); //check_disk_change將導致對media_change的調(diào)用,,若介質(zhì)已改變,將導致調(diào)用revalidate_disk 225 dev->users++; 228 } media_changed不為NULL時,,則會被內(nèi)核API check_disk_change所調(diào)用,; 在media_changed返回true的情況下, revalidate_disk會被內(nèi)核API check_disk_change所調(diào)用 int check_disk_change(struct block_device *bdev) { struct gendisk *disk = bdev->bd_disk; struct block_device_operations * bdops = disk->fops; if (!bdops->media_changed) //media_changed為NULL return 0; if (!bdops->media_changed(bdev->bd_disk)) //media_changed不為NULL時,,則會被內(nèi)核API check_disk_change所調(diào)用 return 0; if (__invalidate_device(bdev)) printk("VFS: busy inodes on changed media.\n"); if (bdops->revalidate_disk) // 在media_changed返回true的情況下,,revalidate_disk不為NULL時,則會被內(nèi)核API check_disk_change所調(diào)用 bdops->revalidate_disk(bdev->bd_disk); if (bdev->bd_disk->minors > 1) bdev->bd_invalidated = 1; return 1; } 230 static int sbull_release(struct inode *inode, struct file *filp) 231 { 232 struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; 235 dev->users--; 244 } 12,、media_change與revalidate_disk media_changed不為NULL時,,則會被內(nèi)核API check_disk_change所調(diào)用 若磁盤介質(zhì)已更改,應返回true,;否則返回false 在media_changed返回true的情況下,, revalidate_disk會被內(nèi)核API check_disk_change所調(diào)用 應完成對新磁盤介質(zhì)進行操作的準備工作 249 int sbull_media_changed(struct gendisk *gd) 250 { 251 struct sbull_dev *dev = gd->private_data; 253 return dev->media_change; 254 } 260 int sbull_revalidate(struct gendisk *gd) 261 { 262 struct sbull_dev *dev = gd->private_data; 264 if (dev->media_change) { 265 dev->media_change = 0; 266 // memset (dev->data, 0, dev->size); 267 } 269 } 13、ioctl 大部分的ioctl命令都已經(jīng)被block layer層所截獲并處理,,到達不了驅(qū)動程序 驅(qū)動ioctl函數(shù)中需要處理的命令是HDIO_GETGEO (用戶,,例如fdisk,要求獲得磁盤的幾何參數(shù))(測試結(jié)果似乎OS并未調(diào)用ioctl,,原因待查) 291 int sbull_ioctl (struct inode *inode, struct file *filp, 292 unsigned int cmd, unsigned long arg) 293 { 294 long size; 295 struct hd_geometry geo; 296 struct sbull_dev *dev = filp->private_data; 298 switch(cmd) { 299 case HDIO_GETGEO: 301 /* 302 * Get geometry: since we are a virtual device, we have to make 303 * up something plausible. So we claim 16 sectors, four heads, 304 * and calculate the corresponding number of cylinders. We set the 305 * start of data at sector four. 306 */ 307 // size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE); 308 size = dev->size / KERNEL_SECTOR_SIZE; 309 geo.cylinders = (size & ~0x3f) >> 6; 310 geo.heads = 4; 311 geo.sectors = 16; 312 geo.start = 4; 313 if (copy_to_user((void __user *) arg, &geo, sizeof(geo))) 314 return -EFAULT; 315 return 0; 316 } 318 return -ENOTTY; /* unknown command */ 319 } 單擊,,與作者交流 more http://www. |
|