凌云时刻 · 技术
导读:Alibaba Cloud Linux 2 是阿里云操作系统团队基于开源 Linux 4.19 LTS 版本打造的一款针对云应用场景的下一代 Linux OS 发行版。在首次推出一年后,阿里云操作系统团队对外正式发布了Alibaba Cloud Linux 2 LTS 版本。
作者 | 窅默
来源 | 云癫论剑
概述
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),内核是完成事件的生产者,应用是消费者。
共享环的设计主要带来以下 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
#include
#include
#include
#include
#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: \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 高级特性
IORING_SETUP_IOPOLL,与非 polling 模式等待硬件中断唤醒不同,内核将采用 polling 模式不断轮询硬件以确认IO请求是否已经完成,这在追求低延时和高 IOPS 的应用场景非常有用。
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 中都已封装完成。
IORING_REGISTER_FILES/IORING_REGISTER_FILES_UPDATE/ 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() 带来的开销。
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
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
END
往期精彩文章回顾
io_uring 新异步 IO 机制,性能提升超 150%,堪比 SPDK
如何写好代码?
如何解决大规模高性能存储可靠性问题?
掌门教育微服务体系 Solar(中)
如何应对容器和云原生时代的安全挑战?
融合阿里云,牛客助您找到心仪好工作
掌门教育微服务体系 Solar
阿里云上新了!
我们该不该在Rust上做点投资?
长按扫描二维码关注凌云时刻
每日收获前沿技术与科技洞见