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

io_getevents 和 io_pgetevents 系统调用及示例

我们来深入学习 io_geteventsio_pgetevents 系统调用,从 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_submit 系列函数用于提交异步 I/O 请求,而 io_geteventsio_pgetevents 则用于获取这些已提交请求的完成状态(事件)。

  • io_getevents: 从指定的异步 I/O 上下文(context)中获取已完成的 I/O 事件。
  • io_pgetevents: 是 io_getevents 的扩展版本,它在获取事件的同时,可以设置一个信号掩码(就像 pselectppoll 一样),在等待事件期间临时改变进程的信号屏蔽字。

简单来说

  • io_getevents:问内核:“有哪些我之前提交的异步读写操作已经完成了?”
  • io_pgetevents:和 io_getevents 功能一样,但可以在等待时临时调整对信号的响应。

2. 函数原型

// 需要定义宏来启用 AIO 和 io_pgetevents
#define _GNU_SOURCE
#include <linux/aio_abi.h> // 包含 AIO 相关结构体和常量 (io_context_t, io_event, iocb)
#include <sys/syscall.h>   // 包含 syscall 函数和系统调用号
#include <unistd.h>        // 包含 syscall 函数
#include <signal.h>        // 包含 sigset_t 等 (io_pgetevents)// io_getevents 系统调用
long syscall(SYS_io_getevents, io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);// io_pgetevents 系统调用
long syscall(SYS_io_pgetevents, io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout, const sigset_t *sigmask);

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

3. 功能

  • io_getevents: 尝试从异步 I/O 上下文 ctx_id 中获取至少 min_nr 个、最多 nr 个已完成的 I/O 事件,并将它们存储在 events 指向的数组中。如果没有任何事件完成,它会根据 timeout 参数决定是阻塞等待还是立即返回。
  • io_pgetevents: 功能与 io_getevents 相同,但在等待事件期间,会将调用进程的信号屏蔽字临时设置为 sigmask 指向的掩码。这可以防止在等待过程中被不希望的信号中断。

4. 参数详解

io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout)
  • ctx_id:
    • io_context_t 类型。
    • 一个异步 I/O 上下文的标识符。这个上下文是通过 io_setup 系统调用创建的,用于管理一组异步 I/O 操作。
  • min_nr:
    • long 类型。
    • 指定函数希望返回的最少事件数量。如果已完成的事件少于 min_nr,函数可能会根据 timeout 选择等待。
  • nr:
    • long 类型。
    • 指定 events 数组能容纳的最大事件数量。函数返回的事件数不会超过 nr
  • events:
    • struct io_event * 类型。
    • 一个指向 struct io_event 数组的指针。函数成功返回时,会将获取到的已完成事件信息填充到这个数组中。
    • struct io_event 结构体包含:
      • __u64 data;:与请求关联的用户数据(通常是你在 iocb 中设置的 data 字段)。
      • __u64 obj;:指向完成的 iocb 的指针(内核空间地址)。
      • __s64 res;:操作结果。对于读/写操作,这是传输的字节数;对于失败的操作,这是一个负的错误码(如 -EIO)。
      • __s64 res2;:预留字段。
  • timeout:
    • struct timespec * 类型。
    • 指向一个 timespec 结构体,指定等待事件的超时时间。
    • 如果为 NULL,函数会无限期阻塞,直到至少有 min_nr 个事件完成。
    • 如果 tv_sectv_nsec 都为 0,函数会立即返回,不进行任何等待,只返回当前已有的事件。
    • 否则,函数最多等待指定的时间。
io_pgetevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout, const sigset_t *sigmask)
  • 前五个参数与 io_getevents 完全相同。
  • sigmask:
    • const sigset_t * 类型。
    • 一个指向信号集的指针。在 io_pgetevents 执行等待(如果需要等待)期间,调用进程的信号屏蔽字会被临时替换为 sigmask 指向的信号集。等待结束后,信号屏蔽字会恢复为原始值。
    • 这使得程序可以在等待 I/O 事件时,精确控制哪些信号可以中断等待。

5. 返回值

两者返回值相同:

  • 成功: 返回实际获取到的事件数量(大于等于 0,小于等于 nr)。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

两者共享许多相同的错误码:

  • EFAULT: eventstimeout 指向了无效的内存地址。
  • EINTR: 系统调用被信号中断(对于 io_getevents)。对于 io_pgetevents,如果 sigmaskNULL,也可能发生。
  • EINVAL: min_nr 大于 nr,或者 ctx_id 无效。
  • ENOMEM: 内核内存不足。
  • EBADF: ctx_id 不是一个有效的异步 I/O 上下文。

7. 相似函数或关联函数

  • io_setup: 创建一个异步 I/O 上下文。
  • io_destroy: 销毁一个异步 I/O 上下文。
  • io_submit: 向异步 I/O 上下文提交一个或多个 I/O 请求 (iocb)。
  • io_cancel: 尝试取消一个已提交但尚未完成的 I/O 请求。
  • struct io_context_t: 异步 I/O 上下文的类型。
  • struct iocb: 描述单个异步 I/O 请求的结构体。
  • struct io_event: 描述单个已完成 I/O 事件的结构体。

8. 示例代码

下面的示例演示了如何使用 io_setup, io_submit, io_getevents 来执行基本的异步 I/O 操作。由于 io_pgetevents 的使用方式类似且需要处理信号,此处主要演示 io_getevents

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

#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>
#include <sys/time.h> // 包含 gettimeofday// 辅助函数:调用 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_read(struct iocb *iocb, int fd, void *buf, size_t count, __u64 offset, __u64 data) {// 清零结构体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 = data;
}int main() {const char *filename = "aio_test_file.txt";const int num_reads = 3;const size_t chunk_size = 1024;int fd;io_context_t ctx = 0; // 必须初始化为 0struct iocb iocbs[num_reads];struct iocb *iocb_ptrs[num_reads];char buffers[num_reads][chunk_size];struct io_event events[num_reads];struct timespec timeout;int ret, i;struct timeval start, end;double elapsed_time;printf("--- Demonstrating io_getevents (Linux AIO) ---\n");// 1. 创建一个测试文件fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);if (fd == -1) {perror("open (create)");exit(EXIT_FAILURE);}char test_data[1024];memset(test_data, 'A', sizeof(test_data));for (int j = 0; j < 10; ++j) { // 写入 10KB 数据if (write(fd, test_data, sizeof(test_data)) != sizeof(test_data)) {perror("write");close(fd);exit(EXIT_FAILURE);}}close(fd);printf("Created test file '%s' with 10KB of data.\n", filename);// 2. 以只读方式打开文件fd = open(filename, O_RDONLY);if (fd == -1) {perror("open (read)");exit(EXIT_FAILURE);}// 3. 初始化异步 I/O 上下文// 我们需要能处理至少 num_reads 个并发请求ret = io_setup(num_reads, &ctx);if (ret < 0) {perror("io_setup");close(fd);exit(EXIT_FAILURE);}printf("Initialized AIO context.\n");// 4. 准备 I/O 请求 (iocb)for (i = 0; i < num_reads; ++i) {// 从文件不同偏移读取prep_read(&iocbs[i], fd, buffers[i], chunk_size, i * chunk_size, i + 1);iocb_ptrs[i] = &iocbs[i];printf("Prepared read request %d: offset=%zu, size=%zu\n", i+1, i * chunk_size, chunk_size);}// 5. 提交 I/O 请求gettimeofday(&start, NULL);printf("Submitting %d asynchronous read requests...\n", num_reads);ret = io_submit(ctx, num_reads, iocb_ptrs);if (ret != num_reads) {fprintf(stderr, "io_submit failed: submitted %d, expected %d\n", ret, num_reads);if (ret < 0) perror("io_submit");io_destroy(ctx);close(fd);exit(EXIT_FAILURE);}gettimeofday(&end, NULL);elapsed_time = ((end.tv_sec - start.tv_sec) * 1000.0) + ((end.tv_usec - start.tv_usec) / 1000.0);printf("Submitted all requests in %.2f ms.\n", elapsed_time);// 6. 等待并获取完成的事件 (使用 io_getevents)printf("Waiting for completion events using io_getevents...\n");gettimeofday(&start, NULL);// 设置超时为 5 秒timeout.tv_sec = 5;timeout.tv_nsec = 0;// 等待所有 num_reads 个事件完成ret = io_getevents(ctx, num_reads, num_reads, events, &timeout);gettimeofday(&end, NULL);elapsed_time = ((end.tv_sec - start.tv_sec) * 1000.0) + ((end.tv_usec - start.tv_usec) / 1000.0);if (ret < 0) {perror("io_getevents");io_destroy(ctx);close(fd);exit(EXIT_FAILURE);}if (ret < num_reads) {printf("Warning: Only got %d events out of %d expected within timeout.\n", ret, num_reads);} else {printf("Received all %d completion events in %.2f ms.\n", ret, elapsed_time);}// 7. 处理完成的事件printf("\n--- Processing Completion Events ---\n");for (i = 0; i < ret; ++i) {struct io_event *ev = &events[i];printf("Event %d:\n", i+1);printf("  Request ID (user data): %llu\n", (unsigned long long)ev->data);// printf("  Request pointer: %llu\n", (unsigned long long)ev->obj); // 内核地址,通常不直接使用if (ev->res >= 0) {printf("  Result: Success, %lld bytes read.\n", (long long)ev->res);// 可以检查 buffers[ev->data - 1] 中的数据// printf("  First byte: %c\n", buffers[ev->data - 1][0]);} else {printf("  Result: Error, code %lld (%s)\n", (long long)ev->res, strerror(-ev->res));}printf("\n");}// 8. 清理资源printf("--- Cleaning up ---\n");io_destroy(ctx);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_getevents retrieves completed asynchronous I/O operations.\n");printf("2. It works with an io_context_t created by io_setup.\n");printf("3. It waits for events based on min_nr, nr, and timeout.\n");printf("4. io_pgetevents is similar but allows setting a signal mask during wait.\n");printf("5. Linux AIO has some limitations; io_uring is the modern, preferred approach.\n");return 0;
}

9. 编译和运行

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

10. 预期输出

--- Demonstrating io_getevents (Linux AIO) ---
Created test file 'aio_test_file.txt' with 10KB of data.
Initialized AIO context.
Prepared read request 1: offset=0, size=1024
Prepared read request 2: offset=1024, size=1024
Prepared read request 3: offset=2048, size=1024
Submitting 3 asynchronous read requests...
Submitted all requests in 0.05 ms.
Waiting for completion events using io_getevents...
Received all 3 completion events in 2.15 ms.--- Processing Completion Events ---
Event 1:Request ID (user data): 1Result: Success, 1024 bytes read.Event 2:Request ID (user data): 2Result: Success, 1024 bytes read.Event 3:Request ID (user data): 3Result: Success, 1024 bytes read.--- Cleaning up ---
Destroyed AIO context.
Closed file descriptor.
Deleted test file 'aio_test_file.txt'.--- Summary ---
1. io_getevents retrieves completed asynchronous I/O operations.
2. It works with an io_context_t created by io_setup.
3. It waits for events based on min_nr, nr, and timeout.
4. io_pgetevents is similar but allows setting a signal mask during wait.
5. Linux AIO has some limitations; io_uring is the modern, preferred approach.

11. 关于 io_pgetevents 的补充说明

io_pgetevents 的使用场景相对较少,主要是在需要精确控制信号处理的异步 I/O 程序中。例如,你可能希望在等待 I/O 完成时,只允许 SIGUSR1 信号中断,而屏蔽其他所有信号。这时就可以构建一个只包含 SIGUSR1sigset_t,并传递给 io_pgetevents
其函数原型和调用方式与 io_getevents 类似,只是多了一个 sigmask 参数:

// 假设已定义 syscall 号 __NR_io_pgetevents
long io_pgetevents(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout, const sigset_t *sigmask);// 使用示例 (概念性)
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);struct timespec timeout = {5, 0}; // 5秒超时
int ret = syscall(__NR_io_pgetevents, ctx, 1, 1, events, &timeout, &mask);
// 在等待期间,只有 SIGUSR1 能中断此调用

12. 总结

io_geteventsio_pgetevents 是 Linux 异步 I/O (AIO) 机制的重要组成部分。

  • 核心作用:从 AIO 上下文中获取已完成的 I/O 操作的结果(事件)。
  • io_getevents:基础版本,用于等待和获取事件。
  • io_pgetevents:增强版本,在等待期间可以原子性地设置信号掩码,提供更精细的信号控制。
  • 工作流程
    1. io_setup 创建上下文。
    2. 构造 iocb 请求并用 io_submit 提交。
    3. 使用 io_getevents/io_pgetevents 等待和获取完成事件。
    4. io_destroy 销毁上下文。
  • 优势:允许程序在 I/O 操作进行的同时执行其他任务,提高并发性能。
  • 局限性
    • 传统 Linux AIO 对于 buffered 文件 I/O 支持不佳,可能退化为同步。
    • API 相对复杂,直接使用系统调用较为繁琐。
  • 现代替代:对于新的高性能异步 I/O 应用,强烈推荐使用 io_uring,它提供了更强大、更易用、性能更好的异步 I/O 接口。

对于 Linux 编程新手,理解 io_getevents 的工作原理有助于掌握异步编程的思想,尽管在实践中可能更倾向于使用更高级的封装或 io_uring

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

相关文章:

  • 【Mysql】日志--错误日志、二进制日志、查询日志、慢查询日志
  • Linux进程启动后,监听端口几分钟后消失之问题分析
  • RocksDb 是什么?levelDB、LSM 树、SSTable又分别是什么?区别呢?
  • Java,八股,cv,算法——双非研0四修之路day24
  • 2025年测绘程序设计比赛--基于统计滤波的点云去噪(已获国特)
  • 【AI】文档理解
  • 旧笔记本电脑如何安装飞牛OS
  • 嵌入式学习日志——数据结构(一)
  • 渗透高级-----应急响应
  • LLM调研
  • nestjs @Param 从入门到精通
  • 大模型能力测评(提示词请帮我把这个项目改写成为python项目)
  • 数据结构基础 - 平衡二叉树
  • 关于 xrdp远程桌面报错“Error connecting to sesman on 127.0.0.1:3350“的解决方法
  • lua table常用函数汇总
  • 6. 平台总线
  • 模型学习系列之参数
  • 秋招笔记-8.3
  • 关于记录一下“bug”,在做图片上传的时候出现的小问题
  • 验房收房怎么避免被坑?
  • 我的世界进阶模组开发教程——伤害(2)
  • 自己实现一个freertos(2)任务调度 1——最基本的TCB
  • 深入解析HashMap:原理与性能优化
  • Redis实战(7)-- 高级特性 Redis Stream数据结构与基础命令
  • spring batch处理数据模板(Reader-Processor-Writer模式)
  • Timer实现定时调度的原理是什么?
  • PPT 转高精度 PDF API 接口
  • 使用DrissionPage实现xhs笔记自动翻页并爬取笔记视频、图片
  • Coin Combinations I(Dynamic Programming)
  • Docker环境离线安装指南