概述
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),核心是完成事件的生産者,應用是消費者。

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 順序讀
Alibaba Cloud Linux 2 LTS 率先提供支援 io_uring概述Linux IO 發展史io_uring 原理介紹io_uring 進階特性io_uring 性能評測社群工作 - 4k 順序寫
Alibaba Cloud Linux 2 LTS 率先提供支援 io_uring概述Linux IO 發展史io_uring 原理介紹io_uring 進階特性io_uring 性能評測社群工作 - 4k 随機讀
Alibaba Cloud Linux 2 LTS 率先提供支援 io_uring概述Linux IO 發展史io_uring 原理介紹io_uring 進階特性io_uring 性能評測社群工作 - 4k 随機寫
Alibaba Cloud Linux 2 LTS 率先提供支援 io_uring概述Linux IO 發展史io_uring 原理介紹io_uring 進階特性io_uring 性能評測社群工作
從上述測試資料可以看出:
- 預設模式下,讀有 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/[email protected]/測試結果如下:
- 非 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
- 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()