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

io_setup系统调用及示例

我们来深入学习 io_setupio_submit 系统调用,从 Linux 编程小白的角度出发。

1. 函数介绍

在 Linux 系统编程中,进行文件 I/O 操作(如 read, write)通常是同步的。这意味着当你的程序调用 read(fd, buffer, size) 时,程序会一直等待,直到内核从磁盘(或网络、设备等)读取完数据并放入 buffer 中,然后 read 函数才返回。如果数据读取很慢(例如从机械硬盘读取大量数据),你的程序就会在这段时间内卡住,无法执行其他任务。

为了提高性能,特别是对于高并发的服务器程序,Linux 提供了异步 I/O (Asynchronous I/O, AIO) 机制。核心思想是:

1. 提交请求:你告诉内核:“请帮我从文件描述符 fd 读取数据到 buffer”,然后你的程序立即返回,可以去做其他事情。
2. 内核处理:内核在后台执行这个读取操作。
3. 获取结果:过一段时间后,你再询问内核:“之前那个读取操作完成了吗?”。如果完成了,内核会告诉你结果(读取了多少字节,是否出错等)。

io_setupio_submit 就是这个异步 I/O 机制的第一步第二步

  • io_setup: 创建一个异步 I/O 上下文 (context)。你可以把它想象成一个“工作队列”或“任务列表”的管理器。所有你提交给这个上下文的异步 I/O 请求都会被它管理。
  • io_submit: 提交一个或多个异步 I/O 请求(读、写等)到一个已创建的上下文中。内核会接收这些请求,并在后台开始执行它们。

简单来说

  • io_setup:创建一个“异步任务管理器”。
  • io_submit:把“异步任务”(读/写文件)交给这个“管理器”去执行。

2. 函数原型

// 需要定义宏来启用 AIO
#define _GNU_SOURCE
#include <linux/aio_abi.h> // 包含 AIO 相关结构体和常量 (io_context_t, iocb)
#include <sys/syscall.h>   // 包含 syscall 函数和系统调用号
#include <unistd.h>        // 包含 syscall 函数// io_setup 系统调用
long syscall(SYS_io_setup, unsigned nr_events, io_context_t *ctxp);// io_submit 系统调用
long syscall(SYS_io_submit, io_context_t ctx_id, long nr, struct iocb **iocbpp);

注意
1. 这些是底层系统调用。标准 C 库(glibc)可能不直接提供用户友好的包装函数。
2. 通常需要通过 syscall() 函数并传入系统调用号来调用它们。
3. 需要包含 linux/aio_abi.h 头文件来获取相关结构体和类型定义。

3. 功能

  • io_setup: 初始化一个异步 I/O 上下文,该上下文能够处理最多 nr_events 个并发的异步 I/O 操作,并将上下文的标识符(句柄)存储在 ctxp 指向的变量中。
  • io_submit: 将 nr 个异步 I/O 请求(由 iocbpp 数组指向)提交到由 ctx_id 标识的异步 I/O 上下文中。内核会尝试立即开始处理这些请求。

4. 参数详解

io_setup(unsigned nr_events, io_context_t *ctxp)
  • nr_events:
    • unsigned 类型。
    • 指定这个异步 I/O 上下文最多可以同时处理多少个未完成的异步 I/O 请求(事件)。这相当于预分配了资源来跟踪这些请求。
    • 内核可能会将这个值向上舍入到内部优化所需的大小。
  • ctxp:
    • io_context_t * 类型。
    • 一个指向 io_context_t 类型变量的指针。io_setup 调用成功后,会将新创建的异步 I/O 上下文的标识符(或句柄)写入到这个变量中。后续的 io_submit, io_getevents 等操作都需要使用这个 ctx_id
io_submit(io_context_t ctx_id, long nr, struct iocb **iocbpp)
  • ctx_id:
    • io_context_t 类型。
    • io_setup 返回的异步 I/O 上下文的标识符。
  • nr:
    • long 类型。
    • 指定要提交的异步 I/O 请求数量。这个值应该与 iocbpp 数组的大小相对应。
  • iocbpp:
    • struct iocb ** 类型。
    • 一个指针数组,数组中的每个元素都指向一个 struct iocb 结构体。struct iocb(I/O Control Block)描述了一个单独的异步 I/O 请求。
    • 数组的大小至少为 nr

5. struct iocb 结构体

这是描述单个异步 I/O 请求的核心结构体。你需要为每个你想要提交的异步操作(如一次读取或写入)填充一个 iocb 结构体。

// 这是 linux/aio_abi.h 中定义的简化版结构
struct iocb {__u64 aio_data;          // 用户定义的数据,通常用于匹配请求和完成事件__u32 aio_key, aio_reserved1;__u16 aio_lio_opcode;    // 操作类型:IOCB_CMD_PREAD, IOCB_CMD_PWRITE 等__s16 aio_reqprio;       // 请求优先级 (通常保留为 0)__u32 aio_fildes;        // 文件描述符__u64 aio_buf;           // 缓冲区地址 (用户空间指针)__u64 aio_nbytes;        // 传输字节数__s64 aio_offset;        // 文件偏移量// ... 还有其他字段,用于更高级的功能
};

关键字段

  • aio_lio_opcode: 指定操作类型。
    • IOCB_CMD_PREAD: 异步预读(Positioned Read)。
    • IOCB_CMD_PWRITE: 异步预写(Positioned Write)。
    • IOCB_CMD_FSYNC: 异步同步文件数据和元数据。
    • IOCB_CMD_FDSYNC: 异步同步文件数据。
  • aio_fildes: 进行 I/O 操作的文件描述符。
  • aio_buf: 用户空间缓冲区的地址。
  • aio_nbytes: 要读取或写入的字节数。
  • aio_offset: 文件中的偏移量(类似 pread/pwrite)。
  • aio_data: 用户自定义数据。当这个请求完成后,对应的完成事件 (io_event) 会包含这个值,方便你识别是哪个请求完成了。

6. 返回值

  • io_setup:
    • 成功: 返回 0。
    • 失败: 返回 -1,并设置 errno
  • io_submit:
    • 成功: 返回实际提交成功的请求数(一个非负整数,可能小于或等于 nr)。
    • 失败: 返回 -1,并设置 errno。如果返回值是 0 到 nr 之间的正数 m,则表示只有前 m 个请求被成功提交,后面的请求提交失败。

7. 错误码 (errno)

io_setup
  • EAGAIN: 内核资源不足,无法创建新的上下文,或者达到用户/系统范围内的 AIO 上下文数量限制。
  • EINVAL: nr_events 为 0。
  • ENOMEM: 内存不足。
io_submit
  • EAGAIN: 资源暂时不可用,例如提交队列已满。
  • EBADF: ctx_id 无效,或者 iocbpp 中某个 iocbaio_fildes 是无效的文件描述符。
  • EINVAL: ctx_id 无效,或者 iocbpp 中某个 iocb 的参数无效(例如 aio_lio_opcode 未知)。
  • ENOMEM: 内存不足。

8. 相似函数或关联函数

  • io_getevents: 用于从异步 I/O 上下文中获取已完成的 I/O 事件。
  • io_destroy: 用于销毁一个异步 I/O 上下文。
  • io_cancel: 用于尝试取消一个已提交但尚未完成的 I/O 请求。
  • struct io_context_t: 异步 I/O 上下文的类型。
  • struct iocb: 描述单个异步 I/O 请求的结构体。
  • struct io_event: 描述单个已完成 I/O 事件的结构体。
  • io_uring: Linux 5.1+ 引入的更现代、更高效的异步 I/O 接口。

9. 示例代码

下面的示例演示了如何使用 io_setupio_submit 来执行基本的异步 I/O 操作,并使用 io_getevents 来获取结果。

警告:Linux 原生 AIO (io_uring 之前的 AIO) 对于文件 I/O 的支持在某些场景下(如 buffered I/O)可能退化为同步操作。对于高性能异步 I/O,现代推荐使用 io_uring。此处仅为演示 io_setupio_submit 的用法。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <linux/aio_abi.h>
#include <sys/syscall.h>// 辅助函数:调用 io_setup 系统调用
static inline int io_setup(unsigned nr_events, io_context_t *ctxp) {return syscall(__NR_io_setup, nr_events, ctxp);
}// 辅助函数:调用 io_destroy 系统调用
static inline int io_destroy(io_context_t ctx) {return syscall(__NR_io_destroy, ctx);
}// 辅助函数:调用 io_submit 系统调用
static inline int io_submit(io_context_t ctx, long nr, struct iocb **iocbpp) {return syscall(__NR_io_submit, ctx, nr, iocbpp);
}// 辅助函数:调用 io_getevents 系统调用
static inline int io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout) {return syscall(__NR_io_getevents, ctx, min_nr, nr, events, timeout);
}// 辅助函数:初始化一个异步读取的 iocb 结构
void prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, __u64 offset) {// 清零结构体memset(iocb, 0, sizeof(*iocb));// 设置操作类型为pread (异步pread)iocb->aio_lio_opcode = IOCB_CMD_PREAD;// 设置文件描述符iocb->aio_fildes = fd;// 设置缓冲区iocb->aio_buf = (__u64)(unsigned long)buf;// 设置读取字节数iocb->aio_nbytes = count;// 设置文件偏移量iocb->aio_offset = offset;// 设置用户数据 (可选,用于匹配事件)iocb->aio_data = (__u64)(unsigned long)buf; // 这里简单地用 buf 地址作为标识
}// 辅助函数:初始化一个异步写入的 iocb 结构
void prep_pwrite(struct iocb *iocb, int fd, const void *buf, size_t count, __u64 offset) {memset(iocb, 0, sizeof(*iocb));iocb->aio_lio_opcode = IOCB_CMD_PWRITE;iocb->aio_fildes = fd;iocb->aio_buf = (__u64)(unsigned long)buf;iocb->aio_nbytes = count;iocb->aio_offset = offset;iocb->aio_data = (__u64)(unsigned long)buf; // 用 buf 地址作为标识
}int main() {const char *filename = "aio_test_file.txt";const int num_ops = 4; // 2次写入 + 2次读取const size_t chunk_size = 1024;int fd;io_context_t ctx = 0; // 必须初始化为 0struct iocb iocbs[num_ops];struct iocb *iocb_ptrs[num_ops];char write_buffers[2][chunk_size];char read_buffers[2][chunk_size];struct io_event events[num_ops];int ret, i;printf("--- Demonstrating io_setup and io_submit (Linux AIO) ---\n");// 1. 初始化要写入的数据memset(write_buffers[0], 'A', chunk_size);memset(write_buffers[1], 'B', chunk_size);// 2. 创建并打开文件 (O_DIRECT 对 AIO 更友好,但为简单起见使用普通模式)fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}printf("Opened/created file '%s'\n", filename);// 3. 初始化异步 I/O 上下文// 我们需要能处理至少 num_ops 个并发请求ret = io_setup(num_ops, &ctx);if (ret < 0) {perror("io_setup");close(fd);exit(EXIT_FAILURE);}printf("Initialized AIO context (ctx_id=%llu).\n", (unsigned long long)ctx);// 4. 准备写入请求prep_pwrite(&iocbs[0], fd, write_buffers[0], chunk_size, 0);prep_pwrite(&iocbs[1], fd, write_buffers[1], chunk_size, chunk_size);iocb_ptrs[0] = &iocbs[0];iocb_ptrs[1] = &iocbs[1];printf("Prepared 2 write requests.\n");// 5. 提交写入请求printf("Submitting write requests...\n");ret = io_submit(ctx, 2, iocb_ptrs);if (ret != 2) {fprintf(stderr, "io_submit (writes) failed: submitted %d, expected %d\n", ret, 2);if (ret < 0) perror("io_submit");io_destroy(ctx);close(fd);exit(EXIT_FAILURE);}printf("Submitted 2 write requests successfully.\n");// 6. 等待写入完成 (简单起见,等待所有已提交的)printf("Waiting for write completions...\n");struct timespec timeout = {5, 0}; // 5秒超时ret = io_getevents(ctx, 2, 2, events, &timeout);if (ret < 0) {perror("io_getevents (writes)");io_destroy(ctx);close(fd);exit(EXIT_FAILURE);}if (ret < 2) {printf("Warning: Only got %d write events, expected 2.\n", ret);} else {printf("Received %d write completion events.\n", ret);for (i = 0; i < ret; ++i) {if (events[i].res < 0) {fprintf(stderr, "Write error: %s\n", strerror(-events[i].res));} else {printf("Write %d completed: %lld bytes written.\n", i+1, (long long)events[i].res);}}}// 7. 准备读取请求prep_pread(&iocbs[2], fd, read_buffers[0], chunk_size, 0);prep_pread(&iocbs[3], fd, read_buffers[1], chunk_size, chunk_size);iocb_ptrs[0] = &iocbs[2]; // 重用指针数组iocb_ptrs[1] = &iocbs[3];printf("\nPrepared 2 read requests.\n");// 8. 提交读取请求printf("Submitting read requests...\n");ret = io_submit(ctx, 2, iocb_ptrs);if (ret != 2) {fprintf(stderr, "io_submit (reads) failed: submitted %d, expected %d\n", ret, 2);if (ret < 0) perror("io_submit");io_destroy(ctx);close(fd);exit(EXIT_FAILURE);}printf("Submitted 2 read requests successfully.\n");// 9. 等待读取完成printf("Waiting for read completions...\n");ret = io_getevents(ctx, 2, 2, events, &timeout);if (ret < 0) {perror("io_getevents (reads)");io_destroy(ctx);close(fd);exit(EXIT_FAILURE);}if (ret < 2) {printf("Warning: Only got %d read events, expected 2.\n", ret);} else {printf("Received %d read completion events.\n", ret);for (i = 0; i < ret; ++i) {if (events[i].res < 0) {fprintf(stderr, "Read error: %s\n", strerror(-events[i].res));} else {printf("Read %d completed: %lld bytes read.\n", i+1, (long long)events[i].res);// 简单验证读取的数据char expected_char = (i == 0) ? 'A' : 'B';if (((char*)(events[i].data))[0] == expected_char) {printf("  Data verification OK for buffer %d.\n", i+1);} else {printf("  Data verification FAILED for buffer %d.\n", i+1);}}}}// 10. 清理资源printf("\n--- Cleaning up ---\n");ret = io_destroy(ctx);if (ret < 0) {perror("io_destroy");} else {printf("Destroyed AIO context.\n");}close(fd);printf("Closed file descriptor.\n");unlink(filename); // 删除测试文件printf("Deleted test file '%s'.\n", filename);printf("\n--- Summary ---\n");printf("1. io_setup(nr_events, &ctx): Creates an AIO context that can handle 'nr_events' concurrent operations.\n");printf("2. io_submit(ctx_id, nr, iocb_ptrs): Submits 'nr' AIO requests (described by iocb structs) to the context.\n");printf("3. struct iocb: Describes a single AIO operation (read/write/fsync etc.) with all necessary parameters.\n");printf("4. These are the first steps in the Linux AIO workflow. Results are fetched with io_getevents().\n");printf("5. Note: Traditional Linux AIO has limitations. io_uring is the modern, preferred approach.\n");return 0;
}

10. 编译和运行

# 假设代码保存在 aio_setup_submit_example.c 中
gcc -o aio_setup_submit_example aio_setup_submit_example.c# 运行程序
./aio_setup_submit_example

11. 预期输出

--- Demonstrating io_setup and io_submit (Linux AIO) ---
Opened/created file 'aio_test_file.txt'
Initialized AIO context (ctx_id=123456789).
Prepared 2 write requests.
Submitting write requests...
Submitted 2 write requests successfully.
Waiting for write completions...
Received 2 write completion events.
Write 1 completed: 1024 bytes written.
Write 2 completed: 1024 bytes written.Prepared 2 read requests.
Submitting read requests...
Submitted 2 read requests successfully.
Waiting for read completions...
Received 2 read completion events.
Read 1 completed: 1024 bytes read.Data verification OK for buffer 1.
Read 2 completed: 1024 bytes read.Data verification OK for buffer 2.--- Cleaning up ---
Destroyed AIO context.
Closed file descriptor.
Deleted test file 'aio_test_file.txt'.--- Summary ---
1. io_setup(nr_events, &ctx): Creates an AIO context that can handle 'nr_events' concurrent operations.
2. io_submit(ctx_id, nr, iocb_ptrs): Submits 'nr' AIO requests (described by iocb structs) to the context.
3. struct iocb: Describes a single AIO operation (read/write/fsync etc.) with all necessary parameters.
4. These are the first steps in the Linux AIO workflow. Results are fetched with io_getevents().
5. Note: Traditional Linux AIO has limitations. io_uring is the modern, preferred approach.

12. 总结

io_setupio_submit 是 Linux 异步 I/O (AIO) 机制的入口点。

  • io_setup
    • 作用:创建一个管理和跟踪异步 I/O 操作的上下文。
    • 参数:指定上下文能处理的最大并发请求数 (nr_events) 和用于返回上下文 ID 的指针 (ctxp)。
  • io_submit
    • 作用:将描述异步操作的 iocb 结构体提交到指定的上下文中,让内核开始执行。
    • 参数:上下文 ID (ctx_id)、请求数量 (nr) 和指向 iocb 指针数组的指针 (iocbpp)。
  • 核心概念
    • io_context_t:异步 I/O 上下文的句柄。
    • struct iocb:描述单个异步请求(操作类型、文件、缓冲区、偏移等)。
  • 工作流程
    1. 调用 io_setup 创建上下文。
    2. 为每个 I/O 操作填充一个 iocb 结构体。
    3. 调用 io_submit 提交这些 iocb
    4. (稍后)调用 io_getevents 获取完成状态。
    5. (最后)调用 io_destroy 销毁上下文。
  • 局限性
    • 传统 Linux AIO 对 buffered 文件 I/O 支持不佳,性能可能不理想。
    • API 相对底层和复杂。
  • 现代替代:对于新的高性能异步 I/O 应用,强烈推荐使用 io_uring,它提供了更强大、更易用、性能更好的异步 I/O 接口。

对于 Linux 编程新手,理解 io_setupio_submit 是学习异步 I/O 的重要一步,尽管在实践中可能更倾向于使用更高级的封装或 io_uring

http://www.lryc.cn/news/608735.html

相关文章:

  • [AI8051U入门第十五步]W5500实现DHCP自动获取IP
  • UE5的渲染Debug技巧
  • [每周一更]-(第154期):Docker 底层深度剖析:掌控 CPU 与内存资源的艺术
  • Leetcode 12 java
  • GitHub 趋势日报 (2025年08月02日)
  • ThinkPad P16 Gen2,P16 Gen2 LTE(21FA,21FB)原装Win10Pro,Win11专业版系统镜像,恢复出厂开箱状态
  • All the Mods 9 - To the Sky - atm9sky 局域网联机报错可能解决方法
  • Timer串口常用库函数(STC8系列)
  • 代码随想录算法训练营第三十九天
  • 【内容规范】关于标题中【】标记的使用说明
  • 【机器学习③】 | CNN篇
  • k8s日志收集
  • Node.js 操作 MySQL
  • [硬件电路-129]:模拟电路 - 继电器的工作原理、关键指标、常用芯片与管脚定义
  • OSPF知识点整理
  • Flutter 函数的基本使用
  • OpenCV轻松入门_面向python(第一章OpenCV入门)
  • 企业IT管理——集团IT项目实施管理办法模板
  • Linux Deepin深度操作系统应用商店加载失败,安装星火应用商店
  • 学习笔记《区块链技术与应用》第六天 问答 匿名技术 零知识证明
  • 机器翻译的分类:规则式、统计式、神经式MT的核心区别
  • 基于单片机火灾报警系统/防火防盗系统设计
  • 块三角掩码(Block-Triangular Masking)
  • MySQL的创建管理表:
  • Memcached Slab分配器:零碎片的极速内存管理
  • [spring-cloud: 服务发现]-源码解析
  • Day 30:模块和库的导入
  • 风光储综合能源系统双层优化规划设计【MATLAB模型实现】
  • 第九章:了解特殊场景下的redis
  • 控制建模matlab练习07:比例积分控制-③PI控制器的应用