学写块设备驱动(三)----踢开IO调度器,自己处理bio(上)
2012-01-09 14:12
204 查看
前两篇我们编写了在内存中的最简单的块设备驱动程序,并为其更换了我们心仪的’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() [b]------> q->request_fn()[/b]
到这里,长征结束,期间和IO调度器打个照面,见识了块设备解除阻塞状态,还唤醒了内核的工作队列,但最终我们可以大呼一口气了。。。
综上所述,当我们实现了块设备驱动程序的策略函数(例如前面实现的simp_blkdev_do_request)并用其作为参数初始化一个request_queue后,通用块层的make_request_fn 函数指针帮我们指定了强力帮手 __make_request,该帮手又拉上了IO调度器,于是,一个bio经过通用块层、IO调度层的处理,最后以request_queue中的request喂给我们实现的策略函数。
其实,我们前面两篇都是围绕着请求队列(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() [b]------> q->request_fn()[/b]
到这里,长征结束,期间和IO调度器打个照面,见识了块设备解除阻塞状态,还唤醒了内核的工作队列,但最终我们可以大呼一口气了。。。
综上所述,当我们实现了块设备驱动程序的策略函数(例如前面实现的simp_blkdev_do_request)并用其作为参数初始化一个request_queue后,通用块层的make_request_fn 函数指针帮我们指定了强力帮手 __make_request,该帮手又拉上了IO调度器,于是,一个bio经过通用块层、IO调度层的处理,最后以request_queue中的request喂给我们实现的策略函数。
相关文章推荐
- 学写块设备驱动(三)----踢开IO调度器,自己处理bio(下)
- 木其工作室(专业程序代写服务)[转]学写块设备驱动(三)----踢开IO调度器,自己处理bio(上)
- 学写块设备驱动(三)----踢开IO调度器,自己处理bio(上)
- 木其工作室(专业程序代写服务)[转]学写块设备驱动(三)----踢开IO调度器,自己处理bio(下)
- 学写块设备驱动(三)----踢开IO调度器,自己处理bio(下)
- 木其工作室(专业程序代写服务)[转]学写块设备驱动(二)----更换IO调度器
- 关于设备栈,IO栈,IO_STACK_LOCATION----文件系统过滤驱动学习收获
- inux设备驱动归纳总结(五):2.操作硬件——IO内存【转】
- 字符设备驱动第十四课----IO读写
- 联想扬天ACPI\LBAI0100未知设备的处理方法 此驱动为电源管理
- WinCE驱动中设备中断的处理
- 内核驱动 - 混杂设备驱动 + 中断处理函数
- 【Linux操作系统分析】设备驱动处理流程
- [linux驱动]linux块设备学习笔记(四)——请求处理
- linux设备驱动归纳总结(三):5.阻塞型IO实现
- 杂项设备驱动之 申请IO口
- 字符设备驱动-Linux内核异常处理体系结构
- 转贴:Mark Russinovich的Inside Vista Kernel系列文章,讲到了Vista内核的调度,IO,内存管理,缓存,事务处理,安全等众多新特性
- [linux驱动]linux块设备学习笔记(四)——请求处理
- Linux设备驱动编程之中断处理