Learning by Tracing

· 404 words · 1 minute read

bpftrace 跟踪一个 Direct I/O 在 polling 和非 polling 模式下的处理逻辑。为了减少无关的 I/O 干扰,限定了 comm == "a.out" – 这个 a.out 是我的测试程序。

内核是 4.18, 关键代码相对比较好找,在 fs/block_dev.c:

static void blkdev_bio_end_io_simple(struct bio *bio)
{
    struct task_struct *waiter = bio->bi_private;

    WRITE_ONCE(bio->bi_private, NULL);
    blk_wake_io_task(waiter);
}

static ssize_t
__blkdev_direct_IO_simple(struct kiocb *iocb, struct iov_iter *iter,
                         int nr_pages)
{
    bio.bi_private = current;
    bio.bi_end_io = blkdev_bio_end_io_simple;

    // 省略无关部分
    qc = submit_bio(&bio);
    for (;;) {
            set_current_state(TASK_UNINTERRUPTIBLE);
            if (!READ_ONCE(bio.bi_private))
                    break;
            if (!(iocb->ki_flags & IOCB_HIPRI) ||
                !blk_poll(bdev_get_queue(bdev), qc, true))
                    blk_io_schedule();
    }
    __set_current_state(TASK_RUNNING);
}

上面的 for 循环中,如果没有设置 IOCB_HIPRI (非 polling 模式), 则直接进入 blk_io_schedule(),等待被 blkdev_bio_end_io_simple() 唤醒。否则,进入 blk_poll() 逻辑,进行忙等或者 hybrid 等待。

跟踪 bio_endio() 得到的 kstack 和代码非常一致,如下:

kprobe:bio_endio: opf=0x800000, hipri requires: 0x800000, comm='a.out', kstack:
        bio_endio+1
        blk_update_request+544
        blk_mq_end_request+26
        nvme_poll+482
        blk_poll+282
        __blkdev_direct_IO_simple+520
        kretprobe_trampoline+0

去掉 RWF_HIPRI 后发现,bio_endio() 怎么也触发不到,像是泥牛入海。

冷静一下。

问题出在 /comm == "a.out"/ 这个过滤器。中断模式下,执行bio_endio() 时的 commswapper/N,甚至也有可能是 sshd! 因为处于中断上下文(严格来说,是下文 - bottom-half)。

kprobe:bio_endio: opf=0x0, hipri requires: 0x800000, comm='swapper/0', kstack:
        bio_endio+1
        blk_update_request+544
        blk_mq_end_request+26
        nvme_irq+295
        __handle_irq_event_percpu+64
        handle_irq_event_percpu+48
        handle_irq_event+54
        handle_edge_irq+130
        handle_irq+28
        do_IRQ+73
        ret_from_intr+0
        native_safe_halt+14
        __cpuidle_text_start+10
        default_idle_call+64
        do_idle+500
        cpu_startup_entry+111
        start_kernel+1309
        secondary_startup_64_no_verify+194
static irqreturn_t nvme_irq(int irq, void *data)
{
    struct nvme_queue *nvmeq = data;

    if (nvme_process_cq(nvmeq))
        return IRQ_HANDLED;
    return IRQ_NONE;
}

Certified by AI. ;-)

disk-bh

comments powered by Disqus