上一章主要講了請求隊列的一系列問題。下面主要說一下請求函數。首先來說一下硬碟類塊裝置的請求函數。
請求函數可以在沒有完成請求隊列的中的所有請求的情況下就返回,也可以在一個請求都不完成的情況下就返回。
下面貼出請求函數的常式:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio){ struct bio_vec *bvec; int i; void *dsk_mem; if ((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES) { printk(KERN_ERR SIMP_BLKDEV_DISKNAME ": bad request: block=%llu, count=%u\n", (unsigned long long)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_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;}
首先是一個while大迴圈的不斷檢測:
while ((req = elv_next_request(q)) != NULL)
這個while大迴圈是在不斷的檢測,同時elv_next_request這個函數的作用是獲得隊列中第一個未完成的請求,其實就是在遍曆隊列中的請求。elv_next_request這個函數用來獲得隊列中第一個未完成的請求,參數是q,這裡的q是請求隊列request_queue的函數指標。
還有一個重要的函數就是end_request(req, 0);
在這個函數中,傳給end_request這個函數的參數如果是0,則意味著請求失敗。如果傳的是1則意味著請求處理成功。
在這裡end_request這個函數很重要,其原型如下所示:
void end_request(struct request *req, int uptodate){ if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)){ add_disk_randomness (req->rq_disk); blkdev_dequeue_request (req); end_that_request_last(req); }}
當裝置已經完成1個IO請求的部分或者全部扇區傳輸後,它必須通告塊裝置層,上述代碼中的第4行完成這個工作。
end_that_request_first()函數的原型為:
int end_that_request_first(struct request *req, int success, int count);
這個函數告知塊裝置層,塊裝置驅動已經完成count個扇區的傳送。
end_that_request_first()的傳回值是一個標誌,指示是否這個請求中的所有扇區已經被傳送。傳回值為0表示所有的扇區已經被傳送並且這個請求完成,之後,我們必須使用 blkdev_dequeue_request()來從隊列中清除這個請求。
最後,將這個請求傳遞給end_that_request_last()函數:
void end_that_request_last(struct request *req);
end_that_request_last()通知所有正在等待這個請求完成的對象請求已經完成並回收這個請求結構體。
第6行的add_disk_randomness()函數的作用是使用塊 I/O 請求的定時來給系統的隨機數池貢獻熵,它不影響塊裝置驅動。但是,僅當磁碟的操作時間是真正隨機的時候(大部分機械裝置如此),才應該調用它。
LDD講了一個複雜的請求函數:
這個請求函數的代碼如下:
static void xxx_full_request(request_queue_t *q){struct request *req;int sectors_xferred;struct xxx_dev *dev = q->queuedata;/* 遍曆每個請求 */while ((req = elv_next_request(q)) != NULL){if (!blk_fs_request(req)){printk(KERN_NOTICE "Skip non-fs request\n"); end_request(req, 0);continue;}sectors_xferred = xxx_xfer_request(dev, req);if (!end_that_request_first(req, 1, sectors_xferred)){blkdev_dequeue_request(req);end_that_request_last(req);}}}/* 請求處理 */static int xxx_xfer_request(struct xxx_dev *dev, struct request *req){struct bio *bio;int nsect = 0;/* 遍曆請求中的每個bio */rq_for_each_bio(bio, req){xxx_xfer_bio(dev, bio);nsect += bio->bi_size / KERNEL_SECTOR_SIZE;}return nsect;}/* bio處理 */static int xxx_xfer_bio(struct xxx_dev *dev, struct bio *bio){int i;struct bio_vec *bvec;sector_t sector = bio->bi_sector;/* 遍曆每1段 */bio_for_each_segment(bvec, bio, i){char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer, bio_data_dir(bio)== WRITE);sector += bio_cur_sectors(bio); __bio_kunmap_atomic(bio, KM_USER0);}return 0;}
複雜的請求函數結構如下所示:
對於SD卡和隨身碟這類裝置支援無請求隊列的的模式。LDD說,為了使用這個模式,驅動必須提供一個製造請求函數,而不是一個請求函數。
第一個參數雖然仍然是請求隊列,但是這個請求隊列實際上不包含任何request,因為塊層沒有必要將bio調整為request所以製造請求的主要參數是bio結構體。bio_endio這個函數是通知結束處理函數。
無論處理成功與否,製造請求函數都應該返回0,如果返回一個非0數,那麼bio請求將會再一次被提交。
最後製造請求函數的一個重要函數:
void bio_endio(struct bio *bio, int error){if (error)clear_bit(BIO_UPTODATE, &bio->bi_flags);else if (!test_bit(BIO_UPTODATE, &bio->bi_flags))error = -EIO;if (bio->bi_end_io)bio->bi_end_io(bio, error);}