天天看點

Alibaba Cloud Linux 2 LTS 率先提供支援 io_uring概述Linux IO 發展史io_uring 原理介紹io_uring 進階特性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 的客戶提供更多保障。

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

Alibaba Cloud Linux 2 LTS 率先提供支援 io_uring概述Linux IO 發展史io_uring 原理介紹io_uring 進階特性io_uring 性能評測社群工作

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
  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()