本来是在调试一个新的 io-scheduler(在基于 rhel 2.6.32-279 的 ali_kernel 上),但是出现了一些 BUG_ON(),追查了很久,在添加了大堆的 trace 信息以后,终于在一个多月后的昨天发现了原因。
linux 的 block 层在分配 io request 以后会对其调用回调函数 elevator_set_req_fn() 做初始化,在 request 放入每个设备的 request queue 后还要调用回调函数 elevator_add_req_fn() ,(这两个回调函数都是给 io-scheduler 来分别实现的,不同的 io-scheduler 会有各自不同的实现)看起来一个 request 一般应该经历这两个函数,而我从调试中开始怀疑:会不会有 request 只经历 set_req,而不经历 add_req?基于这个假设我翻了好几遍代码,发现当调度器切换的瞬间,新发出的 request 不走这两个函数。但毕竟是两个函数都不走,不是我怀疑的 "只 set_req 不 add_req"。
于是我不得不放弃自己的怀疑,实验了别的重现步骤和错误分析,一个月后还是没有进展。
靠着 trace 的不断堆砌,疑点终于还是回到了 "只 set_req 不 add_req",这次把各种可能调用到 get_request_wait 的函数都加上了调试信息,又跑了一个星期,终于发现,确实有 io request 只走 elevator_set_req_fn() 而不走 elevator_add_req_fn()!它就是 scsi command。
当我们用 sginfo 一类命令查看 IO 设备的信息时,是会向 kernel 发送 scsi 命令的,这些 scsi 命令一样是走 get_request_wait() –> get_request() –> blk_alloc_request() 来分配 request,也就一样要调用 elevator_set_req_fn,但是,它们却是用 blk_execute_rq_nowait 来将新分配的 request 放入 request queue 的,而不是通常用的 elv_insert(),而 blk_execute_rq_nowait 直接以 ELEVATOR_INSERT_FRONT 或 ELEVATOR_INSERT_BACK 为参数调用__elv_add_request,把 request 不经 io-scheduler 直接放入 request queue 了,不会调用 elevator_add_req_fn()!
- void blk_execute_rq_nowait(struct request_queue *q, struct gendisk *bd_disk,
- struct request *rq, int at_head,
- rq_end_io_fn *done)
- {
- int where = at_head ? ELEVATOR_INSERT_FRONT : ELEVATOR_INSERT_BACK;
- WARN_ON(irqs_disabled());
- rq->rq_disk = bd_disk;
- rq->end_io = done;
- spin_lock_irq(q->queue_lock);
- if (unlikely(blk_queue_dead(q))) {
- rq->errors = -ENXIO;
- if (rq->end_io)
- rq->end_io(rq, rq->errors);
- spin_unlock_irq(q->queue_lock);
- return;
- }
- __elv_add_request(q, rq, where, 1);
- ......
所以各个 io-scheduler 都是要自己处理这种情况的,不能产生 "set_req 后必然 add_req" 的假设。
不容易,一个多月才发现 scsi 命令的奇妙路径,也算是有所收获。
来源: http://it.taocms.org/08/1265.htm