linux 塊設備驅動(四)——簡單的sbull實例


#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/errno.h> #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> static int sbull_major = 0;/* 塊設備號,0就自動分配*/ module_param(sbull_major, int, 0);//模參數,這個主設備號可以在加載模塊時指定一個值 static int hardsect_size = 512; /* 硬件扇區大小 */ module_param(hardsect_size, int, 0); /* 同上 */ static int nsectors = 1024;/* 硬件扇區數目 */ module_param(nsectors, int, 0); /* 同上 */ static int ndevices = 4; module_param(ndevices,int,0); /* The different "request modes " we can use. */ enum { RM_SIMPLE = 0, RM_FULL = 1, RM_NOQUEUE = 2, }; static int request_mode = RM_SIMPLE; module_param(request_mode, int, 0); #define SBULL_MINORS 16 #define KERNEL_SECTOR_SIZE 512 #define INVALIDATE_DELAY 30*HZ /*sbull 設備結構*/ struct sbull_dev{ int size; /* 以扇區為單位, 設備的大小 */ u8 *data; /* 數據數組 */ short users; /* 用戶數目 */ short media_change; /* 介質改變標志 */ spinlock_t lock; /* 用於互斥 */ struct request_queue *queue; /* 設備請求隊列 */ struct gendisk *gd; /* gendisk結構 */ struct timer_list timer; /* 用來模擬介質改變 */ }; static struct sbull_dev *Devices = NULL; /* * Handle an I/O request. 處理 I/O 拷貝數據的 函數 */ 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; } 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; //定義請求結構體 req = blk_fetch_request(q); while(req != NULL){ struct sbull_dev *dev = req->rq_disk->private_data;//獲得隊列中第一個未完成請求 if(req->cmd_type != REQ_TYPE_FS){ //判斷是否為文件系統請求 printk(KERN_NOTICE "Skip non-fs request\n"); blk_end_request_all(req, -EIO); //通知請求處理失敗,EIO為i/o error. continue; } sbull_transfer(dev, blk_rq_pos(req), //扇區光標所在的位置 blk_rq_cur_sectors(req), //需要傳輸的扇區數目 req->buffer, //要傳輸或者接受的數據緩存區 rq_data_dir(req)); //獲取傳輸方向,0表示讀,1表示寫 if(!__blk_end_request_cur(req, 0)){ //下一個請求 req = NULL; } } } /* * Transfer a single BIO. bio處理函數 */ static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio) { int i; struct bio_vec *bvec; //定義實際的 vec 列表 sector_t sector = bio->bi_sector; //定義要傳輸的第一個扇區 bio_for_each_segment(bvec,bio,i){ //下面的宏遍歷bio的每一段,獲得一個內核虛擬地址來存取緩沖 char *buffer = __bio_kmap_atomic(bio,i,KM_USER0);//通過kmap_atomic()函數獲得返回bio的第i個緩沖區的虛擬地址 sbull_transfer(dev, sector,// 開始扇區的索引號 bio_cur_bytes(bio)/KERNEL_SECTOR_SIZE, // 需要傳輸的扇區數 buffer, // 傳輸數據的緩沖區指針 bio_data_dir(bio) == WRITE); // 傳輸方向,0表述從設備讀,非0從設備寫 sector += bio_cur_bytes(bio)/KERNEL_SECTOR_SIZE;//返回扇區數 __bio_kunmap_atomic(bio,KM_USER0);//返回由 __bio_kmap_atomic()獲得的內核虛擬地址 } return 0; } /* * Transfer a full request.請求處理函數 */ static int sbull_xfer_request(struct sbull_dev *dev, struct request *req) { struct bio *bio; int nsect = 0; __rq_for_each_bio(bio, req) { //此宏遍歷請求中的每個bio,傳遞用於sbull_xfer_bio()傳輸的指針 sbull_xfer_bio(dev, bio); //調用 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; struct sbull_dev *dev = q->queuedata; req = blk_fetch_request(q); /* 遍歷每個請求 */ while ( req != NULL ) { if(req->cmd_type != REQ_TYPE_FS) {//判斷請求是否是個文件系統請求 printk(KERN_NOTICE "Skip non-fs request\n"); blk_end_request_all(req, -EIO); //該請求是一個I/O error continue; } sbull_xfer_request(dev, req); //調用請求處理函數 if (!blk_end_request_cur(req, 0)) { //req指向下一個請求 req = NULL; } } } /* * 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, status); // bio_endio()函數通知處理結束 return 0; } /* * open()函數 */ static int sbull_open(struct block_device *bd, fmode_t mode) { struct sbull_dev *dev = bd->bd_disk->private_data; del_timer_sync(&dev->timer); /* 介質移除定時器: 銷毀定時器 */ spin_lock(&dev->lock); /* lock */ if(!dev->users) check_disk_change(bd); dev->users++; spin_unlock(&dev->lock); /* unlock */ return 0; } static int sbull_release(struct gendisk *gd, fmode_t mode) { struct sbull_dev *dev = gd->private_data; spin_lock(&dev->lock); dev->users--; if(!dev->users){ dev->timer.expires = jiffies + INVALIDATE_DELAY; add_timer(&dev->timer); /* 介質移除定時器: 加載定時器 ,30s */ } spin_unlock(&dev->lock); return 0; } /* * Look for a (simulated) media change. */ int sbull_media_change(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. * 調用此函數內核將試着重新讀取分區表,在這里這個函數這是簡單的重置 media_change 的標志位,並 * 清除內存空間以模擬插入一張磁盤 */ 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 "invalidte"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); } /* *ioctl: *暫時處理一個命令: 對設備的物理信息的查詢請求 */ int sbull_ioctl(struct block_device *bd, fmode_t mode, unsigned cmd, unsigned long arg) { long size; struct hd_geometry geo; struct sbull_dev *dev = bd->bd_disk->private_data; switch (cmd) { case HDIO_GETGEO: size = dev->size*(hardsect_size / KERNEL_SECTOR_SIZE); geo.cylinders = (size & ~0x3f) >> 6; geo.sectors = 16; geo.heads = 4; geo.start = 4; if(copy_to_user((void __user*)arg, &geo, sizeof(geo))) return -EFAULT; return 0; } return -ENOTTY; } /* 獲取幾何信息 */ static int sbull_getgeo(struct block_device *bd, struct hd_geometry *geo) { long size; struct sbull_dev *dev = bd->bd_disk->private_data; size = dev->size *(hardsect_size / KERNEL_SECTOR_SIZE); geo->cylinders = (size & ~0x3f) >> 6; geo->heads = 4; geo->sectors = 16; geo->start = 4; 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_change, /* 用戶檢查介質是否被改變(移除) */ .revalidate_disk = sbull_revalidate, .ioctl = sbull_ioctl, .getgeo = sbull_getgeo }; /*初始化 sbull_dev 數據結構的具體實現*/ static void sbull_setup_device(struct sbull_dev *dev) { printk(KERN_INFO "sbull: step into into setup_device\n"); memset(dev, 0, sizeof(struct sbull_dev)); dev->size = nsectors * hardsect_size; //整個塊設備大小1024(扇區個數) * 512(扇區大小) dev->data = vmalloc(dev->size); /* 開辟虛擬存儲空間 */ if(dev->data == NULL){ printk(KERN_NOTICE "vmalloc failure\n"); return; } spin_lock_init(&dev->lock); /* 初始化自旋鎖 */ 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; case RM_SIMPLE: dev->queue = blk_init_queue(sbull_request, &dev->lock);/*請求隊列初始化*/ if(dev->queue == NULL) goto out_vfree; break; default: printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode); } blk_queue_logical_block_size(dev->queue, hardsect_size);/* 硬件扇區尺寸設置 */ dev->queue->queuedata = dev; dev->gd = alloc_disk(SBULL_MINORS); if(!dev->gd){ /* 動態分配 gendisk 結構體*/ printk(KERN_NOTICE "alloc_disk failure\n"); goto out_vfree; } dev->gd->major = sbull_major; /* 主設備號 */ dev->gd->first_minor = SBULL_MINORS; /* 次設備號 */ dev->gd->fops = &sbull_ops; /* 塊設備操作結構體 */ dev->gd->queue = dev->queue; /* 請求隊列 */ dev->gd->private_data = dev; /* 私有數據 */ snprintf(dev->gd->disk_name, 32, "sbull"); /* 次設備的名字 */ /* 每個請求的大小都是扇區大小的整數倍,內核總是認為扇區大小是512字節,因此必須進行轉換 */ set_capacity(dev->gd, nsectors * (hardsect_size / KERNEL_SECTOR_SIZE)); add_disk(dev->gd);/* 完成以上初始化后,調用 add_disk 函數來注冊這個磁盤設備 */ return; out_vfree: if(dev->data) vfree(dev->data); } static int __init sbull_init(void) { int i; printk(KERN_WARNING "sbull: start init\n"); // 注冊塊設備,第一個參數是設備號,0為動態分配,第二個參數是設備名 sbull_major = register_blkdev(sbull_major, "sbull"); if (sbull_major <= 0) { printk(KERN_WARNING "sbull: unable to get major number\n"); return -EBUSY; } printk(KERN_INFO "sbull: start kmalloc\n"); Devices = kmalloc(ndevices * sizeof(struct sbull_dev), GFP_KERNEL);/* 為塊核心數據結構 sbull_dev 分配空間 */ if(Devices == NULL) goto out_unregister; printk(KERN_WARNING "sbull: start setup_device\n"); sbull_setup_device(Devices); /* 初始化 sbull_dev 核心數據結構 ,並add_disk*/ return 0; out_unregister: unregister_blkdev(sbull_major, "sbull"); return -ENOMEM; } static void sbull_exit(void) { struct sbull_dev *dev = Devices; del_timer_sync(&dev->timer); if (dev->gd) { del_gendisk(dev->gd); put_disk(dev->gd); } if (dev->queue) { if (request_mode == RM_NOQUEUE) kobject_put(&(dev->queue)->kobj); else blk_cleanup_queue(dev->queue); } if (dev->data) vfree(dev->data); unregister_blkdev(sbull_major, "sbull"); kfree(Devices); } MODULE_LICENSE("Dual BSD/GPL"); module_init(sbull_init); module_exit(sbull_exit);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM