学写块设备驱动(三)----踢开IO调度器,自己处理bio(上)

前两篇我们编写了在内存中的最简单的块设备驱动程序,并为其更换了我们心仪的’noop‘IO调度器。本篇我们试着搞清楚内核的块设备层在这里为我们做的事情,以及我们如何做点自己想做的事情。

其实,我们前面两篇都是围绕着请求队列(request_queue)这东西做事情。初始化请求队列时我们注册上驱动处理请求(request)的策略函数(simp_blkdev_do_request),然后在gendisk结构初始化时又填充上前面初始化好的queue。后面我们又用‘noop’IO调度器更换掉默认的'cfq'调度器。

下面试着搞清楚通用块层在这里的框架机制。先看一张图:



当通用块层以上的层要对块设备进行访问时,通常是准备好一个bio,调用generic_make_request(struct bio *bio),OK。

但是我们是编写底层驱动的可怜IT男,要是不知道generic_make_request是怎么把我们前面实现的simp_blkdev_do_request和request_queue联系起来,那就相当没有安全感。于是,我们开始RTFSC。

既然是围着request_queue做文章,那么我们先看下这个数据结构:

struct request_queue{

......

request_fn_proc *request_fn;// Method that implements the entry point of the strategy routine of the driver

make_request_fn *make_request_fn;//Method invoked when a new request has to be inserted in the queue

......

}

聪明的你肯定也看出来了,上面是两个函数指针,它们的定义如下:

typedef void (request_fn_proc) (struct request_queue *q);
typedef int (make_request_fn) (struct request_queue *q, struct bio *bio);

对上面的数据结构留个印象,我们开始看 通用块层的入口函数generic_make_request(struct bio *bio)。你可以发现下面的调用关系

generic_make_request(struct bio *bio) ------> __generic_make_request ------> q->make_request_fn(q, bio)

于是我们得出结论,generic_make_request()最终是通过调用request_queue.make_request_fn函数完成bio所描述的请求处理的。

那么,make_request_fn具体又指向哪个函数呢?我们前面也没有实现过make_request_fn这样的函数啊?!

我们只记得初始化request_queue时调用了blk_init_queue(request_fn_proc *, spinlock_t *)这个函数,所以我们来看一下这个函数,

blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) ------> blk_init_queue_node() ------>

q-> unplug_fn = generic_unplug_device;

q->request_fn = rfn;

blk_queue_make_request(q, __make_reqeust) ------> q->make_request_fn = mfn(即__make_request)

原来,我们request_queue的make_request_fn实际上指向了__make_request()函数。这样,大名鼎鼎的__make_request()函数就可以叫来某个IO调度器帮忙,并对bio做些利于用户的加工,比如将其映射到非线性映射区域。至此,我们知道了一个新的request是如何被提交给IO调度器的了(通过__make_request)。我们也知道了我们编写的simp_blkdev_do_request就是在这里被赋值给q->request_fn的了(通过blk_queue_make_request)。那么,通用块层的框架是什么时候对我们的simp_blkdev_do_request函数进行调用,从而真正执行对数据的拷贝了呢?



这里有点复杂,先补充一点理论知识,就一点,块设备有“阻塞”和“非阻塞”的状态,从而通用块层才能利用这样的机制推迟对请求的处理,从而给了IO调度参与的机会;当“非阻塞”时(该函数为blk_remove_plug),才能够处理请求。

有了这样的知识,我们RTFSC时见到blk_remove_plug就不奇怪了。好了,我们下面给出q->request_fn指向的simp_blkdev_do_request在何时被调用。

这次从__make_request()开始,这函数果然强大。。

__make_request() ------> add_request() ------> __elv_add_request() ------> elv_insert() ------> blk_remove_plug ------> blk_unplug_timeout ------> kblocked_schedule_work ------> blk_unplug_work() ------> q->unplug_fn()

如果你循到了上面的调用关系,那么恭喜你,你遇到了新问题,q->unplug_fn是哪只鸟,它调用的谁??

如果你记性还不错,那么你会想起我们blk_init_queue调用关系里面有一个“ q-> unplug_fn = generic_unplug_device; ”,很好,我们发现q->unplug_fn其实调用的是通用块层另一个小有名气的函数,generic_unplug_device()。我们继续寻找调用关系。

generic_unplug_device() ------> __generic_unplug_device() ------> q->request_fn()

到这里,长征结束,期间和IO调度器打个照面,见识了块设备解除阻塞状态,还唤醒了内核的工作队列,但最终我们可以大呼一口气了。。。

综上所述,当我们实现了块设备驱动程序的策略函数(例如前面实现的simp_blkdev_do_request)并用其作为参数初始化一个request_queue后,通用块层的make_request_fn 函数指针帮我们指定了强力帮手 __make_request,该帮手又拉上了IO调度器,于是,一个bio经过通用块层、IO调度层的处理,最后以request_queue中的request喂给我们实现的策略函数。

你可能感兴趣的:(学写块设备驱动(三)----踢开IO调度器,自己处理bio(上))