本文所有内容基于内核版本Linux-v3.2.40。
块设备驱动处理IO的方式主要有两种:
方式1:透过内核的IO调度层,不进行request的合并,驱动直接实现make_request函数。
方式2:经过内核的IO调层对request进行必要的合并,以减少底层硬件的寻址耗时。
两种方式在层次结构上的区别如下图所示:
对于方式1而言,典型的应用就是NVME、sd等真正的随机读写设备(对任意地址的访问耗时是相同的);而方式2的典型应用就是机械硬盘,对不同地址的寻址耗时是不同的。因此,通过合并request可有效的减少磁头移动,提高块设备的处理速度和硬盘的寿命。
针对以上两种方式,底层驱动在实现上也是有差别的:
方式1:直接实现make_request函数,透过IO调度层,减少IO路径
驱动直接实现自己的make_request函数,申请请求队列并通过
blk_queue_make_request()将make_request注册到队列中。
如ramdisk中的实现:
brd->brd_queue = blk_alloc_queue(GFP_KERNEL); /* 申请一个块设备的queue */
blk_queue_make_request(brd->brd_queue, brd_make_request); /* 注册make_request函数 */
注意:
make_request的函数原型为:void make_request_fn(struct request_queue *q, struct bio *bio)
其中bio是通用块层传入的待处理的bio,驱动程序可直接使用并处理该bio。
方式2:借助内核默认的请求制造函数
blk_queue_bio进行bio的合并,驱动实现request的处理函数
request处理函数的原型为void request_fn(struct request_queue *q),所有待处理的request都挂在q下。
驱动程序可通过
blk_init_queue()注册
request_fn,此时不需要单独申请request_queue,如scsi层中的实现:
scsi_alloc_queue()->__scsi_alloc_queue()->blk_init_queue()
总结:
1. 方式1比较简单,只需要处理传入的单个bio;而方式2处理的是request链表,而且每个request又是一个bio链表,所以处理时相对复杂。
2. 方式1透过了IO调度层,IO路径更短,IO时延更低;但方式2在某些特定的场合却可以发挥巨大的作用,如机械硬盘。
3. IO调度层是一个比较复杂的子系统,涉及到几种IO调度算法(NOOP、CFQ、Deadline、AS等)以及request的合并策略,后续会专门写一篇相关的博客。
最后参考网上的一个示例,说明request_fn的实现方法:
1) 对于不太关心bio的场景,可以直接使用request->buffer进行处理,如下所示:
-
/*
-
* Handle I/O request.
-
*/
-
static void example_disk_transfer(struct example_dev *dev, unsigned long sector,
-
unsigned long nsect, char *buffer, int rw)
-
{
-
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 (rw) { /* write */
-
memcpy(dev->data + offset, buffer, nbytes);
-
} else { /* read */
-
memcpy(buffer, dev->data + offset, nbytes);
-
}
-
}
-
-
/*
-
* The example of a request function.
-
*/
-
static void example_request(struct request_queue *q)
-
{
-
struct request *req;
-
-
while ((req = blk_fetch_request(q)) != NULL) { /* 从queue队列中取出一个request */
-
struct example_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);
-
continue;
-
}
-
-
/* 底层数据传输,注意req->buffer最多只能表示一个page,因此如果当前request包括多个bio,或者bio中包括多个段,
-
* 就不能使用该方法,只能使用下面的方法2)
-
*/
-
example_disk_transfer(dev, blk_rq_pos(req), blk_rq_cur_sectors(req), req->buffer, rq_data_dir(req));
-
-
if(blk_end_request_cur(req, 0)) { /* 结束request */
-
printk("End current request failed!\n");
-
return ;
-
}
-
}
-
}
2) 如果对于底层驱动来说不得不对request中的每一个bio单独处理,那么除去遍历queue中request链表,还要遍历request中的bio链表。如下:
-
/*
-
* Transfer a full request.
-
*/
-
static int example_disk_xfer_request(struct example_dev *dev, struct request *req)
-
{
-
struct req_iterator iter;
-
int nsect = 0;
-
struct bio_vec *bvec;
-
struct bio *bio;
-
-
#if defined(use_bio)
-
/* 遍历request中的所有bio */
-
__rq_for_each_bio(bio, req) {
-
/* 处理bio,一般会借助bio_for_each_segment宏 */
-
}
-
#elif defined(use_bvec)
-
/* 两次遍历,先遍历request中的bio,再遍历bio中的bvec */
-
rq_for_each_segment(bvec, req, iter) {
-
char *buffer = __bio_kmap_atomic(iter.bio, iter.i, KM_USER0);
-
sector_t sector = iter.bio->bi_sector;
-
-
/* 底层数据传输 */
-
data_transfer(dev, sector, bio_sectors(iter.bio),
-
buffer, bio_data_dir(iter.bio) == WRITE);
-
sector += bio_sectors(iter.bio);
-
__bio_kunmap_atomic(iter.bio, KM_USER0);
-
nsect += iter.bio->bi_size/KERNEL_SECTOR_SIZE;
-
}
-
#endif
-
-
return nsect;
-
}
-
-
/*
-
* The example of a request function.
-
*/
-
static void example_request(struct request_queue *q)
-
{
-
struct request *req;
-
-
while ((req = blk_fetch_request(q)) != NULL) {
-
struct example_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);
-
continue;
-
}
-
-
/* 处理request */
-
example_disk_xfer_request(dev, req);
-
-
if(blk_end_request_cur(req, 0)) { /* 结束request */
-
printk("End current request failed!\n");
-
return ;
-
}
-
}
-
}
需要注意,处理完request后要记得end该request,借助blk_end_request_cur实现;另外还有一个函数__blk_end_request_cur,二者的区别主要是调用__blk_end_request_cur时,需要获得queue->queue_lock锁,而 blk_end_request_cur在内部实现时会自动上锁,也就是说,如果驱动代码中已经使用了类似spin_lock(q->queue_lock)的
锁操作,就不能再调用blk_end_request_cur,否则会产生死锁。
本文主要是做一个备忘,方便以后查询。
阅读(1275) | 评论(0) | 转发(0) |