Linux异步IO新时代:io_uring

简介: # 引言 Linux 5.1合入了一个新的异步IO框架和实现:io_uring,由block IO大神Jens Axboe开发。这对当前异步IO领域无疑是一个喜大普奔的消息,这意味着,Linux native aio的时代即将成为过去,io_uring的时代即将开启。 Aliyun Linux 2正计划将io_uring backport,届时欢迎尝鲜。 # 从Linux IO说起 L

引言

Linux 5.1合入了一个新的异步IO框架和实现:io_uring,由block IO大神Jens Axboe开发。这对当前异步IO领域无疑是一个喜大普奔的消息,这意味着,Linux native aio的时代即将成为过去,io_uring的时代即将开启。
Aliyun Linux 2正计划将io_uring backport,届时欢迎尝鲜。

从Linux IO说起

Linux最初的IO系统调用需要追溯到read(2)和write(2)。后来发展为增加offset的pread(2)和pwrite(2),以及基于vector的版本preadv(2)和pwritev(2)。再接下来扩展为preadv2(2)和pwritev2(2)。尽管形式看上去多种多样,但它们都有一个共同的特征就是同步,即系统调用需要在数据读取或写入后才返回。
应某些应用场景的诉求,异步IO接口应势而生。POSIX对应的接口为aio_read(3)和aio_write(3),但其实现平淡无奇且性能不好。Linux Native异步IO接口即我们常称的aio,同样有着诸多限制:

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

相比新开发一套接口,扩展和改进现有接口往往有着更多的优势。在过去的数年间,针对上述限制一的很多改进努力都未果。尤其是近年来快速设备的出现,更加体现出现有异步接口的局限性。

io_uring

新接口的设计要求如下,尽管有些看上去存在互斥性,如高效且可伸缩的接口往往比较难用,或者说难以正确地使用;特性丰富和高效很难同时满足等等。

  • 简单易用;
  • 可扩展,如为未来的网络/非块存储IO接入考虑;
  • 特性丰富,满足所有应用;
  • 高效,尤其是针对大部分场景的512B或4K IO;
  • 可伸缩。

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

数据结构

/*
 * IO submission data structure (Submission Queue Entry)
 */
struct io_uring_sqe {
    __u8    opcode;         /* type of operation for this sqe */
    __u8    flags;          /* IOSQE_ flags */
    __u16   ioprio;         /* ioprio for the request */
    __s32   fd;             /* file descriptor to do IO on */
    __u64   off;            /* offset into file */
    __u64   addr;           /* pointer to buffer or iovecs */
    __u32   len;            /* buffer size or number of iovecs */
    union {
        __kernel_rwf_t  rw_flags;
        __u32           fsync_flags;
        __u16           poll_events;
    };
    __u64   user_data;      /* data to be passed back at completion time */
    union {
        __u16   buf_index;      /* index into fixed buffers, if used */
        __u64   __pad2[3];
    };
};

/*
 * IO completion data structure (Completion Queue Entry)
 */
struct io_uring_cqe {
    __u64   user_data;      /* sqe->data submission passed back */
    __s32   res;            /* result code for this event */
    __u32   flags;
};

系统调用

/* 
 * setup a context for performing asynchronous I/O
 *
 * The io_uring_setup() system call sets up a submission queue (SQ) and completion queue (CQ) with 
 * at least entries entries, and returns a file descriptor which can be used to perform subsequent 
 * operations on the io_uring instance.The submission and completion queues are shared between 
 * userspace and the kernel, which eliminates the need to copy data when initiating and completing 
 * I/O.
 */
int io_uring_setup(u32 entries, struct io_uring_params *p);

/*
 * initiate and/or complete asynchronous I/O
 *
 * io_uring_enter() is used to initiate and complete I/O using the shared submission and completion 
 * queues setup by a call to io_uring_setup(2).  A single call can both submit new I/O and wait for 
 * completions of I/O initiated by this call or previous calls to io_uring_enter().
 */
int io_uring_enter(unsigned int fd, unsigned int to_submit,
                unsigned int min_complete, unsigned int flags,
                sigset_t *sig);

/*
 * register files or user buffers for asynchronous I/O
 *
 * The io_uring_register() system call registers user buffers or files for use in an io_uring(7) instance 
 * referenced by fd.  Registering files or user buffers allows the kernel to take long term references to 
 * internal data structures or create long term mappings of application memory, greatly reducing
 * per-I/O overhead.
 */
int io_uring_register(unsigned int fd, unsigned int opcode,
                void *arg, unsigned int nr_args)

liburing

为了方便使用,Jens Axboe还开发了一套liburing库,同时在fio中提供了ioengine=io_uring的支持。通过liburing库,应用不必了解诸多io_uring的细节就可以简单地使用起来。例如,无需担心memory barrier,或者是ring buffer管理之类等。简单的example如下:

/* setup io_uring and do mmap */
io_uring_queue_init(ENTRIES, &ring, 0);

/* get an sqe and fill in a READV operation */
sqe = io_uring_get_sqe(&ring);
io_uring_prep_readv(sqe, fd, &iovec, 1, offset);

/* tell the kernel we have an sqe ready for consumption */
io_uring_submit(&ring);

/* wait for the sqe to complete */
io_uring_wait_cqe(&ring, &cqe);

/* read and process cqe event */
io_uring_cqe_seen(&ring, cqe);

/* tear down */
io_uring_queue_exit(&ring);

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都要去做。

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

io_uring性能

注:数据来源于io_uring的patchset v5。

  • 3d xpoint, 4k random read
Interface    QD    Polled        Latency        IOPS
--------------------------------------------------------------------------
io_uring    1    0         9.5usec     77K
io_uring    2    0         8.2usec    183K
io_uring    4    0         8.4usec    383K
io_uring    8    0        13.3usec    449K

libaio        1    0         9.7usec     74K
libaio        2    0         8.5usec    181K
libaio        4    0         8.5usec    373K
libaio        8    0        15.4usec    402K

io_uring    1    1         6.1usec    139K
io_uring    2    1         6.1usec    272K    
io_uring    4    1         6.3usec    519K
io_uring    8    1        11.5usec    592K

spdk        1    1         6.1usec    151K
spdk        2    1         6.2usec    293K
spdk        4    1         6.7usec    536K
spdk        8    1        12.6usec    586K

非polling模式,io_uring相比libaio提升不是很明显;在polling模式下,io_uring能与spdk接近,甚至在queue depth较高时性能更好,完爆libaio。

  • Peak IOPS, 512b random read
Interface    QD    Polled        Latency        IOPS
--------------------------------------------------------------------------
io_uring    4    1         6.8usec     513K
io_uring    8    1         8.7usec     829K
io_uring    16    1        13.1usec    1019K
io_uring    32    1        20.6usec    1161K
io_uring    64    1        32.4usec    1244K

spdk        4    1         6.8usec     549K
spdk        8    1         8.6usec     865K
spdk        16    1        14.0usec    1105K
spdk        32    1        25.0usec    1227K
spdk        64    1        47.3usec    1251K

在queue depth较低时有约7%的差距,但在queue depth较高时基本接近。

  • Peak per-core, multiple devices, 4k random read
Interface    QD    Polled        IOPS
--------------------------------------------------------------------------
io_uring    128    1        1620K
libaio        128    0         608K
spdk        128    1        1739K

注:根据引用文档1的描述,per-core极限性能已经能到约1700K。

Reference

https://kernelnewbies.org/Linux_5.1
https://lwn.net/Articles/776703/
http://kernel.dk/io_uring.pdf [1]

目录
相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
99 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
115 1
Linux C/C++之IO多路复用(aio)
|
2月前
|
并行计算 数据处理 Python
Python并发编程迷雾:IO密集型为何偏爱异步?CPU密集型又该如何应对?
在Python的并发编程世界中,没有万能的解决方案,只有最适合特定场景的方法。希望本文能够为你拨开迷雾,找到那条通往高效并发编程的光明大道。
47 2
|
3月前
|
开发框架 并行计算 算法
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
55 4
|
4月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
38 0
Linux C/C++之IO多路复用(poll,epoll)
|
3月前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
在数据驱动时代,高效处理大规模数据和高并发请求至关重要。Python凭借其优雅的语法和强大的库支持,成为开发者首选。本文将介绍Python中的并发与异步编程,涵盖并发与异步的基本概念、IO密集型任务的并发策略、CPU密集型任务的并发策略以及异步IO的应用。通过具体示例,展示如何使用`concurrent.futures`、`asyncio`和`multiprocessing`等库提升程序性能,帮助开发者构建高效、可扩展的应用程序。
158 0
|
4月前
|
小程序 Linux 开发者
Linux之缓冲区与C库IO函数简单模拟
通过上述编程实例,可以对Linux系统中缓冲区和C库IO函数如何提高文件读写效率有了一个基本的了解。开发者需要根据应用程序的具体需求来选择合适的IO策略。
38 0
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用