Linux I/O

Linux I/O 演进

  • 阻塞式:read()/write()
  • 非阻塞式:select()/poll()/epoll(),不支持文件 I/O
  • Thread Pool
  • Direct I/O(数据软件):绕过 page cache
  • 异步 IO(Linux AIO):早起进支持文件 I/O,近期支持了 epoll 支持非文件 I/O

Linux io_uring

对比 Linux AIO:

  • 重新设计实现真正的是不。
  • 支持任何类型的 I/O:cached files、direct-access files 甚至 blocking sockets。
  • 灵活、可扩展:基于 io_uring 能够重写 Linux 的每个系统调用。

原理及核心数据结构:SQ/CQ/SQE/CQE

每个 io_uring 实例都有两个环形队列,在内核和应用程序之间共享:

  • 提交队列:submission queue(SQ)
  • 完成队列:completion queue(CQ)

这两个队列:

  • 都是单生产者、单消费者,size 是 2 的幂次;
  • 提供无锁接口(lock-less access interface),内部使用内存屏障做同步(coordinated with memory barrers)。

使用方式:

  • 请求
    • 应用创建 SQ entries(SQE),更新 SQ tail;
    • 内核消费 SQE,更新 SQ head
  • 完成
    • 内核为完成一个或多个请求创建 CQ enries(CQE),更新 CQ tail;
    • 应用消费 CQE,更新 CQ head
    • 完成事件(completion events)可能以任意顺序到达,到总是与特定的 SQE 相关联的。
    • 消费 CQE 过程无需切换到内核态

带来的好处

  • 支持批处理
  • 支持文件 I/O 系统调用:read、write、send、recv、accept、opentat、stat、专用的一些系统调用,如 fallocate
  • 不再局限于数据库应用
  • 应对现在硬件架构:将硬件架构本身作为一个网络(多核多 CPU 是一个基础网络、CPU 之间是一个网络、CPU 和磁盘 I/O 之间又是一个网络)

三种工作模式

  • 中断驱动模式(interrupt driven):默认模式。可通过 io_uring_enter() 提交 I/O 请求,然后直接检查 CQ 状态判断是否完成。

  • 轮询模式(polled)

    需要文件系统和块设备支持轮询功能。对比中断驱动方式,这种方式延迟更低,但可能会小号更多的 CPU 资源。

    只有指定了 O_DIRECT 标识位的的文件描述符才能使用这种模式。但一个读或写请求提交给轮询上下文(polled context)之后, 应用必须调用 io_uring_enter() 来轮询 CQ 队列,判断请求是否已经完成。

    同一个 io_uring 不支持混合使用轮询和非轮询模式。

  • 内核轮询模式(kernel polled)

    创建一个内核线程(kernel thread)来执行 SQ 的轮询工作。应用无需切换到内核态牛结果出发 I/O 操作。通过 SQ 来提交 SQE,以及监控 CQ 的完成状态,应用无需任何系统调用,就能提交和收割 I/O(submit and reap I/Os)。

    如果内核线程的空闲时间超过了用户的配置值,它会通知应用,然后进入 idle 状态。 这种情况下,应用必须调用 io_uring_enter() 来唤醒内核线程。如果 I/O 一直很繁忙,内核线性是不会 sleep 的。

高级特性

  1. File registration
  2. Buffer registration
  3. Poll ring
  4. Linked operations

用户空间库 liburing

Linux Virtual Memory Management