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

Alibaba Cloud Linux 2 LTS 版本,其中一个重要的特性更新是提供了对 io_uring 的支持。io_uring 是由 block 维护者 Jens Axboe 开发的新型异步 IO 框架。io_uring 在 2019 年 1 月初提出,到 2019 年 3 月初合并到 Linux 内核主线,仅用短短的 2 个月时间就合入了 Linux v5.1,充分表明了社区对该框架的积极态度。当前社区发展非常火热,很多主流应用都开始提供对 io_uring 的支持,如 Node.js,Nginx,PostgreSQL,RocksDB,QEMU,spdk,等等。

Alibaba Cloud Linux 2 LTS 版本的 io_uring 功能同步自 Linux 内核主线 v5.4,测试过程中发现的稳定性和性能问题已得到修复,相关补丁也已被接收合入到社区上游,并持续对其进行维护和支持。

Linux IO 发展史

Linux 中有很多方式来执行文件 IO 操作。最初的 IO 系统调用需要追溯到 read(2) 和write(2),后来发展为增加 offset 参数的 pread(2) 和 pwrite(2),以及基于 vector 的版本 preadv(2) 和 pwritev(2),再扩展成允许修改 flags 的版本 preadv2(2) 和 pwritev2(2)。这些系统调用看上去多种多样,但有一个共同的特性就是同步,即系统调用需要在数据读取完成或写入完成才返回。应某些应用场景的诉求,异步 IO 接口应势而生。POSIX 对应的接口为 aio_read(3)和 aio_write(3),但由于性能不好实际使用很少。

目前异步 IO 使用最多的是 Linux Native 异步 IO,即我们通常称的 aio。不幸的是,其同样有着诸多约束:

  • 最大的限制无疑是仅支持 direct io。而 O_DIRECT 存在 bypass 缓存和 size 对齐等限制,直接影响了 aio 在很多场景的使用。而针对 buffered io,其表现为同步。
  • 即使满足了所有异步 IO 的约束,有时候还是可能会被阻塞。例如,等待元数据 IO,或者等待 request 的分配等。
  • 存在额外的拷贝开销,每个 IO 提交需要拷贝 64+8 字节,每个 IO 完成需要拷贝 32 字节,这 104 字节的拷贝在大量小 IO 的场景下影响很可观。同时,需要非常小心地使用完成事件以避免丢事件。IO 需要至少 2 个系统调用(submit + wait-for-completion),这在 spectre/meltdown 开启的前提下性能下降非常严重。

io_uring 原理介绍

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

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

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

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 的一个简单的示例如下:

#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 */
    while (1) {
        io_uring_peek_cqe(&ring, &cqe);
        if (!cqe) {
            printf("Not completed, waiting...\n");
            usleep(1);
        } else {
            printf("Completed\n");
            break;
        }
    }

    /* 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

使用 fio io_uring 测试性能

Alibaba Cloud Linux 2 LTS 版本在 experimental 源中提供支持 io_uring 的 fio-3.17,用户可通过 ioengine=io_uring 使用 fio io_uring 进行性能测试。

sudo yum install -y alinux-release-experimentals
sudo yum install -y fio-3.17

fio 示例:

fio -name=fiotest -filename=/mnt/vdd/testfile -iodepth=128 -thread -rw=randread -ioengine=io_uring -sqthread_poll=1 -direct=1 -bs=4k -size=10G -numjobs=1 -runtime=600 -group_reporting

io_uring 高级特性

Fixed Files and Buffers

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

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 中都已封装完成。

io_uring 性能评测

Alibaba Cloud Linux 2 LTS 评测

我们基于 Alibaba Cloud Linux 2 LTS 的 fio 测试评估数据如下:

测试环境:本地 8C16G VM,后端为 raw 文件,存放在 NVMe 盘 ext4 文件系统上。
与云盘环境的不同的是,该虚拟磁盘没有 IO 能力规格的限制;云上建议使用 ESSD 或 NVMe SSD 本地盘。

  • 4k 顺序读
    image.png
  • 4k 顺序写
    image.png
  • 4k 随机读
    image.png
  • 4k 随机写
    image.png

从上述测试数据可以看出:

  • 默认模式下,读有 10% 左右的性能提升,写略微提升。
  • 开启 sqthread_poll 后,顺序读写提升很明显,达到 70% ~ 220%;随机读写提升相对较少,只有 10% ~ 30%。

社区性能数据

原作者 Jens 在 PATCHSET v5 中有分别对比 io_uring vs libaio,io_uring vs spdk 的 4k 随机读数据:
https://lore.kernel.org/linux-block/20190116175003.17880-1-axboe@kernel.dk/

测试结果如下:

  • 非 polling 模式,io_uring 相比 libaio 有略微提升。
  • 在 polling 模式下,io_uring 与 spdk 接近,甚至在 queue depth 较高时性能更好,完胜 libaio。

社区工作

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

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

性能优化

  • engines/io_uring: delete fio_option_is_set() calls when submitting sqes
  1. 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 & io_uring: initialize fixed_file_data lock

    重构 file register/unregister/update 特性,能更好地处理大量文件场景。
  • io_uring: do not always copy iovec in io_req_map_rw()
目录
相关文章
|
1月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
89 0
|
1月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
82 1
Linux C/C++之IO多路复用(aio)
|
3月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
1月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
24 0
Linux C/C++之IO多路复用(poll,epoll)
|
3月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
32 0
|
3月前
|
存储 IDE Linux
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
|
4月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
76 0
|
4月前
|
Linux 编译器 C语言
【Linux】基础IO----理解缓冲区
【Linux】基础IO----理解缓冲区
68 0
【Linux】基础IO----理解缓冲区
|
4月前
|
弹性计算 运维 Linux
基于在Alibaba Cloud Linux 3 的OS Copilot 产品评测
作为一名技术运维的工作者,每天做的最多一件事就是在Linux下敲各种各样的命令,或完成功能性的部署,或检查系统的稳定性。如果 OS Copilot 作为我的日常工具,我使用场景会更集中于快捷命令或脚本的实现,智能帮我生成各类功能脚本,为我提高运维效率。
基于在Alibaba Cloud Linux 3 的OS Copilot 产品评测
|
4月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
207 2