1. 程式人生 > >linux下的塊裝置驅動(二)

linux下的塊裝置驅動(二)

上一章主要講了請求佇列的一系列問題。下面主要說一下請求函式。首先來說一下硬碟類塊裝置的請求函式。

請求函式可以在沒有完成請求佇列的中的所有請求的情況下就返回,也可以在一個請求都不完成的情況下就返回。
下面貼出請求函式的例程:

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卡和U盤這類裝置支援無請求佇列的的模式。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);
}