12

下一代异步 IO io_uring 技术解密

 3 years ago
source link: https://kernel.taobao.org/2020/08/Introduction_to_IO_uring/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Aug 2, 2020 • 齐江

下一代异步 IO io_uring 技术解密

Alibaba Cloud Linux 2 是阿里云操作系统团队基于开源 Linux 4.19 LTS 版本打造的一款针对云应用场景的下一代 Linux OS 发行版。在首次推出一年后,阿里云操作系统团队对外正式发布了Alibaba Cloud Linux 2 LTS 版本。LTS 版本的发布是一个重要的里程碑,标志着阿里云操作系统团队将为 Alibaba Cloud Linux 2 提供长期技术支持、稳定的更新和更好的服务,为 Alibaba Cloud Linux 2 的客户提供更多保障。

上一篇我们在 Alibaba Cloud Linux 2 上对比测试了 io_uring 与 libaio 以及 SPDK,可以看到 io_uring 带来的性能提升非常明显。这篇文章我们详细分析下 io_uring 的原理,以及我们在 io_uring 社区所做的工作。

io_uring 原理介绍

为了从根本上解决当前 Linux aio 存在的问题和约束,io_uring 从零开始全新设计的了异步 IO 框架。其设计的主要目标如下:

  • 简单易用,方便应用集成。
  • 可扩展,不仅仅为 block IO 使用,同样可以用于网络 IO。
  • 特性丰富,满足所有应用,如 buffer io。
  • 高效,尤其是针对大部分场景的 512 字节或 4K IO。
  • 可伸缩,满足峰值场景的性能需要。

io_uring 为了避免在提交和完成事件中的内存拷贝,设计了一对共享的 ring buffer 用于应用和内核之间的通信。其中,针对提交队列(SQ),应用是 IO 提交的生产者(producer),内核是消费者(consumer);反过来,针对完成队列(CQ),内核是完成事件的生产者,应用是消费者。

img

共享环的设计主要带来以下 3 个好处:

  • 提交、完成请求时节省应用和内核之间的内存拷贝;
  • 使用 SQPOLL 高级特性时,应用程序无需调用系统调用;
  • 无锁操作,用 memory ordering 实现同步。

io_uring 系统调用

io_uring 一共提供了 3 个系统调用:io_uring_setup(),io_uring_enter(),以及io_uring_register(),位于 fs/io_uring.c。

/**
 * io_uring_setup - setup a context for performing asynchronous I/O
 */
int io_uring_setup(u32 entries, struct io_uring_params *p);
/**
 * io_uring_enter - initiate and/or complete asynchronous I/O
 */
int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete,
                   unsigned int flags, sigset_t *sig)
 
/**
 * io_uring_register - register files or user buffers for asynchronous I/O
 */
int io_uring_register(int fd, unsigned int opcode, void *arg,
                      unsigned int nr_args)

Alibaba Cloud Linux 2 LTS 版本支持的异步操作如下,更多的特性支持持续完善中。

  • IORING_OP_NOP 仅产生一个完成事件,除此之外没有任何操作。

  • IORING_OP_READV / IORING_OP_WRITEV 提交 readv() / writev() 操作,大多数场景最核心的操作。

  • IORING_OP_READ_FIXED / IORING_OP_WRITE_FIXED 使用已注册的 buffer 来提交 IO 操作,由于这些 buffer 已经完成映射,可以降低系统调用的开销。

  • IORING_OP_FSYNC 下发 fsync() 调用。

  • IORING_OP_POLL_ADD / IORING_OP_POLL_REMOVE 使用 IORING_OP_POLL_ADD 可对一组文件描述符 (file descriptors) 执行 poll() 操作;可以使用 IORING_OP_POLL_REMOVE 显式地取消 poll()。这种方式可以用来异步地监控一组文件描述符。

  • IORING_OP_SYNC_FILE_RANGE 执行 sync_file_range() 调用,是对 fsync() 的一个增强。

  • IORING_OP_SENDMSG / IORING_OP_RECVMSG 在 sendmsg() 和 recvmsg() 基础上,提供异步收发网络包功能。

  • IORING_OP_TIMEOUT 用户态程序等待 IO 完成事件时,可以通过 IORING_OP_TIMEOUT 设置一个超时时间,类似 io_getevents(2) 的 timeout 机制。

io_uring 用户态库 liburing

为了简化使用,原作者 Jens 开发了一套 liburing 库,用户无需了解诸多 io_uring 细节便可以使用起来,如无需关心 memory barrier,以及 ring buffer 的管理等。相关接口在头文件 /usr/include/liburing.h 中定义。

Alibaba Cloud Linux 2 LTS 提供了 liburing 和 liburing-devel 包供用户安装。

sodo yum install liburing liburing-devel

基于 liburing 的一个 helloworld 示例如下:

#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <liburing.h>
#define ENTRIES     4
int main(int argc, char *argv[])
{
    struct io_uring ring;
    struct io_uring_sqe *sqe;
    struct io_uring_cqe *cqe;
    struct iovec iov = {
        .iov_base = "Hello World",
        .iov_len = strlen("Hello World"),
    };
    int fd, ret;
    if (argc != 2) {
        printf("%s: <testfile>\n", argv[0]);
        return 1;
    }
    /* setup io_uring and do mmap */
    ret = io_uring_queue_init(ENTRIES, &ring, 0);
    if (ret < 0) {
        printf("io_uring_queue_init: %s\n", strerror(-ret));
        return 1;
    }
    fd = open("testfile", O_WRONLY | O_CREAT);
    if (fd < 0) {
        printf("open failed\n");
        ret = 1;
        goto exit;
    }
    /* get an sqe and fill in a WRITEV operation */
    sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        printf("io_uring_get_sqe failed\n");
        ret = 1;
        goto out;
    }
    io_uring_prep_writev(sqe, fd, &iov, 1, 0);
    /* tell the kernel we have an sqe ready for consumption */
    ret = io_uring_submit(&ring);
    if (ret < 0) {
        printf("io_uring_submit: %s\n", strerror(-ret));
        goto out;
    }
    /* wait for the sqe to complete */
    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        printf("io_uring_wait_cqe: %s\n", strerror(-ret));
        goto out;
    }
    /* read and process cqe event */
    io_uring_cqe_seen(&ring, cqe);
out:
    close(fd);
exit:
    /* tear down */
    io_uring_queue_exit(&ring);
    return ret;
}

更多的示例可参考:

https://github.com/axboe/liburing/tree/master/examples/ https://github.com/axboe/liburing/tree/master/test

io_uring 高级特性

Polled IO

IORING_SETUP_IOPOLL,与非 polling 模式等待硬件中断唤醒不同,内核将采用 polling 模式不断轮询硬件以确认 IO 请求是否已经完成,这在追求低延时和高 IOPS 的应用场景非常有用。

Kernel Side Polling

IORING_SETUP_SQPOLL,当前应用更新 SQ ring 并填充一个新的 sqe,内核线程 sqthread 会自动完成提交,这样应用无需每次调用 io_uring_enter() 系统调用来提交 IO。应用可通过 IORING_SETUP_SQ_AFF 和 sq_thread_cpu 绑定特定的 CPU。 同时,为了节省无 IO 场景的 CPU 开销,该内核线程会在一段时间空闲后自动睡眠。应用在下发新的 IO 时,通过 IORING_ENTER_SQ_WAKEUP 唤醒该内核线程,该操作在 liburing 中都已封装完成。

Fixed Files

IORING_REGISTER_FILES / IORING_REGISTER_FILES_UPDATE / IORING_UNREGISTER_FILES,通过 io_uring_register() 系统调用提前注册一组 file,缓解每次 IO 操作因 fget() / fput() 带来的开销。

Fixed Buffers

IORING_REGISTER_BUFFERS / IORING_UNREGISTER_BUFFERS,通过 io_uring_register() 系统调用注册一组固定的 IO buffers,当应用重用这些 IO buffers 时,只需要 map / unmap 一次即可,而不是每次 IO 都要去做,减少get_user_pages() / put_page() 带来的开销。

Linked SQE

IOSQE_IO_LINK,建立 sqe 序列之间的关联,这在诸如 copy 之类的操作中非常有用。使用 linked sqe 后,copy 操作的写请求链接在读请求之后,应用程序无需等待读请求数据返回后再下发写请求,而是共享了同一个 buffer,避免了上下文切换的开销。

阿里云操作系统团队在 backport io_uring 特性到 Alibaba Cloud Linux 2 的过程中,进一步优化性能,并加固 io_uring 的稳定性,相关工作以补丁的形式回馈到社区。

  • engines/io_uring: delete fio_option_is_set() calls when submitting sqes fio io_uring 提交 IO 性能提升 30%。

  • __io_uring_get_cqe: eliminate unnecessary io_uring_enter() syscalls 在某些场景下,减少 50% 的 io_uring_enter() 系统调用开销。

  • ext4: start to support iopoll method

  • io_uring: io_uring_enter(2) don’t poll while SETUP_IOPOLL|SETUP_SQPOLL enabled 能带来 13% 的性能提升,同时减少 20% 的 CPU 开销。

代码优化和特性重构

  • io_uring: cleanup io_alloc_async_ctx()
  • io_uring: refactor file register/unregister/update handling 重构 file register/unregister/update 特性,能更好地处理大量文件场景。
  • io_uring: do not always copy iovec in io_req_map_rw()
  • io_uring: avoid whole io_wq_work copy for requests completed inline
  • io_uring: avoid unnecessary io_wq_work copy for fast poll feature
  • e697deed834d io_uring: check file O_NONBLOCK state for accept

BugFix

  • io_uring: fix __io_iopoll_check deadlock in io_sq_thread
  • io_uring: fix poll_list race for SETUP_IOPOLL|SETUP_SQPOLL
  • io_uring: restore req->work when canceling poll request
  • io_uring: only restore req->work for req that needs do completion
  • io_uring: use cond_resched() in io_ring_ctx_wait_and_kill()
  • io_uring: fix mismatched finish_wait() calls in io_uring_cancel_files()
  • io_uring: handle -EFAULT properly in io_uring_setup()
  • io_uring: reset -EBUSY error when io sq thread is waken up
  • io_uring: don’t submit sqes when ctx->refs is dying
  • io_uring: fix io_kiocb.flags modification race in IOPOLL mode
  • io_uring: don’t fail links for EAGAIN error in IOPOLL mode
  • io_uring: add memory barrier to synchronize io_kiocb’s result and iopoll_completed
  • io_uring: fix possible race condition against REQ_F_NEED_CLEANUP

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK