- tags: Linux
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 的。
高级特性
- File registration
- Buffer registration
- Poll ring
- Linked operations