让 io_submit 阻塞的另一种方法

让 io_submit 阻塞的另一种方法

开始之前先给大家讲个笑话:

io_submit 是非阻塞的

大家都知道,io_submit 是为了异步提交 IO 任务而设计的接口。然而实际上,关于 io_submit 的抱怨一直持续不断。其中一个最主要的槽点,就是 io_submit 无法真正的做到非阻塞(non-blocking)。让 io_submit 阻塞的情况有很多,例如文件系统,Page Cache,Sync。

有人可能说,io_submit 阻塞那是你的姿势问题,用 io_submit,就不应该用文件系统,不应该用 Page Cache,不应该用 io_submit 执行 Sync,只要绕开这些坑,io_submit 就不会阻塞了。

真的是这样吗?今天我们就介绍让 io_submit 阻塞的另一种方法。

首先,我们来尝试搭建一个环境

通过 nbdkit,创建一个 memory backend 的 nbd 盘。

nbdkit -f memory size=1g

然后我们把它挂载到 nbd0

nbd-client localhost /dev/nbd0

现在可以通过 fio,对 nbd0 注入 IO 压力

fio --name=test --ioengine=libaio --filename=/dev/nbd0 --bs=4k --size=1g --rw=randwrite --direct=1 --iodepth=128 --runtime=10000 --time_based=1

看上去一切正常,我们使用 libaio,加上 Direct IO,直接写 Block Device,没有 Sync,按道理应该不会阻塞了。果然也是没有阻塞的。

现在让我们来把 iodepth 调整到 256。好了,如果你是老司机,你会发现一些不寻常的现象。

首先,通过 top 命令,可以观察到 io_wait 的比例非常高。这说明有进程因为 IO 被挂起了。通过 iotop 命令,可以断定是 fio 进程发生了挂起。

这就奇怪了,fio 如果使用 libaio 作为 engine,理论上是不应该被挂起的才对。

通过 perf trace 命令,我们可以观察到有 io_submit 调用花费了非常久的时间才返回

perf trace -p `pidof -s fio` --duration 1
   213.360 (203.640 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x18056b8           ) = 1
   419.566 (204.440 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x18057b8           ) = 1
   626.108 (203.936 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x18058b8           ) = 1
   831.660 (200.346 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x18059b8           ) = 1
  1034.644 (200.467 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x1805ab8           ) = 1
  1240.208 (223.018 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x1805bb8           ) = 1
  1464.865 (205.283 ms): io_submit(ctx_id: 140006384533504, nr: 1, iocbpp: 0x18054b8           ) = 1

这实在是太不寻常了。io_submit 这么久才返回,难道被挂起了?不是说好的不阻塞么?

但到底阻塞在哪里呢?这里我们借用 Brendan 大神的脚本来进行分析

/usr/share/bcc/tools/offcputime -K -p `pgrep -nx fio`
    finish_task_switch
    schedule
    schedule_timeout
    io_schedule_timeout
    io_schedule
    get_request
    blk_queue_bio
    generic_make_request
    submit_bio
    do_blockdev_direct_IO
    __blockdev_direct_IO
    blkdev_direct_IO
    generic_file_direct_write
    __generic_file_aio_write
    blkdev_aio_write
    do_io_submit
    sys_io_submit
    system_call_fastpath
    -                fio (307914)
        2002298

看起来是在 get_request 函数里面被挂起了。关于 get_request 的代码在这里就不贴出来了,有兴趣大家可以在 block/blk-core.c 里面找到看一下。简单来说,get_request 会创建一个可用的 request 对象,用于提交 IO 请求。如果未完成请求数量超过了 nr_requests 的数量,那么就会阻塞住。。。

cat /sys/block/nbd0/queue/nr_requests
128

检查一下 nbd 的 nr_requests,果然是 128。手动调整为更大的值,这个现象就不存在了。


io_submit 是异步编程最常用到的系统调用之一,但是也是被吐槽最多的系统调用之一。Linux 社区已经推出了新的系统调用 io_uring,希望能够解决 io_submit 遗留的问题,做到真正的非阻塞。

如果你异步编程感兴趣,欢迎留言讨论,也欢迎加入我们团队~

作者介绍
@张凯(Kyle Zhang),SmartX 联合创始人 & CTO。SmartX 拥有国内最顶尖的分布式存储和超融合架构研发团队,是国内超融合领域的技术领导者。

编辑于 2020-06-01 19:39