塊設備的驅動比字符設備的難,這是由於塊設備的驅動和內核的聯繫進一步增大,可是同時塊設備的訪問的幾個基本結構和字符仍是有類似之處的。node
有一句話必須記住:對於存儲設備(硬盤~~帶有機械的操做)而言,調整讀寫的順序做用巨大,由於讀寫連續的扇區比分離的扇區快。linux
可是同時:SD卡和U盤這類設備沒有機械上的限制,因此像上面說的進行連續扇區的調整顯得就沒有必要了。算法
先說一下對於硬盤這類設備的簡單的驅動。編程
在linux的內核中,使用gendisk結構來表示一個獨立的磁盤設備或者分區。這個結構中包含了磁盤的主設備號,次設備號以及設備名稱。app
在國嵌給的歷程中,對gendisk這個結構體的填充是在simp_blkdev_init函數中完成的。在對gendisk這個結構填充以前要對其進行分配空間。具體代碼以下:函數
simp_blkdev_disk = alloc_disk(1); if (!simp_blkdev_disk) { ret = -ENOMEM; goto err_alloc_disk; }
這裏的alloc_disk函數是在內核中實現的,它後面的參數1表明的是使用次設備號的數量,這個數量是不能被修改的。post
在分配好了關於gendisk的空間之後就開始對gendisk裏面的成員進行填充。具體代碼以下:優化
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME); //宏定義simp_blkdev 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; set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9); //宏定義(16*1024*1024),實際上就是這個結構體。
在填充好gendisk這個結構之後向內核中 註冊這個磁盤設備。具體代碼以下:this
add_disk(simp_blkdev_disk);
在LDD中說,想內核中註冊設備的必須在gendisk這個結構體已經填充好了之後,咱們之前的字符設備的時候也是這麼作的,不知道爲何LDD在這裏強調了這個。atom
當不須要一個磁盤的時候要釋放gendisk,釋放部分的代碼在函數simp_blkdev_exit中實現的。具體的釋放代碼以下:
del_gendisk(simp_blkdev_disk);
在simp_blkdev_exit中同時還有put_disk(simp_blkdev_disk),這個是用來進行操做gendisk的引用計數。simp_blkdev_exit還實現了blk_cleanup_queue清除請求隊列的這個函數。終於說到請求隊列了。
在說等待隊列以前先要明確幾個概念:
①用戶但願對硬盤數據作的事情叫作請求,這個請求和IO請求是同樣的,因此IO請求來自於上層。
②每個IO請求對應內核中的一個bio結構。
③IO調度算法能夠將連續的bio(也就是用戶的對硬盤數據的相鄰簇的請求)合併成一個request。
④多個request就是一個請求隊列,這個請求隊列的做用就是驅動程序響應用戶的需求的隊列。
請求隊列在國嵌的程序中的simp_blkdev_queue
下面先說一下硬盤這類帶有機械的存儲設備的驅動。
這類驅動中用戶的IO請求對應於硬盤上的簇多是連續的,多是不連續的,連續的固然好,若是要是不連續的,那麼IO調度器就會對這些BIO進行排序(例如老謝說的電梯調度算法),合併成一個request,而後再接收請求,再合併成一個request,多個request以後那麼咱們的請求隊列就造成了,而後就能夠向驅動程序提交了。
在硬盤這類的存儲設備中,請求隊列的初始化代碼以下:
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
老謝說,在這種狀況下首先調用的是內核中的make_requst函數,而後再調用本身定義的simp_blkdev_do_request。追了一下內核代碼,會發現make_requst內核代碼以下所示:
static int make_request(struct request_queue *q, struct bio * bio)
具體的就不貼了,不過能夠知道這個make_request的做用就是使用IO調度器對多個bio的訪問順序進行了優化調整合併爲一個request。也就是在執行完成了這個函數以後纔去正式的執行內核的請求隊列。
合併後的request其實仍是一個結構,這個結構用來表徵IO的請求,這個結構在內核中有具體的定義。
請求是一個結構,同時請求隊列也是一個結構,這個請求隊列在內核中的結構定義以下:
struct request_queue { /* * Together with queue_head for cacheline sharing */ struct list_head queue_head; struct request *last_merge; struct elevator_queue *elevator; /* * the queue request freelist, one for reads and one for writes */ struct request_list rq; request_fn_proc *request_fn; make_request_fn *make_request_fn; prep_rq_fn *prep_rq_fn; unplug_fn *unplug_fn; merge_bvec_fn *merge_bvec_fn; prepare_flush_fn *prepare_flush_fn; softirq_done_fn *softirq_done_fn; rq_timed_out_fn *rq_timed_out_fn; dma_drain_needed_fn *dma_drain_needed; lld_busy_fn *lld_busy_fn; /* * Dispatch queue sorting */ sector_t end_sector; struct request *boundary_rq; /* * Auto-unplugging state */ struct timer_list unplug_timer; int unplug_thresh; /* After this many requests */ unsigned long unplug_delay; /* After this many jiffies */ struct work_struct unplug_work; struct backing_dev_info backing_dev_info; /* * The queue owner gets to use this for whatever they like. * ll_rw_blk doesn't touch it. */ void *queuedata; /* * queue needs bounce pages for pages above this limit */ gfp_t bounce_gfp; /* * various queue flags, see QUEUE_* below */ unsigned long queue_flags; /* * protects queue structures from reentrancy. ->__queue_lock should * _never_ be used directly, it is queue private. always use * ->queue_lock. */ spinlock_t __queue_lock; spinlock_t *queue_lock; /* * queue kobject */ struct kobject kobj; /* * queue settings */ unsigned long nr_requests; /* Max # of requests */ unsigned int nr_congestion_on; unsigned int nr_congestion_off; unsigned int nr_batching; void *dma_drain_buffer; unsigned int dma_drain_size; unsigned int dma_pad_mask; unsigned int dma_alignment; struct blk_queue_tag *queue_tags; struct list_head tag_busy_list; unsigned int nr_sorted; unsigned int in_flight[2]; unsigned int rq_timeout; struct timer_list timeout; struct list_head timeout_list; struct queue_limits limits; /* * sg stuff */ unsigned int sg_timeout; unsigned int sg_reserved_size; int node; #ifdef CONFIG_BLK_DEV_IO_TRACE struct blk_trace *blk_trace; #endif /* * reserved for flush operations */ unsigned int ordered, next_ordered, ordseq; int orderr, ordcolor; struct request pre_flush_rq, bar_rq, post_flush_rq; struct request *orig_bar_rq; struct mutex sysfs_lock; #if defined(CONFIG_BLK_DEV_BSG) struct bsg_class_device bsg_dev; #endif };
LDD說,請求隊列實現了一個插入接口,這個接口容許使用多個IO調度器,大部分IO調度器批量累計IO請求,並將它們排列爲遞增或者遞減的順序提交給驅動。
多個連續的bio會合併成爲一個request,多個request就成爲了一個請求隊列,這樣bio的是直接的也是最基本的請求,bio這個結構的定義以下:
struct bio { sector_t bi_sector; struct bio *bi_next; /* request queue link */ struct block_device *bi_bdev; /* target device */ unsigned long bi_flags; /* status, command, etc */ unsigned long bi_rw; /* low bits: r/w, high: priority */ unsigned int bi_vcnt; /* how may bio_vec's */ unsigned int bi_idx; /* current index into bio_vec array */ unsigned int bi_size; /* total size in bytes */ unsigned short bi_phys_segments; /* segments after physaddr coalesce*/ unsigned short bi_hw_segments; /* segments after DMA remapping */ unsigned int bi_max; /* max bio_vecs we can hold used as index into pool */ struct bio_vec *bi_io_vec; /* the actual vec list */ bio_end_io_t *bi_end_io; /* bi_end_io (bio) */ atomic_t bi_cnt; /* pin count: free when it hits zero */ void *bi_private; bio_destructor_t *bi_destructor; /* bi_destructor (bio) */ };
須要注意的是,在bio這個結構中最重要的是bio.vec這個結構。同時還有許多操做bio的宏,這些都是內核給實現好了的。
請求隊列的實現:
首先使用 while ((req = elv_next_request(q)) != NULL)進行循環檢測,看看到底傳來的IO請求是個什麼。
而後進行讀寫區域的斷定:
if ((req->sector + req->current_nr_sectors) << 9 > SIMP_BLKDEV_BYTES) { printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": bad request: block=%llu, count=%u\n", (unsigned long long)req->sector, req->current_nr_sectors); //結束本次請求。 end_request(req, 0); continue; }
在進行讀寫區域的斷定的時候涉及到了不少linux的編程習慣。
sector表示要訪問的第一個扇區。
current_nr_sectors表示預計訪問扇區的數目。
這裏的左移九位其實就是乘以512。
這樣((req->sector + req->current_nr_sectors) << 9就計算出能夠預計要訪問的扇區的大小。進行了一次判斷。
若是上面的判斷沒有超出範圍,那麼就能夠對請求的這一部分塊設備進行操做了。
simp_blkdev_disk = alloc_disk(1); if (!simp_blkdev_disk) { ret = -ENOMEM; goto err_alloc_disk; } 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; set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9); add_disk(simp_blkdev_disk); return 0;
關於gendisk結構的內存分配和成員的填充和硬盤類塊設備是同樣的。
因爲SD卡和U盤屬於一類非機械類的設備,因此咱們不須要那麼複雜的調度算法,也就是不須要把io請求進行排序,因此咱們須要本身爲本身分配一個請求隊列。具體代碼以下:
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
須要注意一下的是在硬盤類塊設備的驅動中這個函數的原型是blk_init_queue(simp_blkdev_do_request, NULL);
在這種狀況下其實並無調用內核的make_request(這個函數的功能上文說過),也就是說咱們接下來要綁定的simp_blkdev_make_request這個函數和make_request是同一級別的函數。這裏就沒有涉及到算法調度的問題了。
而後須要進行的工做是:綁定製造請求函數和請求隊列。具體代碼以下:
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
把gendisk的結構成員都添加好了之後就能夠執行add_disk(simp_blkdev_disk);這個函數把這塊分區添加進內核了。
總體列出製造請求部分的函數。
//這個條件是在判斷當前正在運行的內核版本。 #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_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_ERR SIMP_BLKDEV_DISKNAME ": unknown value 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; }