C++ io_uring的使用小結(jié)
io_uring 是 Linux 內(nèi)核在 5.1 版本引入的一套全新的、高性能的異步 I/O (Asynchronous I/O) 接口。它的出現(xiàn)是為了解決舊有的 epoll 和 linux-aio 在面對現(xiàn)代高速存儲設備(如 NVMe SSD)和高并發(fā)網(wǎng)絡場景時的性能瓶頸。
雖然 io_uring 是一個 C 語言的內(nèi)核 API,但在 C++ 高性能網(wǎng)絡編程和存儲編程中,它正逐漸成為主流選擇。
以下是對 C++ io_uring 的詳細介紹,包括其原理、優(yōu)勢以及如何在 C++ 中使用它。
1. 為什么我們需要 io_uring?
在 io_uring 出現(xiàn)之前,Linux 下主要有兩種 I/O 模式:
- 同步 I/O (read/write) + 多路復用 (epoll):
- 這是最主流的網(wǎng)絡編程模式(如 Nginx, Redis, Node.js)。
- 缺點:
read/write是系統(tǒng)調(diào)用,每次調(diào)用都需要在用戶態(tài)和內(nèi)核態(tài)之間切換。對于海量小包處理,系統(tǒng)調(diào)用的開銷非常大。此外,epoll只能通知“可讀/可寫”狀態(tài),實際的數(shù)據(jù)拷貝還是同步發(fā)生的。
- Linux Native AIO (libaio):
- 缺點: 僅支持 Direct I/O (O_DIRECT),對 Buffered I/O 支持很差(經(jīng)常退化為同步阻塞)。API 設計復雜,且存在不必要的內(nèi)存拷貝。
io_uring 的目標: 提供統(tǒng)一的、全異步的、零拷貝(或少拷貝)的、無鎖的 I/O 接口,既支持文件 I/O 也支持網(wǎng)絡 I/O。
2. io_uring 的核心原理:環(huán)形緩沖區(qū) (Ring Buffer)
io_uring 的名字來源于 “User Ring”。它在用戶態(tài)和內(nèi)核態(tài)之間共享了兩個環(huán)形隊列(Ring Buffer),從而避免了頻繁的系統(tǒng)調(diào)用和內(nèi)存拷貝。
這兩個隊列分別是:
- 提交隊列 (Submission Queue, SQ):
- 用戶程序向這個隊列中放入 I/O 請求(稱為 SQE, Submission Queue Entry)。
- 例如:“請把文件 A 的前 4KB 讀取到緩沖區(qū) B”。
- 完成隊列 (Completion Queue, CQ):
- 內(nèi)核處理完請求后,將結(jié)果(稱為 CQE, Completion Queue Entry)放入這個隊列。
- 用戶程序從這里讀取結(jié)果(例如:“讀取成功,讀取了 4096 字節(jié)”)。
工作流程:
- 用戶將 SQE 放入 SQ。
- 用戶通過一次系統(tǒng)調(diào)用 (
io_uring_enter) 通知內(nèi)核(或者在輪詢模式下甚至不需要系統(tǒng)調(diào)用)。 - 內(nèi)核從 SQ 獲取請求并執(zhí)行。
- 內(nèi)核將結(jié)果寫入 CQ。
- 用戶從 CQ 讀取結(jié)果。
3. C++ 中使用 io_uring (liburing)
直接操作內(nèi)核的原始結(jié)構(gòu)體非常繁瑣且容易出錯。因此,通常使用官方封裝的 C 庫 liburing。在 C++ 中,我們通常直接調(diào)用 liburing 的 C 接口,或者使用對其進行 C++ 封裝的庫(如 asio 的 io_uring backend)。
下面是一個使用 liburing 進行異步文件讀取的 C++ 示例。
前置準備
你需要安裝 liburing 開發(fā)庫:
# Ubuntu/Debian sudo apt install liburing-dev
完整代碼示例
這個例子展示了如何異步讀取一個文件的前 1024 個字節(jié)。
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <liburing.h>
#include <sys/stat.h>
// 定義隊列深度,即環(huán)形緩沖區(qū)的大小
#define QUEUE_DEPTH 8
#define BLOCK_SZ 1024
int main() {
// 1. 初始化 io_uring 結(jié)構(gòu)
struct io_uring ring;
// io_uring_queue_init(深度, 實例指針, 標志位)
// 0 表示默認配置
int ret = io_uring_queue_init(QUEUE_DEPTH, &ring, 0);
if (ret < 0) {
std::cerr << "io_uring_queue_init failed: " << -ret << std::endl;
return 1;
}
// 2. 打開文件 (使用 O_DIRECT 通常能發(fā)揮 io_uring 最大性能,但這里為了簡單使用普通模式)
// 注意:實際項目中請確保文件存在,或者創(chuàng)建一個測試文件
int fd = open("test.txt", O_RDONLY);
if (fd < 0) {
// 如果文件不存在,創(chuàng)建一個臨時的
fd = open("test.txt", O_RDWR | O_CREAT, 0644);
const char* msg = "Hello from io_uring! This is a test file content.";
write(fd, msg, strlen(msg));
fsync(fd);
lseek(fd, 0, SEEK_SET); // 重置文件指針
}
// 準備緩沖區(qū)
char buffer[BLOCK_SZ];
memset(buffer, 0, BLOCK_SZ);
struct iovec iov;
iov.iov_base = buffer;
iov.iov_len = BLOCK_SZ;
// 3. 獲取一個提交隊列項 (SQE)
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
if (!sqe) {
std::cerr << "Could not get SQE" << std::endl;
return 1;
}
// 4. 填充 SQE 請求
// 這是一個 "Read Vector" 操作
// 參數(shù): sqe, 文件描述符, iovec數(shù)組, iovec數(shù)量, 偏移量
io_uring_prep_readv(sqe, fd, &iov, 1, 0);
// 設置用戶數(shù)據(jù) (user_data),這是一個 64 位字段,內(nèi)核會原樣傳回 CQE。
// 通常用來存放請求的 ID 或者回調(diào)函數(shù)的指針。
io_uring_sqe_set_data(sqe, nullptr); // 這里簡單設為 null
// 5. 提交請求給內(nèi)核
// io_uring_submit 會調(diào)用系統(tǒng)調(diào)用 io_uring_enter
ret = io_uring_submit(&ring);
if (ret < 0) {
std::cerr << "io_uring_submit failed: " << -ret << std::endl;
return 1;
}
std::cout << "Request submitted, waiting for completion..." << std::endl;
// 6. 等待完成隊列項 (CQE)
struct io_uring_cqe *cqe;
// io_uring_wait_cqe 會阻塞直到至少有一個事件完成
ret = io_uring_wait_cqe(&ring, &cqe);
if (ret < 0) {
std::cerr << "io_uring_wait_cqe failed: " << -ret << std::endl;
return 1;
}
// 7. 處理結(jié)果
if (cqe->res < 0) {
std::cerr << "Async read failed: " << -cqe->res << std::endl;
} else {
std::cout << "Read " << cqe->res << " bytes." << std::endl;
std::cout << "Content: " << buffer << std::endl;
}
// 8. 標記 CQE 已處理 (這一步很重要,否則隊列會滿)
io_uring_cqe_seen(&ring, cqe);
// 9. 清理資源
close(fd);
io_uring_queue_exit(&ring);
return 0;
}
代碼編譯
g++ -o uring_test uring_test.cpp -luring
4. io_uring 的高級特性
對于追求極致性能的 C++ 開發(fā)者,io_uring 提供了幾個殺手級特性:
A. Submission Queue Polling (SQPOLL)
默認情況下,io_uring_submit 仍然需要一次系統(tǒng)調(diào)用 (io_uring_enter) 來通知內(nèi)核有新任務。
如果在初始化時設置 IORING_SETUP_SQPOLL 標志,內(nèi)核會啟動一個專門的內(nèi)核線程來輪詢 SQ。
- 效果: 用戶只需把 SQE 放入環(huán)形隊列,內(nèi)核線程自動發(fā)現(xiàn)并處理。完全消除了系統(tǒng)調(diào)用開銷。
- 代價: 消耗更多的 CPU 資源(內(nèi)核線程一直在空轉(zhuǎn)檢查)。
B. 注冊文件和緩沖區(qū) (Registered Files/Buffers)
在傳統(tǒng)的系統(tǒng)調(diào)用中,每次操作內(nèi)核都需要把文件描述符 (fd) 映射到內(nèi)部的文件結(jié)構(gòu),并鎖定內(nèi)存頁。
- Registered Files: 預先將 fd 數(shù)組注冊給 io_uring,后續(xù)請求直接使用索引,減少內(nèi)核查找 fd 的開銷。
- Registered Buffers: 預先將用戶態(tài)內(nèi)存鎖定并映射,避免每次 I/O 時內(nèi)核重復進行
get_user_pages操作。
C. 鏈接請求 (IOSQE_IO_LINK)
你可以將多個 SQE 鏈接起來,強制它們按順序執(zhí)行。例如:先 open 文件,成功后再 read,最后 close。這允許在一次系統(tǒng)調(diào)用中編排復雜的 I/O 邏輯。
5. C++ 生態(tài)中的 io_uring
雖然可以直接使用 liburing,但在現(xiàn)代 C++ 開發(fā)中,我們通常使用更高層的封裝:
Boost.Asio:
- Boost.Asio 已經(jīng)支持
io_uring作為底層的 Reactor 實現(xiàn)(在 Linux 上)。 - 通過
BOOST_ASIO_HAS_IO_URING宏啟用。這使得你可以在不改變現(xiàn)有 Asio 代碼邏輯的情況下,享受到io_uring的性能提升。
- Boost.Asio 已經(jīng)支持
Seastar:
- 一個高性能的 C++ futures 框架,專為現(xiàn)代硬件設計。它是
io_uring的早期采用者,非常適合構(gòu)建高吞吐量的網(wǎng)絡服務。
- 一個高性能的 C++ futures 框架,專為現(xiàn)代硬件設計。它是
Userver:
- Yandex 開源的 C++ 異步框架,底層也深度集成了
io_uring。
- Yandex 開源的 C++ 異步框架,底層也深度集成了
6. io_uring vs Epoll 性能對比
- 小包/高頻 I/O:
io_uring優(yōu)勢巨大。因為減少了系統(tǒng)調(diào)用次數(shù),上下文切換開銷大幅降低。 - 大包/低頻 I/O: 差距較小,瓶頸主要在內(nèi)存拷貝和硬件帶寬。
- Spectre/Meltdown 補丁影響: 這些 CPU 漏洞補丁增加了系統(tǒng)調(diào)用的開銷,因此
io_uring(尤其是 SQPOLL 模式)在受補丁影響的機器上優(yōu)勢更明顯。
7. 總結(jié)
C++ io_uring 是 Linux 高性能編程的未來。
- 核心優(yōu)勢: 真正的異步 I/O、減少系統(tǒng)調(diào)用、減少內(nèi)存拷貝、統(tǒng)一了網(wǎng)絡和磁盤 I/O 接口。
- 適用場景: 高并發(fā)網(wǎng)絡服務器(HTTP/RPC)、高性能數(shù)據(jù)庫、分布式存儲系統(tǒng)。
- 建議: 如果你在編寫通用的業(yè)務代碼,建議使用 Boost.Asio 等封裝好的庫;如果你在開發(fā)底層的存儲引擎或極致性能的網(wǎng)關(guān),深入學習并直接使用
liburing是非常有價值的。
到此這篇關(guān)于C++ io_uring的使用小結(jié)的文章就介紹到這了,更多相關(guān)C++ io_uring內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++命名空間?缺省參數(shù)?const總結(jié)?引用總結(jié)?內(nèi)聯(lián)函數(shù)?auto關(guān)鍵字詳解
這篇文章主要介紹了C++命名空間?缺省參數(shù)?const總結(jié)?引用總結(jié)?內(nèi)聯(lián)函數(shù)?auto關(guān)鍵字詳解的相關(guān)資料,需要的朋友可以參考下2023-01-01

