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. 函数原型

c 复制代码
// 需要定义宏来启用 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 结构体。

c 复制代码
// 这是 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 的用法。

c 复制代码
#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; // 必须初始化为 0
    struct 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. 编译和运行

bash 复制代码
# 假设代码保存在 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) 机制的入口点。

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

相关推荐
乌萨奇也要立志学C++1 小时前
【Linux】linux基础开发工具(三) 版本控制器Git、调试器 - gdb/cgdb使用、一些实用的调试技巧
linux·git·elasticsearch
Ronin3051 小时前
【Linux系统】进程间通信:匿名管道
linux
Tipriest_1 小时前
ubuntu apt安装与dpkg安装相互之间的关系
linux·运维·ubuntu·apt·flatpak·dpkg·snap
liliangcsdn1 小时前
linux pip/conda 修改默认cache位置
linux·运维·pip
sky_share2 小时前
嵌入式相关书籍
linux
mortimer2 小时前
记一次网站服务器无缝扩容:利用软链接实现零停机数据迁移
linux·运维
John.Lewis3 小时前
C语言数据结构(7)贪吃蛇项目2.贪吃蛇项目实现
c语言·数据结构·算法
Blossom.1183 小时前
基于深度学习的医学图像分析:使用PixelRNN实现医学图像超分辨率
c语言·人工智能·python·深度学习·yolo·目标检测·机器学习
小小洋洋3 小时前
笔记:C语言中指向指针的指针作用
c语言·开发语言·笔记