本来是在调试一个新的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命令的奇妙路径,也算是有所收获。