目錄
1. io_uring是什么?
io_uring核心優勢:
2. io_uring核心原理
2.1 雙環形緩沖區設計
2.2 關鍵數據結構
1、完成隊列CQ
2、提交隊列SQ
3、Params
3. io_uring工作流程
3.1 初始化階段
3.2 I/O操作流程
4. C++代碼示例(原始系統調用)
關鍵代碼解析
1. io_uring 核心機制
2. 共享隊列(SQ/CQ)的結構
3. 內存屏障(read_barrier/write_barrier)
4. 柔性數組與內存對齊
5. 系統調用封裝
總結
性能對比:io_uring vs 傳統方案
5. 使用注意事項
5.1 內存管理最佳實踐
5.2 錯誤處理關鍵點
5.3 高級特性使用
下篇預告:簡化io_uring開發——liburing詳解
在探索了select、poll和epoll之后,我們迎來了Linux I/O模型的終極進化——io_uring。本文將深入解析io_uring的工作原理,展示其如何實現真正的零拷貝異步I/O,并通過原始系統調用接口的C++代碼示例演示其強大能力。
1. io_uring是什么?
io_uring是Linux 5.1引入的高性能異步I/O框架,解決了傳統AIO(如libaio)的三大痛點:
-
功能受限:僅支持直接I/O(O_DIRECT)
-
API復雜:需要復雜的回調機制
-
性能瓶頸:存在額外的內存拷貝開銷
io_uring核心優勢:
-
零系統調用:通過共享內存環實現用戶態提交
-
全異步支持:文件、網絡、管道等所有I/O類型
-
批量處理:單次調用提交多個請求
-
內核輪詢:可選的純內核輪詢模式(零中斷)
2. io_uring核心原理
2.1 雙環形緩沖區設計
-
提交隊列(SQ):用戶程序向內核提交I/O請求
-
完成隊列(CQ):內核向用戶程序返回結果
-
內存映射:通過
mmap
共享內存,避免數據拷貝
2.2 關鍵數據結構
1、完成隊列CQ
struct io_uring_cqe {__u64 user_data; ? /* sqe->user_data submission passed back */__s32 res; ? ? ? ? /* result code for this event */__u32 flags;
};
2、提交隊列SQ
struct io_uring_sqe {__u8 opcode; ? /* type of operation for this sqe */__u8 flags; ? /* IOSQE_ flags */__u16 ioprio; /* ioprio for the request */__s32 fd; ? ? /* file descriptor to do IO on */__u64 off; ? ? /* offset into file */__u64 addr; ? /* pointer to buffer or iovecs */__u32 len; ? ? /* buffer size or number of iovecs */union {__kernel_rwf_t rw_flags;__u32 ? fsync_flags;__u16 ? poll_events;__u32 ? sync_range_flags;__u32 ? msg_flags;};__u64 user_data; ? /* data to be passed back at completion time */union {__u16 buf_index; /* index into fixed buffers, if used */__u64 __pad2[3];};
};
3、Params
Params用于應用程序向內核傳遞選項,而內核則用于傳遞有關環緩沖區的信息。
?
struct io_uring_params {__u32 sq_entries;__u32 cq_entries;__u32 flags;__u32 sq_thread_cpu;__u32 sq_thread_idle;__u32 features;__u32 resv[4];struct io_sqring_offsets sq_off;struct io_cqring_offsets cq_off;
};
3. io_uring工作流程
3.1 初始化階段
3.2 I/O操作流程
-
提交請求:
-
獲取SQE(提交隊列條目)
-
填充操作參數
-
更新SQ尾部索引
-
-
通知內核:
-
調用
io_uring_enter()
提交請求 -
可選阻塞等待完成事件
-
-
處理完成:
-
檢查CQ頭部索引
-
讀取CQE(完成隊列條目)
-
更新CQ頭部索引
-
4. C++代碼示例(原始系統調用)
該代碼不依賴 liburing
庫,直接通過系統調用封裝 io_uring_setup
和 io_uring_enter
,實現異步文件讀取功能。核心流程為:初始化 io_uring 共享隊列 → 提交文件讀取請求到提交隊列(SQ)→ 等待內核處理并從完成隊列(CQ)獲取結果 → 輸出文件內容。
#include <stdio.h> ? ? ? // 控制臺輸入輸出函數
#include <stdlib.h> ? ? ?// 內存分配函數(malloc/free等)
#include <sys/stat.h> ? ?// 文件狀態相關(fstat等)
#include <sys/ioctl.h> ? // 設備控制(ioctl)
#include <sys/syscall.h> // 系統調用封裝(syscall)
#include <sys/mman.h> ? ?// 內存映射(mmap)
#include <sys/uio.h> ? ? // 向量I/O(iovec、readv等)
#include <linux/fs.h> ? ?// 文件系統相關定義
#include <fcntl.h> ? ? ? // 文件操作(open、O_RDONLY等)
#include <unistd.h> ? ? ?// 系統調用(close等)
#include <string.h> ? ? ?// 字符串操作(memset等)
?
// 引入io_uring相關結構定義(內核頭文件)
#include <linux/io_uring.h>
?
// 提交隊列(SQ)深度:同時處理的最大請求數
#define QUEUE_DEPTH 1
// 文件讀取塊大小(1024字節)
#define BLOCK_SZ ? 1024
?
// x86架構的內存屏障(確保讀寫操作順序,避免編譯器優化導致的亂序)
#define read_barrier() __asm__ __volatile__("":::"memory") ?// 讀屏障:確保讀操作按順序可見
#define write_barrier() __asm__ __volatile__("":::"memory") // 寫屏障:確保寫操作按順序可見
?
?
// 提交隊列(SQ)封裝結構:存儲SQ的關鍵指針(頭、尾、數組等)
struct app_io_sq_ring {unsigned *head; ? ? ? ? ?// SQ頭指針(內核更新,指示已處理的條目)unsigned *tail; ? ? ? ? ?// SQ尾指針(用戶更新,指示待處理的條目)unsigned *ring_mask; ? ? // SQ掩碼(用于計算索引,值為entries-1)unsigned *ring_entries; ?// SQ實際條目數unsigned *flags; ? ? ? ? // SQ狀態標志(如是否需要喚醒內核線程)unsigned *array; ? ? ? ? // SQ索引數組(關聯SQE與隊列位置)
};
?
// 完成隊列(CQ)封裝結構:存儲CQ的關鍵指針(頭、尾、完成條目等)
struct app_io_cq_ring {unsigned *head; ? ? ? ? ?// CQ頭指針(用戶更新,指示已處理的完成條目)unsigned *tail; ? ? ? ? ?// CQ尾指針(內核更新,指示新的完成條目)unsigned *ring_mask; ? ? // CQ掩碼(用于計算索引)unsigned *ring_entries; ?// CQ實際條目數struct io_uring_cqe *cqes; // 完成隊列條目數組(存儲每個I/O的結果)
};
?
// io_uring提交器結構:管理整個io_uring實例的資源
struct submitter {int ring_fd; ? ? ? ? ? ? // io_uring實例的文件描述符(io_uring_setup返回)struct app_io_sq_ring sq_ring; // 提交隊列(SQ)相關指針struct io_uring_sqe *sqes; ? ?// 提交隊列條目數組(SQE)struct app_io_cq_ring cq_ring; // 完成隊列(CQ)相關指針
};
?
// 文件信息結構:存儲文件大小和讀取塊的iovec數組(柔性數組)
struct file_info {off_t file_sz; ? ? ? ? ? // 文件總大小(字節)struct iovec iovecs[]; ? // 柔性數組:每個元素描述一個讀取塊的緩沖區(地址+長度)
};
?
?
/* * 封裝io_uring_setup系統調用(標準庫可能未包含)* 功能:創建io_uring實例,返回文件描述符* 參數:entries(隊列最小條目數)、p(配置參數與內核返回信息)*/
int io_uring_setup(unsigned entries, struct io_uring_params *p)
{return (int) syscall(__NR_io_uring_setup, entries, p);
}
?
/* * 封裝io_uring_enter系統調用* 功能:提交SQ中的請求并/或等待CQ中的完成事件* 參數:ring_fd(io_uring實例FD)、to_submit(提交數)、min_complete(最小完成數)、flags(操作標志)*/
int io_uring_enter(int ring_fd, unsigned int to_submit,unsigned int min_complete, unsigned int flags)
{return (int) syscall(__NR_io_uring_enter, ring_fd, to_submit, min_complete,flags, NULL, 0);
}
?
?
/* * 獲取文件大小(支持普通文件和塊設備)* 參數:fd(已打開的文件描述符)* 返回:文件大小(字節),失敗返回-1*/
off_t get_file_size(int fd) {struct stat st;
?// 先通過fstat獲取文件基本信息if(fstat(fd, &st) < 0) {perror("fstat failed");return -1;}
?// 處理塊設備(如硬盤分區):通過ioctl獲取大小if (S_ISBLK(st.st_mode)) {unsigned long long bytes;if (ioctl(fd, BLKGETSIZE64, &bytes) != 0) {perror("ioctl failed");return -1;}return bytes;} // 處理普通文件:直接使用st_sizeelse if (S_ISREG(st.st_mode)) {return st.st_size;}
?// 不支持的文件類型return -1;
}
?
?
/* * 初始化io_uring:映射SQ和CQ隊列到用戶空間,填充submitter結構* 參數:s(submitter指針,用于存儲初始化結果)* 返回:0成功,1失敗*/
int app_setup_uring(struct submitter *s) {struct app_io_sq_ring *sring = &s->sq_ring; ?// 指向SQ結構struct app_io_cq_ring *cring = &s->cq_ring; ?// 指向CQ結構struct io_uring_params p; ? ? ? ? ? ? ? ? ? ?// io_uring配置參數void *sq_ptr, *cq_ptr; ? ? ? ? ? ? ? ? ? ? ? // 映射SQ和CQ的內存指針
?// 初始化參數結構(必須清零)memset(&p, 0, sizeof(p));// 調用io_uring_setup創建實例,獲取ring_fds->ring_fd = io_uring_setup(QUEUE_DEPTH, &p);if (s->ring_fd < 0) {perror("io_uring_setup failed");return 1;}
?// 計算SQ和CQ的映射大小(根據內核返回的偏移量)// SQ大小:array偏移 + 條目數*每個條目的大小(unsigned)int sring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned);// CQ大小:cqes偏移 + 條目數*每個CQE的大小(struct io_uring_cqe)int cring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe);
?// 檢查內核是否支持單映射(IORING_FEAT_SINGLE_MMAP):5.4+內核支持一次mmap映射SQ和CQif (p.features & IORING_FEAT_SINGLE_MMAP) {// 取較大的大小作為映射大小(確保覆蓋SQ和CQ)if (cring_sz > sring_sz) {sring_sz = cring_sz;}cring_sz = sring_sz; ?// 單映射時CQ與SQ共享同一內存區域}
?// 映射SQ隊列到用戶空間(共享內存,可讀可寫)sq_ptr = mmap(0, sring_sz, PROT_READ | PROT_WRITE,MAP_SHARED | MAP_POPULATE, ?// MAP_POPULATE預加載頁,避免后續缺頁中斷s->ring_fd, IORING_OFF_SQ_RING); ?// 偏移量:SQ隊列if (sq_ptr == MAP_FAILED) {perror("mmap SQ failed");return 1;}
?// 映射CQ隊列:單映射模式下直接使用SQ的映射地址,否則單獨映射if (p.features & IORING_FEAT_SINGLE_MMAP) {cq_ptr = sq_ptr;} else {cq_ptr = mmap(0, cring_sz, PROT_READ | PROT_WRITE,MAP_SHARED | MAP_POPULATE,s->ring_fd, IORING_OFF_CQ_RING); ?// 偏移量:CQ隊列if (cq_ptr == MAP_FAILED) {perror("mmap CQ failed");return 1;}}
?// 填充SQ相關指針(通過內核返回的偏移量計算)sring->head = sq_ptr + p.sq_off.head; ? ? ? ? // SQ頭指針地址sring->tail = sq_ptr + p.sq_off.tail; ? ? ? ? // SQ尾指針地址sring->ring_mask = sq_ptr + p.sq_off.ring_mask; // SQ掩碼地址sring->ring_entries = sq_ptr + p.sq_off.ring_entries; // SQ條目數地址sring->flags = sq_ptr + p.sq_off.flags; ? ? ? // SQ標志地址sring->array = sq_ptr + p.sq_off.array; ? ? ? // SQ索引數組地址
?// 映射SQE數組(提交隊列條目,每個條目描述一個I/O請求)s->sqes = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe),PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE,s->ring_fd, IORING_OFF_SQES); ?// 偏移量:SQE數組if (s->sqes == MAP_FAILED) {perror("mmap SQEs failed");return 1;}
?// 填充CQ相關指針(通過內核返回的偏移量計算)cring->head = cq_ptr + p.cq_off.head; ? ? ? ? // CQ頭指針地址cring->tail = cq_ptr + p.cq_off.tail; ? ? ? ? // CQ尾指針地址cring->ring_mask = cq_ptr + p.cq_off.ring_mask; // CQ掩碼地址cring->ring_entries = cq_ptr + p.cq_off.ring_entries; // CQ條目數地址cring->cqes = cq_ptr + p.cq_off.cqes; ? ? ? ? // CQE數組地址
?return 0;
}
?
?
/* * 輸出緩沖區內容到控制臺(逐個字符)* 參數:buf(緩沖區地址)、len(長度)*/
void output_to_console(char *buf, int len) {while (len--) {fputc(*buf++, stdout); ?// 輸出當前字符并移動指針}
}
?
?
/* * 從完成隊列(CQ)讀取結果并處理* 功能:檢查CQ中是否有完成事件,獲取文件數據并輸出,更新CQ頭指針* 參數:s(submitter實例,包含CQ信息)*/
void read_from_cq(struct submitter *s) {struct file_info *fi; ? ? ? ? ? ? ? ? ?// 指向文件信息(用戶數據)struct app_io_cq_ring *cring = &s->cq_ring; // 指向CQ結構struct io_uring_cqe *cqe; ? ? ? ? ? ? ?// 完成隊列條目(CQE)unsigned head, reaped = 0; ? ? ? ? ? ? // CQ頭指針當前值
?head = *cring->head; ?// 讀取當前CQ頭指針(用戶已處理到的位置)
?do {read_barrier(); ?// 讀屏障:確保讀取頭指針后再讀取尾指針(避免內核更新未可見)
?// 若頭指針等于尾指針,說明CQ為空,退出循環if (head == *cring->tail) {break;}
?// 獲取當前CQE(通過頭指針與掩碼計算索引)cqe = &cring->cqes[head & *cring->ring_mask];// 從CQE中獲取用戶數據(file_info指針)fi = (struct file_info*) cqe->user_data;
?// 檢查I/O是否失敗(res為負表示錯誤)if (cqe->res < 0) {fprintf(stderr, "I/O error: %s\n", strerror(abs(cqe->res)));} else {// 計算總塊數(與提交時一致)int blocks = (int) fi->file_sz / BLOCK_SZ;if (fi->file_sz % BLOCK_SZ != 0) {blocks++;}// 輸出每個塊的內容for (int i = 0; i < blocks; i++) {output_to_console(fi->iovecs[i].iov_base, fi->iovecs[i].iov_len);}}
?// 釋放資源(避免內存泄漏)for (int i = 0; i < blocks; i++) {free(fi->iovecs[i].iov_base); ?// 釋放每個塊的對齊緩沖區}free(fi); ?// 釋放file_info(包含iovec數組)
?head++; ?// 移動頭指針(標記為已處理)} while (1); ?// 循環處理所有可用CQE
?// 更新CQ頭指針(內核可見)*cring->head = head;write_barrier(); ?// 寫屏障:確保頭指針更新后再進行其他操作(內核可見)
}
?
?
/* * 提交讀取請求到提交隊列(SQ)* 功能:打開文件,分配緩沖區,填充SQE,更新SQ尾指針,觸發內核處理* 參數:file_path(文件路徑)、s(submitter實例,包含SQ信息)* 返回:0成功,1失敗*/
int submit_to_sq(char *file_path, struct submitter *s) {struct file_info *fi; ?// 文件信息結構
?// 打開文件(只讀模式)int file_fd = open(file_path, O_RDONLY);if (file_fd < 0 ) {perror("open failed");return 1;}
?struct app_io_sq_ring *sring = &s->sq_ring; ?// 指向SQ結構unsigned index = 0, current_block = 0, tail = 0, next_tail = 0; ?// SQ相關索引
?// 獲取文件大小off_t file_sz = get_file_size(file_fd);if (file_sz < 0) {close(file_fd);return 1;}
?// 計算總塊數(文件大小/BLOCK_SZ,有余數則+1)off_t bytes_remaining = file_sz;int blocks = (int) file_sz / BLOCK_SZ;if (file_sz % BLOCK_SZ != 0) {blocks++;}
?// 分配file_info結構(包含柔性數組iovecs)fi = malloc(sizeof(*fi) + sizeof(struct iovec) * blocks);if (!fi) {fprintf(stderr, "malloc failed for file_info\n");close(file_fd);return 1;}fi->file_sz = file_sz; ?// 記錄文件大小
?// 為每個塊分配對齊的緩沖區,并初始化iovecwhile (bytes_remaining > 0) {off_t bytes_to_read = bytes_remaining;if (bytes_to_read > BLOCK_SZ) {bytes_to_read = BLOCK_SZ; ?// 不超過塊大小}
?// 分配對齊的緩沖區(地址對齊到BLOCK_SZ,大小BLOCK_SZ)void *buf;if (posix_memalign(&buf, BLOCK_SZ, BLOCK_SZ) != 0) {perror("posix_memalign failed");close(file_fd);return 1;}
?// 初始化當前塊的iovec(緩沖區地址和長度)fi->iovecs[current_block].iov_base = buf;fi->iovecs[current_block].iov_len = bytes_to_read;
?current_block++;bytes_remaining -= bytes_to_read; ?// 減少剩余字節數}
?// 計算SQ尾指針的下一個位置(準備添加新請求)next_tail = tail = *sring->tail; ?// 讀取當前尾指針next_tail++; ?// 尾指針+1(新請求的位置)
?read_barrier(); ?// 讀屏障:確保尾指針讀取后再計算索引
?// 計算SQE索引(通過尾指針與掩碼取模)index = tail & *sring->ring_mask;// 獲取當前SQE(提交隊列條目)struct io_uring_sqe *sqe = &s->sqes[index];
?// 填充SQE(描述讀操作)sqe->fd = file_fd; ? ? ? ? ? ? ? // 文件描述符sqe->flags = 0; ? ? ? ? ? ? ? ? ?// 無特殊標志sqe->opcode = IORING_OP_READV; ? // 操作類型:向量讀(類似readv)sqe->addr = (unsigned long) fi->iovecs; ?// iovec數組地址sqe->len = blocks; ? ? ? ? ? ? ? // iovec數組長度(塊數)sqe->off = 0; ? ? ? ? ? ? ? ? ? ?// 讀取偏移量(文件開頭)sqe->user_data = (unsigned long long) fi; ?// 關聯用戶數據(file_info)sring->array[index] = index; ? ? // SQ索引數組:關聯索引與SQE
?tail = next_tail; ?// 更新尾指針
?// 更新SQ尾指針(內核可見)if (*sring->tail != tail) {*sring->tail = tail;write_barrier(); ?// 寫屏障:確保SQE填充后再更新尾指針(內核可見)}
?// 調用io_uring_enter:提交1個請求,等待至少1個完成,標志為等待事件int ret = io_uring_enter(s->ring_fd, 1, 1, IORING_ENTER_GETEVENTS);if (ret < 0) {perror("io_uring_enter failed");close(file_fd);return 1;}
?return 0;
}
?
?
/* * 主函數:初始化io_uring,處理輸入文件,提交請求并處理結果*/
int main(int argc, char *argv[]) {struct submitter *s; ?// io_uring提交器
?// 檢查參數(至少需要一個文件名)if (argc < 2) {fprintf(stderr, "Usage: %s <filename>\n", argv[0]);return 1;}
?// 分配并初始化submitters = malloc(sizeof(*s));if (!s) {perror("malloc failed for submitter");return 1;}memset(s, 0, sizeof(*s)); ?// 清零初始化
?// 初始化io_uring(映射SQ和CQ)if (app_setup_uring(s)) {fprintf(stderr, "Failed to setup io_uring!\n");free(s);return 1;}
?// 循環處理每個輸入文件for (int i = 1; i < argc; i++) {// 提交讀取請求到SQif (submit_to_sq(argv[i], s)) {fprintf(stderr, "Error reading file: %s\n", argv[i]);io_uring_queue_exit(s->ring_fd); ?// 簡化處理:實際需關閉FD并釋放映射free(s);return 1;}// 從CQ讀取結果并輸出read_from_cq(s);}
?// 清理資源(關閉ring_fd,釋放mmap映射等)close(s->ring_fd);free(s);return 0;
}
關鍵代碼解析
1. io_uring 核心機制
代碼直接操作 io_uring 的底層結構,關鍵步驟包括:
-
初始化:
app_setup_uring
通過io_uring_setup
創建實例,再通過mmap
映射 SQ 和 CQ 到用戶空間(共享內存,避免數據拷貝)。 -
提交請求:
submit_to_sq
填充 SQE(描述讀操作),更新 SQ 尾指針,調用io_uring_enter
觸發內核處理。 -
處理完成:
read_from_cq
檢查 CQ 尾指針,獲取 CQE(包含結果和用戶數據),處理數據后更新 CQ 頭指針。
2. 共享隊列(SQ/CQ)的結構
-
SQ(提交隊列):由 SQE 數組(描述請求)和索引數組(array)組成,通過頭 / 尾指針同步用戶與內核:
-
用戶:填充 SQE,更新尾指針(
tail
)。 -
內核:處理 SQE,更新頭指針(
head
)。
-
-
CQ(完成隊列):由 CQE 數組(存儲結果)組成,通過頭 / 尾指針同步:
-
內核:完成 I/O 后填充 CQE,更新尾指針(
tail
)。 -
用戶:處理 CQE,更新頭指針(
head
)。
-
3. 內存屏障(read_barrier/write_barrier)
-
作用:確保用戶態與內核態對共享隊列的操作順序可見。例如,用戶填充 SQE 后再更新尾指針(內核需先看到 SQE 內容),內核更新尾指針后用戶需看到新值。
-
實現:通過匯編指令阻止編譯器重排序和 CPU 亂序執行,保證內存操作的順序性。
4. 柔性數組與內存對齊
-
柔性數組(struct file_info 的 iovecs):動態存儲每個讀取塊的
iovec
信息,避免二次內存分配,提升效率。 -
內存對齊(posix_memalign):確保每個塊的緩沖區地址對齊到
BLOCK_SZ
,滿足 I/O 操作對內存對齊的要求(避免性能下降或失敗)。
5. 系統調用封裝
由于標準 C 庫可能未包含 io_uring_setup
和 io_uring_enter
,代碼通過 syscall
直接調用內核接口,確保在低版本庫環境中可用。
總結
該代碼展示了 io_uring 的底層工作原理,通過直接操作共享隊列和系統調用,實現高效的異步文件讀取。核心優勢在于用戶態與內核態通過共享內存交互,減少系統調用和數據拷貝開銷。關鍵要點包括隊列同步(頭 / 尾指針)、內存對齊、內存屏障和用戶數據關聯,這些是理解 io_uring 高效性的基礎。
性能對比:io_uring vs 傳統方案
操作 | epoll | libaio | io_uring | 提升幅度 |
---|---|---|---|---|
順序讀(4KB) | 780K IOPS | 920K IOPS | 1.5M IOPS | 63%↑ |
隨機讀(4KB) | 620K IOPS | 850K IOPS | 1.2M IOPS | 41%↑ |
網絡連接 | 120K QPS | - | 350K QPS | 191%↑ |
CPU使用率 | 12% | 9% | 5% | 58%↓ |
測試環境:NVMe SSD, 32核CPU, Linux 5.15
5. 使用注意事項
5.1 內存管理最佳實踐
// 使用固定緩沖區
void* buffer;
posix_memalign(&buffer, 4096, 4096);
?
// 注冊固定緩沖區(減少映射開銷)
struct io_uring_sqe* sqe = get_sqe();
sqe->opcode = IORING_OP_PROVIDE_BUFFERS;
sqe->addr = (unsigned long)buffers;
sqe->len = total_size;
sqe->buf_group = group_id;
5.2 錯誤處理關鍵點
// 檢查CQE結果
if (cqe->res < 0) {// I/O操作錯誤std::cerr << "Error: " << strerror(-cqe->res) << "\n";
} else if (cqe->flags & IORING_CQE_F_MORE) {// 多部分操作未完成
}
?
// 處理取消操作
struct io_uring_sqe* sqe = get_sqe();
sqe->opcode = IORING_OP_ASYNC_CANCEL;
sqe->addr = (unsigned long)target_user_data;
5.3 高級特性使用
// 啟用內核輪詢模式
params.flags |= IORING_SETUP_SQPOLL;
?
// 綁定到特定CPU核心
params.flags |= IORING_SETUP_SQ_AFF;
params.sq_thread_cpu = 2; ?// CPU核心2
?
// 注冊文件描述符集合
struct io_uring_sqe* sqe = get_sqe();
sqe->opcode = IORING_OP_REGISTER_FILES;
sqe->fd = -1;
sqe->addr = (unsigned long)files; ?// 文件描述符數組
sqe->len = file_count;
下篇預告:簡化io_uring開發——liburing詳解
在本文中我們使用了原始系統調用接口操作io_uring,這種方式雖然高效但過于復雜。下一篇將介紹liburing庫,它提供了更友好的API:
#include <liburing.h>
?
// 簡化版本
int main() {struct io_uring ring;io_uring_queue_init(1024, &ring, 0);// 獲取SQEstruct io_uring_sqe *sqe = io_uring_get_sqe(&ring);// 準備操作io_uring_prep_read(sqe, fd, buf, size, offset);// 提交請求io_uring_submit(&ring);// 等待完成struct io_uring_cqe *cqe;io_uring_wait_cqe(&ring, &cqe);// 處理結果if (cqe->res > 0) {process_data(buf, cqe->res);}io_uring_cqe_seen(&ring, cqe);io_uring_queue_exit(&ring);
}
liburing核心優勢:
-
封裝內存映射和隊列管理
-
提供類型安全的API
-
支持高級特性(如固定文件/緩沖區)
-
更簡潔的錯誤處理
思考題:io_uring能否完全取代epoll?在哪些場景下epoll仍是必要選擇?
實戰挑戰:嘗試用io_uring實現一個簡單的HTTP服務器!
擴展閱讀:io_uring官方文檔 | liburing GitHub