上一章主要讲了请求队列的一系列问题。下面主要说一下请求函数。首先来说一下硬盘类块设备的请求函数。
请求函数可以在没有完成请求队列的中的所有请求的情况下就返回,也可以在一个请求都不完成的情况下就返回。
下面贴出请求函数的例程:
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); }