当前位置: 首页 > news >正文

C++ io_uring的使用小结

io_uring是 Linux 内核在 5.1 版本引入的一套全新的、高性能的异步 I/O (Asynchronous I/O) 接口。它的出现是为了解决旧有的epolllinux-aio在面对现代高速存储设备(如 NVMe SSD)和高并发网络场景时的性能瓶颈。

虽然io_uring是一个 C 语言的内核 API,但在 C++ 高性能网络编程和存储编程中,它正逐渐成为主流选择。

以下是对 C++io_uring的详细介绍,包括其原理、优势以及如何在 C++ 中使用它。

1. 为什么我们需要 io_uring?

io_uring出现之前,Linux 下主要有两种 I/O 模式:

  1. 同步 I/O (read/write) + 多路复用 (epoll):
    • 这是最主流的网络编程模式(如 Nginx, Redis, Node.js)。
    • 缺点:read/write是系统调用,每次调用都需要在用户态和内核态之间切换。对于海量小包处理,系统调用的开销非常大。此外,epoll只能通知“可读/可写”状态,实际的数据拷贝还是同步发生的。
  2. Linux Native AIO (libaio):
    • 缺点:仅支持 Direct I/O (O_DIRECT),对 Buffered I/O 支持很差(经常退化为同步阻塞)。API 设计复杂,且存在不必要的内存拷贝。

io_uring的目标:提供统一的、全异步的、零拷贝(或少拷贝)的、无锁的 I/O 接口,既支持文件 I/O 也支持网络 I/O。

2. io_uring 的核心原理:环形缓冲区 (Ring Buffer)

io_uring的名字来源于 “User Ring”。它在用户态和内核态之间共享了两个环形队列(Ring Buffer),从而避免了频繁的系统调用和内存拷贝。

这两个队列分别是:

  1. 提交队列 (Submission Queue, SQ):
    • 用户程序向这个队列中放入 I/O 请求(称为 SQE, Submission Queue Entry)。
    • 例如:“请把文件 A 的前 4KB 读取到缓冲区 B”。
  2. 完成队列 (Completion Queue, CQ):
    • 内核处理完请求后,将结果(称为 CQE, Completion Queue Entry)放入这个队列。
    • 用户程序从这里读取结果(例如:“读取成功,读取了 4096 字节”)。

工作流程:

  1. 用户将 SQE 放入 SQ。
  2. 用户通过一次系统调用 (io_uring_enter) 通知内核(或者在轮询模式下甚至不需要系统调用)。
  3. 内核从 SQ 获取请求并执行。
  4. 内核将结果写入 CQ。
  5. 用户从 CQ 读取结果。

3. C++ 中使用 io_uring (liburing)

直接操作内核的原始结构体非常繁琐且容易出错。因此,通常使用官方封装的 C 库liburing。在 C++ 中,我们通常直接调用liburing的 C 接口,或者使用对其进行 C++ 封装的库(如asio的 io_uring backend)。

下面是一个使用liburing进行异步文件读取的 C++ 示例。

前置准备

你需要安装liburing开发库:

1

2

# Ubuntu/Debian

sudoaptinstallliburing-dev

完整代码示例

这个例子展示了如何异步读取一个文件的前 1024 个字节。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

#include <iostream>

#include <fcntl.h>

#include <unistd.h>

#include <cstring>

#include <liburing.h>

#include <sys/stat.h>

// 定义队列深度,即环形缓冲区的大小

#define QUEUE_DEPTH 8

#define BLOCK_SZ 1024

intmain() {

// 1. 初始化 io_uring 结构

structio_uring ring;

// io_uring_queue_init(深度, 实例指针, 标志位)

// 0 表示默认配置

intret = io_uring_queue_init(QUEUE_DEPTH, &ring, 0);

if(ret < 0) {

std::cerr <<"io_uring_queue_init failed: "<< -ret << std::endl;

return1;

}

// 2. 打开文件 (使用 O_DIRECT 通常能发挥 io_uring 最大性能,但这里为了简单使用普通模式)

// 注意:实际项目中请确保文件存在,或者创建一个测试文件

intfd = open("test.txt", O_RDONLY);

if(fd < 0) {

// 如果文件不存在,创建一个临时的

fd = open("test.txt", O_RDWR | O_CREAT, 0644);

constchar* msg ="Hello from io_uring! This is a test file content.";

write(fd, msg,strlen(msg));

fsync(fd);

lseek(fd, 0, SEEK_SET);// 重置文件指针

}

// 准备缓冲区

charbuffer[BLOCK_SZ];

memset(buffer, 0, BLOCK_SZ);

structiovec iov;

iov.iov_base = buffer;

iov.iov_len = BLOCK_SZ;

// 3. 获取一个提交队列项 (SQE)

structio_uring_sqe *sqe = io_uring_get_sqe(&ring);

if(!sqe) {

std::cerr <<"Could not get SQE"<< std::endl;

return1;

}

// 4. 填充 SQE 请求

// 这是一个 "Read Vector" 操作

// 参数: sqe, 文件描述符, iovec数组, iovec数量, 偏移量

io_uring_prep_readv(sqe, fd, &iov, 1, 0);

// 设置用户数据 (user_data),这是一个 64 位字段,内核会原样传回 CQE。

// 通常用来存放请求的 ID 或者回调函数的指针。

io_uring_sqe_set_data(sqe, nullptr);// 这里简单设为 null

// 5. 提交请求给内核

// io_uring_submit 会调用系统调用 io_uring_enter

ret = io_uring_submit(&ring);

if(ret < 0) {

std::cerr <<"io_uring_submit failed: "<< -ret << std::endl;

return1;

}

std::cout <<"Request submitted, waiting for completion..."<< std::endl;

// 6. 等待完成队列项 (CQE)

structio_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;

return1;

}

// 7. 处理结果

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

return0;

}

代码编译

1

g++ -o uring_test uring_test.cpp -luring

4. io_uring 的高级特性

对于追求极致性能的 C++ 开发者,io_uring提供了几个杀手级特性:

A. Submission Queue Polling (SQPOLL)

默认情况下,io_uring_submit仍然需要一次系统调用 (io_uring_enter) 来通知内核有新任务。
如果在初始化时设置IORING_SETUP_SQPOLL标志,内核会启动一个专门的内核线程来轮询 SQ。

  • 效果:用户只需把 SQE 放入环形队列,内核线程自动发现并处理。完全消除了系统调用开销
  • 代价:消耗更多的 CPU 资源(内核线程一直在空转检查)。
http://www.rkmt.cn/news/1430987.html

相关文章:

  • MapLibre GL JS第29课:添加Canvas源
  • 2026年AI论文网站深度评测:6款工具全能表现得分排名
  • Win7离线环境救星:手把手教你修改4个XML和1个注册表,彻底解决VMware Converter 6.2无法启动服务报错
  • 实验一 常用网络命令的使用
  • Arduino雨水监测系统:从传感器原理到物联网报警实现
  • TrafficMonitor插件完全指南:如何将Windows任务栏打造成全能信息中心
  • 因民事养老金管理失误,英国政府拒绝向Capita授予5.63亿英镑合同
  • [开源] 多部门会签文档进度自动重建系统:面向医院行政与临床协同的OCR+状态机追踪工具
  • AnyFlip下载器:三步实现电子书PDF转换的跨平台解决方案
  • 老Mac焕新记:手把手教你用U盘和Ghost镜像给iMac安装纯净版Win7
  • 2026年5月更新:河北有实力的平台钢格板定制厂家选哪家?专业解析与推荐 - 2026年企业资讯
  • 第 20 篇 搭建 Kubernetes 实验环境:Minikube 与 kubectl
  • 2026年国内GEO服务商实力盘点:从短期流量到长效资产的转型之路 - GEO优化
  • 2026降AI率工具红黑榜:降AI率工具怎么选?一文讲透
  • 郑州茅台酒回收商家排行:郑州闲置酒水回收、郑州高价名酒回收、郑州高端名酒回收、郑州上门收茅台、郑州专业老酒回收选择指南 - 优质品牌商家
  • 2026年5月更新:聚焦安徽市场,甄选高性价比安全生产培训直销服务商 - 2026年企业资讯
  • 如何高效管理浏览器下载:Motrix WebExtension专业解决方案
  • Windows高DPI缩放总让你头疼?从‘模糊’到‘清晰’的完整设置指南(含Win10/11避坑清单)
  • C# 重写
  • 干货合集:2026年实测靠谱的专业降AI率平台
  • Perseus原生库:无偏移地址设计的游戏脚本补丁架构解析
  • Parallels Desktop 17保姆级教程:给CentOS 7虚拟机配个固定IP,开发调试再也不怕IP变来变去
  • Arduino电位器控制RGB LED:从模拟输入到PWM输出的完整实践
  • 2624张光伏缺陷图像:ELPV数据集如何重塑AI质检标准
  • 西安好阿姨家政,专业育婴师推荐的不二之选 - myqiye
  • Veo多场景视频生成合规红线清单,2024最新GDPR+《生成式AI服务管理暂行办法》双标适配指南
  • 福建外墙涂料多少钱?丽哆美价格合理 - mypinpai
  • 求推荐内蒙古生产小型水泥构件的源头厂家 - 工业品牌热点
  • 生成式AI视频侵权判定标准首次公开:国家版权中心2024新规解读与企业自查清单
  • 知识图谱与 Agent Harness 的深度融合