導讀: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 社區所做的工作。
為了從根本上解決當前 Linux aio 存在的問題和約束,io_uring 從零開始全新設計的了異步 IO 框架。其設計的主要目標如下:
io_uring 為了避免在提交和完成事件中的內存拷貝,設計了一對共享的 ring buffer 用於應用和內核之間的通信。其中,針對提交隊列(SQ),應用是 IO 提交的生產者(producer),內核是消費者(consumer);反過來,針對完成隊列(CQ),內核是完成事件的生產者,應用是消費者。
共享環的設計主要帶來以下 3 個好處:
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 版本支持的異步操作如下,更多的特性支持持續完善中。
為了簡化使用,原作者 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 <fcntl.h>include <stdio.h>define ENTRIES 4int main(int argc, char *argv[]){ struct io_uring ring; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct iovec iov = { .iov_base = &34;, .iov_len = strlen(&34;), }; int fd, ret; if (argc != 2) { printf(&34;, argv[0]); return 1; } /* setup io_uring and do mmap */ ret = io_uring_queue_init(ENTRIES, &ring, 0); if (ret < 0) { printf(&34;, strerror(-ret)); return 1; } fd = open(&34;, O_WRONLY | O_CREAT); if (fd < 0) { printf(&34;); ret = 1; goto exit; } /* get an sqe and fill in a WRITEV operation */ sqe = io_uring_get_sqe(&ring); if (!sqe) { printf(&34;); 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(&34;, strerror(-ret)); goto out; } /* wait for the sqe to complete */ ret = io_uring_wait_cqe(&ring, &cqe); if (ret < 0) { printf(&34;, 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;}
→ 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 的穩定性,相關工作以補丁的形式回饋到社區。
→ 性能優化