Ant-Veil

Caspar Blog

Block Device Plugging

| Comments

关于 block 设备的 plug 和 unplug 机制,可以参考这篇内核文档。简单来说就是内核提供了一个 plug 机制,请求被放到一个空队列里之后,可以将队列处于 “plugged” 状态,此时队列并不真正向下层设备发射(issue)请求,而是攒够足够多的请求之后,将队列 “unplug” 之后才发射,在这期间,I/O 的调度算法将有机会对请求进行合并和排序。unplug 的时机既可以是一个手工的 blk_unplug() 操作,也可以由一个 unplug_timer 定时器超时触发。这整个过程听起来有点像在等机场大巴,到站停靠(plug),装载乘客(攒 request),到点发车(unplug_timeout)或者人满发车(blk_unplug)。

很容易想到,unplug_timer 这个定时器的启动会发生在 plug 阶段(见 block/blk-core.c 中的实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void blk_plug_device(struct request_queue *q)
{
        WARN_ON(!irqs_disabled());
        /*
         * don't plug a stopped queue, it must be paired with blk_start_queue()
         * which will restart the queueing
         */
        if (blk_queue_stopped(q))
                return;
        if (!queue_flag_test_and_set(QUEUE_FLAG_PLUGGED, q)) {
                mod_timer(&q->unplug_timer, jiffies + q->unplug_delay);
                trace_block_plug(q);
        }
}
EXPORT_SYMBOL(blk_plug_device);

而定时器超时函数见同一个文件下的实现:

1
2
3
4
5
6
void blk_unplug_timeout(unsigned long data)
{
        struct request_queue *q = (struct request_queue *)data;
        trace_block_unplug_timer(q);
        kblockd_schedule_work(q, &q->unplug_work);
}

这个函数是在 blk_queue_make_request 定义的时候设置的(见 block/blk-settings.c):

1
2
3
4
5
6
7
8
9
10
11
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{
        <snip>
        q->unplug_thresh = 4;           /* hmm */
        q->unplug_delay = msecs_to_jiffies(3);  /* 3 milliseconds */
        if (q->unplug_delay == 0)
                q->unplug_delay = 1;
        q->unplug_timer.function = blk_unplug_timeout;
        q->unplug_timer.data = (unsigned long)q;
        <snip>
}

可以看到除了定义了超时函数,还定义了超时的时间,是 3ms. 这个 3ms 可以用来排查一系列奇怪的 I/O 性能问题。比如说在 iodepth 为 1 的情况下,发现 IOPS 是很固定的 333,那么多半就是因为请求队列没有及时 unplug 而导致定时器超时自动 unplug 了。通过简单计算得知,因为每个队列需要 3ms 传输,那一秒钟只能传送 333 个队列,即 IOPS == 333.

如果要主动 unplug,就需要调用 blk_unplug() 函数,或者 generic_unplug_device() 函数,从通用性来说,调用前者会比较好,看如下代码(block/blk-core.c):

1
2
3
4
5
6
7
8
9
10
void blk_unplug(struct request_queue *q)
{
        /*
         * devices don't necessarily have an ->unplug_fn defined
         */
        if (q->unplug_fn) {
                trace_block_unplug_io(q);
                q->unplug_fn(q);
        }
}

q->unplug_fn 一般不需要自行设置,如果没有定义,会使用默认的函数,即 generic_unplug_device()

看完了这几个函数,可以知道,plug 完之后及时 unplug 通常是避免 IO 延迟过高的良好手段。

Comments