io_setup/io_submit系统调用及示例

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

1. 函数介绍

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

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

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

io_setup 和 io_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_submitio_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 上下文数量限制。
  • EINVALnr_events 为 0。
  • ENOMEM: 内存不足。

io_submit

  • EAGAIN: 资源暂时不可用,例如提交队列已满。
  • EBADFctx_id 无效,或者 iocbpp 中某个 iocb 的 aio_fildes 是无效的文件描述符。
  • EINVALctx_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_setup 和 io_submit 来执行基本的异步 I/O 操作,并使用 io_getevents 来获取结果。

警告:Linux 原生 AIO (io_uring 之前的 AIO) 对于文件 I/O 的支持在某些场景下(如 buffered I/O)可能退化为同步操作。对于高性能异步 I/O,现代推荐使用 io_uring。此处仅为演示 io_setup 和 io_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; // 必须初始化为 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. 编译和运行

# 假设代码保存在 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_setup 和 io_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_setup 和 io_submit 是学习异步 I/O 的重要一步,尽管在实践中可能更倾向于使用更高级的封装或 io_uring

发表在 linux文章 | 留下评论

 io_getevents 和 io_pgetevents 系统调用及示例

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

1. 函数介绍

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

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

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

io_submit 系列函数用于提交异步 I/O 请求,而 io_getevents 和 io_pgetevents 则用于获取这些已提交请求的完成状态(事件)。

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

简单来说

  • 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_sec 和 tv_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)

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

  • EFAULTevents 或 timeout 指向了无效的内存地址。
  • EINTR: 系统调用被信号中断(对于 io_getevents)。对于 io_pgetevents,如果 sigmask 为 NULL,也可能发生。
  • EINVALmin_nr 大于 nr,或者 ctx_id 无效。
  • ENOMEM: 内核内存不足。
  • EBADFctx_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_setupio_submitio_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; // 必须初始化为 0
    struct 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): 1
  Result: Success, 1024 bytes read.

Event 2:
  Request ID (user data): 2
  Result: Success, 1024 bytes read.

Event 3:
  Request ID (user data): 3
  Result: 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 信号中断,而屏蔽其他所有信号。这时就可以构建一个只包含 SIGUSR1 的 sigset_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_getevents 和 io_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

发表在 linux文章 | 留下评论

io_pgetevents 系统调用及示例

我们继续按照您的要求学习 Linux 系统编程中的重要函数。这次我们介绍 io_pgetevents


1. 函数介绍

io_pgetevents 是一个 Linux 系统调用,它是 Linux AIO (Asynchronous I/O) 子系统的一部分。它是 io_getevents 函数的增强版本,主要增加了对信号屏蔽(signal mask)的支持。

简单来说,io_pgetevents 的作用是:

等待并获取之前提交给 Linux AIO 子系统的异步 I/O 操作的完成状态

想象一下你去邮局寄很多封信(异步 I/O 请求):

  • 你把所有信件交给邮局(调用 io_submit),然后你就可以去做别的事情了,不需要在邮局柜台等着。
  • 过了一段时间,你想知道哪些信已经寄出去了(I/O 操作完成了)。
  • 你就可以使用 io_pgetevents 这个功能去邮局查询(等待)并取回那些已经处理完毕的回执单(I/O 完成事件)。

io_pgetevents 相比 io_getevents 的优势在于,它允许你在等待 I/O 完成的同时,原子性地设置一个临时的信号屏蔽字。这在需要精确控制信号处理的多线程程序中非常有用,可以避免竞态条件。


2. 函数原型

#include <linux/aio_abi.h> // 包含 io_event 结构体等 AIO 相关定义
#include <signal.h>        // sigset_t
#include <sys/syscall.h>   // syscall
#include <unistd.h>

// 注意:glibc 通常不直接包装 io_pgetevents,需要使用 syscall
// 系统调用号在不同架构上不同,例如 x86_64 上是 333 (SYS_io_pgetevents)

// 通过 syscall 调用的原型 (概念上)
long io_pgetevents(aio_context_t ctx_id,
                   long min_nr,
                   long nr,
                   struct io_event *events,
                   struct timespec *timeout,
                   const struct __aio_sigset *usig);

重要: 与 rseq 类似,io_pgetevents 在标准的 C 库 (glibc) 中通常没有直接的包装函数。你需要使用 syscall() 来调用它。


3. 功能

  • 等待 AIO 事件: 阻塞调用线程,直到至少 min_nr 个异步 I/O 事件完成,或者达到 timeout 指定的时间。
  • 获取完成事件: 将已完成的 I/O 事件信息填充到调用者提供的 events 数组中,最多填充 nr 个。
  • 原子性信号控制: 在等待期间,根据 usig 参数临时设置线程的信号屏蔽字。等待结束后,信号屏蔽字会恢复到调用前的状态。这是 io_getevents 所不具备的功能。
  • 超时控制: 可以指定一个等待超时时间,避免无限期阻塞。

4. 参数

  • aio_context_t ctx_id: 这是通过 io_setup 创建的 AIO 上下文(或称为 AIO 完成端口)的 ID。所有相关的异步 I/O 操作都提交到这个上下文中。
  • long min_nr: 调用希望获取的最少事件数量。
    • 如果设置为 1,则函数在至少有一个事件完成时返回。
    • 如果设置为 N(N > 1),则函数会等待,直到至少有 N 个事件完成(或超时)。
  • long nrevents 数组的大小,即调用者希望获取的最大事件数量。
    • 函数返回时,实际返回的事件数会 <= nr
  • struct io_event *events: 指向一个 struct io_event 类型数组的指针。这个数组用于接收完成的 I/O 事件信息。
    struct io_event (定义在 <linux/aio_abi.h>) 通常包含:struct io_event { __u64 data; // 用户在 iocb 中指定的数据 (与请求关联) __u64 obj; // 指向完成的 iocb 的指针 __s64 res; // 操作结果 (例如 read/write 返回的字节数,或负的 errno) __s64 res2; // 额外的结果信息 (通常为 0) };
  • struct timespec *timeout: 指向一个 struct timespec 结构的指针,用于指定超时时间
    • 如果为 NULL,则调用会无限期阻塞,直到至少 min_nr 个事件完成。
    • 如果 timeout->tv_sec == 0 && timeout->tv_nsec == 0,则函数变为非阻塞检查,立即返回已有的完成事件。
    • 否则,函数最多阻塞 timeout 指定的时间。
  • const struct __aio_sigset *usig: 这是 io_pgetevents 相比 io_getevents 新增的关键参数。
    • 它指向一个 struct __aio_sigset 结构,用于指定在等待期间要使用的临时信号屏蔽字
    struct __aio_sigset { const sigset_t *sigmask; // 指向新的信号屏蔽字 size_t sigsetsize; // sigmask 指向的内存大小 (通常用 sizeof(sigset_t)) };
    • 如果 usig 为 NULL,则不修改信号屏蔽字,行为类似于 io_getevents
    • 如果 usig 非 NULL,则在进入内核等待状态之前,线程的信号屏蔽字会被原子性地替换为 *usig->sigmask。在等待结束(无论是因事件到达还是超时)后,信号屏蔽字会恢复。

5. 返回值

  • 成功时: 返回实际获取到的事件数量(一个非负整数,且 >= min_nr 除非超时或被信号中断)。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EFAULT events 或 timeout 指针无效,EINVAL ctx_id 无效或 min_nr/nr 无效,EINTR 调用被信号中断等)。

6. 相似函数,或关联函数

  • io_getevents: 功能与 io_pgetevents 相同,但不支持 usig 参数,无法原子性地控制信号屏蔽字。
  • io_setup: 创建 AIO 上下文。
  • io_destroy: 销毁 AIO 上下文。
  • io_submit: 向 AIO 上下文提交异步 I/O 请求。
  • io_cancel: 尝试取消一个已提交但尚未完成的异步 I/O 请求。
  • io_uring: Linux 5.1+ 引入的更新、更高效的异步 I/O 接口,通常比传统的 aio 性能更好且功能更强大。

7. 示例代码

重要提示: AIO 编程本身就比较复杂,涉及多个系统调用。下面的示例将展示 io_pgetevents 的使用,但会简化一些错误处理和资源清理,以突出重点。

示例 1:使用 io_pgetevents 读取文件并原子性地屏蔽信号

这个例子演示了如何设置 AIO 上下文,提交异步读取请求,然后使用 io_pgetevents 等待完成,并在等待期间原子性地屏蔽 SIGUSR1 信号。

// aio_pgetevents_example.c
// 编译: gcc -o aio_pgetevents_example aio_pgetevents_example.c
#define _GNU_SOURCE // For syscall, SIGUSR1, etc.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/syscall.h>
#include <linux/aio_abi.h>
#include <signal.h>
#include <sys/stat.h>
#include <assert.h>
#include <pthread.h> // For pthread_kill in signal sender

// 定义系统调用号 (x86_64)
#ifndef SYS_io_pgetevents
#define SYS_io_pgetevents 333
#endif
#ifndef SYS_io_setup
#define SYS_io_setup 206
#endif
#ifndef SYS_io_destroy
#define SYS_io_destroy 207
#endif
#ifndef SYS_io_submit
#define SYS_io_submit 209
#endif
#ifndef SYS_io_getevents
#define SYS_io_getevents 208
#endif

// 包装 io_pgetevents 系统调用
static inline int io_pgetevents(aio_context_t ctx, long min_nr, long nr,
                                struct io_event *events,
                                struct timespec *timeout,
                                struct __aio_sigset *usig) {
    return syscall(SYS_io_pgetevents, ctx, min_nr, nr, events, timeout, usig);
}

// 包装 io_setup
static inline int io_setup(unsigned nr_events, aio_context_t *ctx_idp) {
    return syscall(SYS_io_setup, nr_events, ctx_idp);
}

// 包装 io_destroy
static inline int io_destroy(aio_context_t ctx) {
    return syscall(SYS_io_destroy, ctx);
}

// 包装 io_submit
static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(SYS_io_submit, ctx, nr, iocbpp);
}

#define NUM_REQUESTS 2
#define BUFFER_SIZE 1024

// 信号处理函数
void signal_handler(int sig) {
    printf("Signal %d received in main thread!\n", sig);
}

// 发送信号的线程函数
void* signal_sender_thread(void *arg) {
    pid_t main_tid = *(pid_t*)arg;
    sleep(2); // 等待 main 线程进入 io_pgetevents
    printf("Signal sender: Sending SIGUSR1 to main thread (TID %d)...\n", main_tid);
    // 注意:pthread_kill 发送给线程,kill 发送给进程
    // 这里假设 main_tid 是线程 ID (实际获取线程 ID 需要 gettid() 或其他方法)
    // 为简化,我们用 kill 发送给整个进程
    // pthread_kill 需要更复杂的设置,这里用 kill 演示
    if (kill(getpid(), SIGUSR1) != 0) {
        perror("kill SIGUSR1");
    }
    return NULL;
}

int main() {
    const char *filename = "test_aio_file.txt";
    int fd;
    aio_context_t ctx = 0;
    struct iocb iocbs[NUM_REQUESTS];
    struct iocb *iocb_ptrs[NUM_REQUESTS];
    struct io_event events[NUM_REQUESTS];
    char buffers[NUM_REQUESTS][BUFFER_SIZE];
    struct sigaction sa;
    sigset_t block_sigusr1, oldset;
    struct __aio_sigset aio_sigset;
    pthread_t sig_thread;
    pid_t main_tid = getpid(); // Simplification for example

    // 1. 创建测试文件
    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) {
        perror("open test file for writing");
        exit(EXIT_FAILURE);
    }
    const char *test_data = "This is test data for asynchronous I/O operation number one.\n"
                            "This is test data for asynchronous I/O operation number two.\n";
    if (write(fd, test_data, strlen(test_data)) != (ssize_t)strlen(test_data)) {
        perror("write test data");
        close(fd);
        exit(EXIT_FAILURE);
    }
    close(fd);
    printf("Created test file '%s'.\n", filename);

    // 2. 设置信号处理
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0; // No SA_RESTART for demonstration
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction SIGUSR1");
        exit(EXIT_FAILURE);
    }
    printf("SIGUSR1 handler installed.\n");

    // 3. 打开文件进行异步读取
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("open test file for reading");
        exit(EXIT_FAILURE);
    }

    // 4. 初始化 AIO 上下文
    if (io_setup(NUM_REQUESTS, &ctx) < 0) {
        perror("io_setup");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("AIO context created.\n");

    // 5. 准备 AIO 读取请求
    for (int i = 0; i < NUM_REQUESTS; i++) {
        // 初始化 iocb 结构
        memset(&iocbs[i], 0, sizeof(struct iocb));
        iocbs[i].aio_fildes = fd;
        iocbs[i].aio_lio_opcode = IOCB_CMD_PREAD; // 异步预读
        iocbs[i].aio_reqprio = 0;
        iocbs[i].aio_buf = (uint64_t)(buffers[i]); // 读入缓冲区
        iocbs[i].aio_nbytes = BUFFER_SIZE / 2; // 读取一半缓冲区大小
        iocbs[i].aio_offset = i * (BUFFER_SIZE / 2); // 从不同偏移量开始读
        iocbs[i].aio_data = i + 1; // 用户数据,用于标识请求
        iocb_ptrs[i] = &iocbs[i];
    }

    // 6. 提交 AIO 请求
    printf("Submitting %d AIO read requests...\n", NUM_REQUESTS);
    int ret = io_submit(ctx, NUM_REQUESTS, iocb_ptrs);
    if (ret < 0) {
        perror("io_submit");
        io_destroy(ctx);
        close(fd);
        exit(EXIT_FAILURE);
    } else if (ret != NUM_REQUESTS) {
        fprintf(stderr, "Submitted %d requests, expected %d\n", ret, NUM_REQUESTS);
    } else {
        printf("Successfully submitted %d AIO requests.\n", ret);
    }

    // 7. 设置信号屏蔽 (用于 io_pgetevents)
    sigemptyset(&block_sigusr1);
    sigaddset(&block_sigusr1, SIGUSR1);
    aio_sigset.sigmask = &block_sigusr1;
    aio_sigset.sigsetsize = sizeof(block_sigusr1);

    // 8. 启动信号发送线程
    if (pthread_create(&sig_thread, NULL, signal_sender_thread, &main_tid) != 0) {
        perror("pthread_create signal sender");
        io_destroy(ctx);
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Main thread: Waiting for AIO events with SIGUSR1 blocked atomically...\n");

    // 9. 关键:使用 io_pgetevents 等待,原子性地屏蔽 SIGUSR1
    // 这意味着在内核等待期间,SIGUSR1 会被阻塞。
    // 如果在此期间有 SIGUSR1 到达,它会被挂起,直到 io_pgetevents 返回。
    struct timespec timeout;
    timeout.tv_sec = 10; // 10 秒超时
    timeout.tv_nsec = 0;

    ret = io_pgetevents(ctx, 1, NUM_REQUESTS, events, &timeout, &aio_sigset);

    if (ret < 0) {
        if (errno == EINTR) {
            printf("io_pgetevents was interrupted by a signal (EINTR).\n");
        } else {
            perror("io_pgetevents");
        }
    } else {
        printf("io_pgetevents returned %d events:\n", ret);
        for (int i = 0; i < ret; i++) {
            printf("  Event %d: data=%llu, res=%lld\n",
                   i, (unsigned long long)events[i].data, (long long)events[i].res);
            if (events[i].res > 0) {
                buffers[events[i].data - 1][events[i].res] = '\0'; // Null-terminate
                printf("    Data: %s", buffers[events[i].data - 1]);
            }
        }
    }

    printf("Main thread: io_pgetevents finished.\n");

    // 10. 等待信号发送线程结束
    pthread_join(sig_thread, NULL);

    // 11. 清理资源
    io_destroy(ctx);
    close(fd);
    unlink(filename); // 删除测试文件

    printf("Example finished.\n");
    return 0;
}

代码解释:

  1. 定义系统调用: 由于 glibc 可能没有包装,我们手动定义了 io_pgetevents 及相关 AIO 系统调用的包装函数。
  2. 创建测试文件: 程序首先创建一个包含测试数据的文件。
  3. 设置信号处理: 为 SIGUSR1 安装一个处理函数,用于演示信号处理。
  4. 打开文件: 以只读方式打开测试文件。
  5. 初始化 AIO 上下文: 调用 io_setup 创建一个可以处理 NUM_REQUESTS 个并发请求的上下文。
  6. 准备 AIO 请求: 初始化两个 struct iocb 结构,设置为从文件不同偏移量异步预读取数据。
  7. 提交请求: 调用 io_submit 将这两个读取请求提交给 AIO 引擎。
  8. 设置信号屏蔽: 创建一个包含 SIGUSR1 的信号集 block_sigusr1,并填充 struct __aio_sigset 结构 aio_sigset
  9. 启动信号发送线程: 创建一个线程,它会在 2 秒后向主进程发送 SIGUSR1 信号。这用来测试信号屏蔽效果。
  10. 关键步骤 – io_pgetevents:
    • 设置 10 秒超时。
    • 调用 io_pgetevents(ctx, 1, NUM_REQUESTS, events, &timeout, &aio_sigset)
    • min_nr=1: 至少等待 1 个事件完成。
    • &aio_sigset: 传递信号集,告诉内核在等待期间原子性地屏蔽 SIGUSR1
  11. 等待和处理: 主线程在 io_pgetevents 中等待。在此期间,SIGUSR1 被屏蔽。信号发送线程发出的 SIGUSR1 会被挂起。当 io_pgetevents 返回时(因为 I/O 完成或超时),信号屏蔽恢复,被挂起的 SIGUSR1 随即被递达,信号处理函数得以执行。
  12. 输出结果: 打印获取到的事件信息和读取到的数据。
  13. 清理: 等待信号发送线程结束,销毁 AIO 上下文,关闭文件,删除测试文件。

核心概念:

  • io_pgetevents 的 usig 参数使得信号屏蔽等待 I/O 成为一个原子操作。这避免了在设置信号屏蔽和调用 io_getevents 之间收到信号的竞态条件。
  • 如果使用 io_getevents,你需要先调用 pthread_sigmask(SIG_SETMASK, ...) 设置屏蔽,然后调用 io_getevents,最后再调用 pthread_sigmask(SIG_SETMASK, ...) 恢复。在这三步之间,信号可能会到达,导致竞态。

重要提示与注意事项:

  1. 内核版本io_pgetevents 需要 Linux 内核 4.18 或更高版本。
  2. glibc 支持: 标准 C 库可能不提供直接包装,需要使用 syscall
  3. 复杂性: AIO 本身就是一个复杂的子系统,涉及上下文管理、请求提交、事件获取等多个步骤。
  4. 性能: 传统的 aio 性能可能不如现代的 io_uring。对于新项目,考虑使用 io_uring
  5. 信号安全io_pgetevents 本身不是异步信号安全的,不应在信号处理函数中直接调用。
  6. usig 参数: 这是 io_pgetevents 的核心优势。正确使用它可以编写出在信号处理方面更健壮的代码。
  7. 错误处理: 始终检查返回值和 errno,尤其是在处理 EINTR(被信号中断)时。

总结:

io_pgetevents 是 Linux AIO 系统调用 io_getevents 的增强版,关键改进是增加了对原子性信号屏蔽的支持。这使得在等待异步 I/O 完成时能够更安全、更精确地控制信号处理,避免了传统方法中的竞态条件。虽然使用起来比较底层和复杂,但对于需要高性能异步 I/O 并且对信号处理有严格要求的系统级编程来说,它是一个非常有价值的工具。

发表在 linux文章 | 留下评论

io_destroy 函数详解

io_destroy 函数详解

1. 函数介绍

io_destroy 是Linux传统异步I/O (AIO) 系统调用,用于销毁和清理之前通过 io_setup 创建的异步I/O上下文。它是AIO框架的重要组成部分,负责释放与AIO上下文相关的所有内核资源,包括环形缓冲区、等待队列和其他内部数据结构。

2. 函数原型

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>

int io_destroy(aio_context_t ctx);

3. 功能

io_destroy 彻底销毁指定的AIO上下文,释放所有相关的内核资源。在销毁过程中,所有未完成的异步操作都会被取消,相关的事件会被清除,确保系统资源得到正确回收。

4. 参数

  • aio_context_t ctx: 要销毁的AIO上下文ID(由io_setup返回)

5. 返回值

  • 成功: 返回0
  • 失败: 返回负的错误码

6. 相似函数,或关联函数

  • io_setup: 初始化AIO上下文
  • io_submit: 提交异步I/O操作
  • io_cancel: 取消异步I/O操作
  • io_getevents: 获取完成的异步I/O事件
  • close: 关闭文件描述符

7. 示例代码

示例1:基础io_destroy使用

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 演示基础io_destroy使用方法
 */
int demo_io_destroy_basic() {
    aio_context_t ctx;
    int ret;
    
    printf("=== 基础io_destroy使用示例 ===\n");
    
    // 初始化AIO上下文
    printf("1. 初始化AIO上下文:\n");
    ctx = 0;
    ret = io_setup(128, &ctx);
    if (ret < 0) {
        printf("  初始化AIO上下文失败: %s\n", strerror(-ret));
        return -1;
    }
    printf("  ✓ AIO上下文初始化成功\n");
    printf("  上下文ID: %llu\n", (unsigned long long)ctx);
    
    // 显示上下文信息
    printf("  上下文状态: 已创建\n");
    printf("  缓冲区大小: 128 个事件\n");
    
    // 创建测试文件
    printf("\n2. 创建测试文件:\n");
    const char *filename = "io_destroy_test.txt";
    int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("  创建测试文件失败");
        io_destroy(ctx);
        return -1;
    }
    printf("  ✓ 测试文件创建成功: %s\n", filename);
    
    // 准备异步写入操作
    printf("\n3. 准备异步写入操作:\n");
    char test_data[] = "Test data for io_destroy demonstration.\n";
    struct iocb iocb;
    
    memset(&iocb, 0, sizeof(iocb));
    iocb.aio_data = 12345;
    iocb.aio_lio_opcode = IOCB_CMD_PWRITE;
    iocb.aio_fildes = fd;
    iocb.aio_buf = (uint64_t)(uintptr_t)test_data;
    iocb.aio_nbytes = strlen(test_data);
    iocb.aio_offset = 0;
    
    printf("  准备异步写入操作:\n");
    printf("    文件描述符: %d\n", fd);
    printf("    数据大小: %zu 字节\n", strlen(test_data));
    printf("    用户数据: %llu\n", (unsigned long long)iocb.aio_data);
    
    // 提交异步操作
    printf("\n4. 提交异步操作:\n");
    struct iocb *iocbs[1] = {&iocb};
    ret = io_submit(ctx, 1, iocbs);
    if (ret != 1) {
        printf("  提交异步操作失败: %s\n", strerror(-ret));
        close(fd);
        unlink(filename);
        io_destroy(ctx);
        return -1;
    }
    printf("  ✓ 异步操作提交成功\n");
    
    // 等待操作完成
    printf("\n5. 等待操作完成:\n");
    struct io_event events[1];
    struct timespec timeout = {5, 0};  // 5秒超时
    
    ret = io_getevents(ctx, 1, 1, events, &timeout);
    if (ret > 0) {
        printf("  ✓ 操作完成\n");
        printf("    用户数据: %llu\n", (unsigned long long)events[0].data);
        printf("    结果: %ld 字节\n", events[0].res);
        printf("    错误: %ld\n", events[0].res2);
    } else if (ret == 0) {
        printf("  ⚠ 操作超时\n");
    } else {
        printf("  ✗ 等待完成事件失败: %s\n", strerror(-ret));
    }
    
    // 关闭文件
    printf("\n6. 关闭文件:\n");
    close(fd);
    printf("  ✓ 文件关闭成功\n");
    
    // 销毁AIO上下文
    printf("\n7. 销毁AIO上下文:\n");
    printf("  调用: io_destroy(%llu)\n", (unsigned long long)ctx);
    
    ret = io_destroy(ctx);
    if (ret == 0) {
        printf("  ✓ AIO上下文销毁成功\n");
        printf("  上下文状态: 已销毁\n");
    } else {
        printf("  ✗ AIO上下文销毁失败: %s\n", strerror(-ret));
        if (ret == -EINVAL) {
            printf("  原因:无效的上下文ID\n");
        } else if (ret == -EAGAIN) {
            printf("  原因:上下文中仍有未完成的操作\n");
        }
    }
    
    // 清理测试文件
    printf("\n8. 清理测试文件:\n");
    if (unlink(filename) == 0) {
        printf("  ✓ 测试文件清理成功\n");
    } else {
        printf("  ✗ 测试文件清理失败: %s\n", strerror(errno));
    }
    
    return 0;
}

int main() {
    return demo_io_destroy_basic();
}

示例2:资源泄漏检测

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/resource.h>

/**
 * 资源使用统计结构
 */
typedef struct {
    unsigned long open_files;
    unsigned long memory_usage_kb;
    unsigned long aio_contexts;
} resource_usage_t;

/**
 * 获取资源使用情况
 */
int get_resource_usage(resource_usage_t *usage) {
    // 获取打开文件数
    struct rlimit rl;
    if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
        usage->open_files = rl.rlim_cur;
    } else {
        usage->open_files = 0;
    }
    
    // 获取内存使用情况
    FILE *fp = fopen("/proc/self/status", "r");
    if (fp) {
        char line[256];
        while (fgets(line, sizeof(line), fp)) {
            if (strncmp(line, "VmRSS:", 6) == 0) {
                sscanf(line + 6, "%lu", &usage->memory_usage_kb);
                break;
            }
        }
        fclose(fp);
    } else {
        usage->memory_usage_kb = 0;
    }
    
    // 获取AIO上下文信息(简化的模拟)
    usage->aio_contexts = 0;  // 实际应用中需要更复杂的实现
    
    return 0;
}

/**
 * 显示资源使用情况
 */
void show_resource_usage(const char *label, const resource_usage_t *usage) {
    printf("%s:\n", label);
    printf("  打开文件数: %lu\n", usage->open_files);
    printf("  内存使用: %lu KB\n", usage->memory_usage_kb);
    printf("  AIO上下文: %lu\n", usage->aio_contexts);
}

/**
 * 演示资源泄漏检测
 */
int demo_resource_leak_detection() {
    aio_context_t contexts[10];
    resource_usage_t before_usage, after_usage, after_destroy_usage;
    int created_count = 0;
    int destroyed_count = 0;
    
    printf("=== 资源泄漏检测演示 ===\n");
    
    // 获取初始资源使用情况
    printf("1. 获取初始资源使用情况:\n");
    if (get_resource_usage(&before_usage) == 0) {
        show_resource_usage("  初始状态", &before_usage);
    }
    
    // 创建多个AIO上下文
    printf("\n2. 创建多个AIO上下文:\n");
    
    for (int i = 0; i < 10; i++) {
        contexts[i] = 0;
        int ret = io_setup(64, &contexts[i]);
        if (ret == 0) {
            printf("  ✓ 创建AIO上下文 %d: ID=%llu\n", i + 1, (unsigned long long)contexts[i]);
            created_count++;
        } else {
            printf("  ✗ 创建AIO上下文 %d 失败: %s\n", i + 1, strerror(-ret));
        }
    }
    
    printf("  成功创建 %d 个AIO上下文\n", created_count);
    
    // 获取创建后的资源使用情况
    printf("\n3. 获取创建后的资源使用情况:\n");
    sleep(1);  // 等待系统更新资源统计
    
    if (get_resource_usage(&after_usage) == 0) {
        show_resource_usage("  创建后状态", &after_usage);
        
        // 显示资源变化
        if (after_usage.memory_usage_kb > before_usage.memory_usage_kb) {
            printf("  内存使用增加: %lu KB\n", 
                   after_usage.memory_usage_kb - before_usage.memory_usage_kb);
        }
    }
    
    // 销毁部分AIO上下文
    printf("\n4. 销毁部分AIO上下文:\n");
    
    // 销毁前5个上下文
    for (int i = 0; i < 5 && i < created_count; i++) {
        int ret = io_destroy(contexts[i]);
        if (ret == 0) {
            printf("  ✓ 销毁AIO上下文 %d: ID=%llu\n", i + 1, (unsigned long long)contexts[i]);
            destroyed_count++;
            contexts[i] = 0;  // 标记为已销毁
        } else {
            printf("  ✗ 销毁AIO上下文 %d 失败: %s\n", i + 1, strerror(-ret));
        }
    }
    
    printf("  成功销毁 %d 个AIO上下文\n", destroyed_count);
    
    // 获取销毁后的资源使用情况
    printf("\n5. 获取销毁后的资源使用情况:\n");
    sleep(1);  // 等待系统更新资源统计
    
    if (get_resource_usage(&after_destroy_usage) == 0) {
        show_resource_usage("  销毁后状态", &after_destroy_usage);
        
        // 显示资源变化
        if (after_destroy_usage.memory_usage_kb < after_usage.memory_usage_kb) {
            printf("  内存使用减少: %lu KB\n", 
                   after_usage.memory_usage_kb - after_destroy_usage.memory_usage_kb);
        }
        
        if (after_destroy_usage.memory_usage_kb < before_usage.memory_usage_kb) {
            printf("  内存使用净减少: %lu KB\n", 
                   before_usage.memory_usage_kb - after_destroy_usage.memory_usage_kb);
        }
    }
    
    // 销毁剩余的AIO上下文
    printf("\n6. 销毁剩余的AIO上下文:\n");
    
    for (int i = 5; i < created_count; i++) {
        if (contexts[i] != 0) {  // 未被销毁的上下文
            int ret = io_destroy(contexts[i]);
            if (ret == 0) {
                printf("  ✓ 销毁AIO上下文 %d: ID=%llu\n", i + 1, (unsigned long long)contexts[i]);
                destroyed_count++;
            } else {
                printf("  ✗ 销毁AIO上下文 %d 失败: %s\n", i + 1, strerror(-ret));
            }
        }
    }
    
    printf("  总共销毁 %d 个AIO上下文\n", destroyed_count);
    
    // 最终资源使用情况
    printf("\n7. 最终资源使用情况:\n");
    resource_usage_t final_usage;
    sleep(1);  // 等待系统更新资源统计
    
    if (get_resource_usage(&final_usage) == 0) {
        show_resource_usage("  最终状态", &final_usage);
        
        // 资源泄漏检测
        printf("\n8. 资源泄漏检测:\n");
        
        if (final_usage.memory_usage_kb <= before_usage.memory_usage_kb + 100) {
            printf("  ✓ 内存资源回收良好\n");
        } else {
            printf("  ⚠ 可能存在内存泄漏\n");
            printf("    内存使用增加: %lu KB\n", 
                   final_usage.memory_usage_kb - before_usage.memory_usage_kb);
        }
        
        if (final_usage.open_files == before_usage.open_files) {
            printf("  ✓ 文件描述符资源回收良好\n");
        } else {
            printf("  ⚠ 可能存在文件描述符泄漏\n");
        }
    }
    
    // 显示资源管理最佳实践
    printf("\n=== 资源管理最佳实践 ===\n");
    printf("1. 及时销毁:\n");
    printf("   ✓ 使用完AIO上下文后立即销毁\n");
    printf("   ✓ 避免长期持有不必要的上下文\n");
    printf("   ✓ 批量销毁提高效率\n");
    
    printf("\n2. 错误处理:\n");
    printf("   ✓ 妥善处理销毁失败的情况\n");
    printf("   ✓ 记录销毁操作日志\n");
    printf("   ✓ 重试机制处理临时失败\n");
    
    printf("\n3. 资源监控:\n");
    printf("   ✓ 监控资源使用情况\n");
    printf("   ✓ 检测潜在的资源泄漏\n");
    printf("   ✓ 定期清理闲置资源\n");
    
    printf("\n4. 安全考虑:\n");
    printf("   ✓ 验证上下文ID的有效性\n");
    printf("   ✓ 避免重复销毁同一上下文\n");
    printf("   ✓ 处理并发访问问题\n");
    
    return 0;
}

int main() {
    return demo_resource_leak_detection();
}

示例3:批量销毁管理

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

/**
 * AIO上下文管理器结构
 */
typedef struct {
    aio_context_t contexts[64];
    int context_count;
    int max_contexts;
    time_t creation_time;
    unsigned long total_created;
    unsigned long total_destroyed;
} aio_context_manager_t;

/**
 * 初始化AIO上下文管理器
 */
int init_aio_context_manager(aio_context_manager_t *manager) {
    memset(manager, 0, sizeof(aio_context_manager_t));
    manager->max_contexts = 64;
    manager->creation_time = time(NULL);
    manager->total_created = 0;
    manager->total_destroyed = 0;
    
    printf("AIO上下文管理器初始化完成\n");
    printf("  最大上下文数: %d\n", manager->max_contexts);
    printf("  创建时间: %s", ctime(&manager->creation_time));
    
    return 0;
}

/**
 * 创建AIO上下文
 */
int create_aio_context(aio_context_manager_t *manager, unsigned nr_events, aio_context_t *ctx) {
    if (manager->context_count >= manager->max_contexts) {
        printf("AIO上下文数量已达上限\n");
        return -1;
    }
    
    *ctx = 0;
    int ret = io_setup(nr_events, ctx);
    if (ret == 0) {
        manager->contexts[manager->context_count] = *ctx;
        manager->context_count++;
        manager->total_created++;
        printf("创建AIO上下文成功: ID=%llu\n", (unsigned long long)*ctx);
        return 0;
    } else {
        printf("创建AIO上下文失败: %s\n", strerror(-ret));
        return -1;
    }
}

/**
 * 批量销毁AIO上下文
 */
int batch_destroy_aio_contexts(aio_context_manager_t *manager) {
    printf("批量销毁AIO上下文:\n");
    printf("  当前上下文数量: %d\n", manager->context_count);
    
    int destroyed_count = 0;
    int failed_count = 0;
    
    // 从后往前销毁(避免数组索引问题)
    for (int i = manager->context_count - 1; i >= 0; i--) {
        aio_context_t ctx = manager->contexts[i];
        if (ctx != 0) {
            int ret = io_destroy(ctx);
            if (ret == 0) {
                printf("  ✓ 销毁上下文 %d: ID=%llu\n", i + 1, (unsigned long long)ctx);
                destroyed_count++;
                manager->total_destroyed++;
                
                // 从数组中移除
                for (int j = i; j < manager->context_count - 1; j++) {
                    manager->contexts[j] = manager->contexts[j + 1];
                }
                manager->context_count--;
            } else {
                printf("  ✗ 销毁上下文 %d 失败: %s\n", i + 1, strerror(-ret));
                failed_count++;
            }
        }
    }
    
    printf("批量销毁完成:\n");
    printf("  成功销毁: %d 个\n", destroyed_count);
    printf("  销毁失败: %d 个\n", failed_count);
    printf("  剩余上下文: %d 个\n", manager->context_count);
    
    return (failed_count == 0) ? 0 : -1;
}

/**
 * 销毁所有AIO上下文
 */
int destroy_all_aio_contexts(aio_context_manager_t *manager) {
    printf("销毁所有AIO上下文:\n");
    
    if (manager->context_count == 0) {
        printf("  没有待销毁的上下文\n");
        return 0;
    }
    
    return batch_destroy_aio_contexts(manager);
}

/**
 * 演示批量销毁管理
 */
int demo_batch_destroy_management() {
    aio_context_manager_t manager;
    
    printf("=== 批量销毁管理演示 ===\n");
    
    // 初始化管理器
    printf("1. 初始化AIO上下文管理器:\n");
    if (init_aio_context_manager(&manager) != 0) {
        return -1;
    }
    
    // 创建多个AIO上下文
    printf("\n2. 创建多个AIO上下文:\n");
    
    const int create_count = 10;
    aio_context_t created_contexts[create_count];
    int successful_creates = 0;
    
    for (int i = 0; i < create_count; i++) {
        printf("  创建第 %d 个上下文:\n", i + 1);
        
        if (create_aio_context(&manager, 32, &created_contexts[i]) == 0) {
            successful_creates++;
        } else {
            created_contexts[i] = 0;  // 标记为创建失败
        }
        
        // 短暂延迟以避免系统过载
        if (i < create_count - 1) {
            usleep(10000);  // 10ms延迟
        }
    }
    
    printf("  创建完成:\n");
    printf("    尝试创建: %d 个\n", create_count);
    printf("    成功创建: %d 个\n", successful_creates);
    printf("    创建失败: %d 个\n", create_count - successful_creates);
    printf("    管理器状态: %d 个上下文\n", manager.context_count);
    
    // 显示当前管理器状态
    printf("\n3. 当前管理器状态:\n");
    printf("  总创建数: %lu\n", manager.total_created);
    printf("  总销毁数: %lu\n", manager.total_destroyed);
    printf("  当前上下文数: %d\n", manager.context_count);
    printf("  管理器运行时间: %ld 秒\n", 
           (long)difftime(time(NULL), manager.creation_time));
    
    // 显示所有上下文ID
    printf("  当前上下文ID列表:\n");
    for (int i = 0; i < manager.context_count; i++) {
        printf("    %d: %llu\n", i + 1, (unsigned long long)manager.contexts[i]);
    }
    
    // 批量销毁
    printf("\n4. 批量销毁操作:\n");
    if (destroy_all_aio_contexts(&manager) != 0) {
        printf("  批量销毁过程中出现错误\n");
    }
    
    // 验证销毁结果
    printf("\n5. 验证销毁结果:\n");
    printf("  销毁后状态:\n");
    printf("    当前上下文数: %d\n", manager.context_count);
    printf("    总销毁数: %lu\n", manager.total_destroyed);
    printf("    剩余上下文ID:\n");
    
    if (manager.context_count > 0) {
        for (int i = 0; i < manager.context_count; i++) {
            printf("      %d: %llu\n", i + 1, (unsigned long long)manager.contexts[i]);
        }
        printf("    ⚠ 警告:仍有 %d 个上下文未被销毁\n", manager.context_count);
    } else {
        printf("      无\n");
        printf("    ✓ 所有上下文均已销毁\n");
    }
    
    // 重新创建一些上下文进行最终销毁测试
    printf("\n6. 最终销毁测试:\n");
    
    // 重新创建几个上下文
    printf("  重新创建测试上下文:\n");
    for (int i = 0; i < 3; i++) {
        if (create_aio_context(&manager, 16, &created_contexts[i]) == 0) {
            printf("    重新创建上下文 %d 成功\n", i + 1);
        }
    }
    
    printf("  重新创建后状态:\n");
    printf("    当前上下文数: %d\n", manager.context_count);
    printf("    总创建数: %lu\n", manager.total_created);
    
    // 最终批量销毁
    printf("  执行最终批量销毁:\n");
    if (destroy_all_aio_contexts(&manager) == 0) {
        printf("  ✓ 最终批量销毁成功\n");
    } else {
        printf("  ✗ 最终批量销毁失败\n");
    }
    
    // 显示最终统计
    printf("\n7. 最终统计:\n");
    printf("  管理器生命周期统计:\n");
    printf("    总创建数: %lu\n", manager.total_created);
    printf("    总销毁数: %lu\n", manager.total_destroyed);
    printf("    当前上下文数: %d\n", manager.context_count);
    printf("    运行时间: %ld 秒\n", 
           (long)difftime(time(NULL), manager.creation_time));
    
    if (manager.total_created == manager.total_destroyed && manager.context_count == 0) {
        printf("  ✓ 资源管理完整性检查通过\n");
    } else {
        printf("  ⚠ 资源管理完整性检查失败\n");
        printf("    可能存在资源泄漏\n");
    }
    
    return 0;
}

int main() {
    return demo_batch_destroy_management();
}

示例4:异常情况处理

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

/**
 * 演示异常情况处理
 */
int demo_exception_handling() {
    aio_context_t ctx;
    int ret;
    
    printf("=== 异常情况处理演示 ===\n");
    
    // 1. 销毁无效的上下文ID
    printf("1. 销毁无效的上下文ID:\n");
    
    // 测试销毁0值上下文ID
    printf("  测试销毁上下文ID 0:\n");
    ret = io_destroy(0);
    if (ret == 0) {
        printf("    ✓ 销毁成功(可能表示空操作)\n");
    } else {
        printf("    %s\n", strerror(-ret));
        if (ret == -EINVAL) {
            printf("    ✓ 预期结果:无效的上下文ID\n");
        } else {
            printf("    ✗ 意外错误\n");
        }
    }
    
    // 测试销毁负值上下文ID
    printf("  测试销毁负值上下文ID (-1):\n");
    ret = io_destroy((aio_context_t)-1);
    if (ret == 0) {
        printf("    ✓ 销毁成功\n");
    } else {
        printf("    %s\n", strerror(-ret));
        if (ret == -EINVAL) {
            printf("    ✓ 预期结果:无效的上下文ID\n");
        } else {
            printf("    ✗ 意外错误\n");
        }
    }
    
    // 测试销毁超大值上下文ID
    printf("  测试销毁超大值上下文ID:\n");
    aio_context_t large_ctx = (aio_context_t)0x7FFFFFFFFFFFFFFFULL;
    ret = io_destroy(large_ctx);
    if (ret == 0) {
        printf("    ✓ 销毁成功\n");
    } else {
        printf("    %s\n", strerror(-ret));
        if (ret == -EINVAL) {
            printf("    ✓ 预期结果:无效的上下文ID\n");
        } else {
            printf("    ✗ 意外错误\n");
        }
    }
    
    // 2. 重复销毁同一上下文
    printf("\n2. 重复销毁同一上下文:\n");
    
    // 创建一个有效的上下文
    printf("  创建有效上下文:\n");
    ctx = 0;
    ret = io_setup(32, &ctx);
    if (ret == 0) {
        printf("    ✓ 上下文创建成功: ID=%llu\n", (unsigned long long)ctx);
        
        // 第一次销毁
        printf("  第一次销毁:\n");
        ret = io_destroy(ctx);
        if (ret == 0) {
            printf("    ✓ 第一次销毁成功\n");
        } else {
            printf("    ✗ 第一次销毁失败: %s\n", strerror(-ret));
        }
        
        // 第二次销毁同一上下文(应该失败)
        printf("  第二次销毁同一上下文:\n");
        ret = io_destroy(ctx);
        if (ret == 0) {
            printf("    ✓ 第二次销毁成功(可能表示幂等操作)\n");
        } else {
            printf("    %s\n", strerror(-ret));
            if (ret == -EINVAL) {
                printf("    ✓ 预期结果:上下文已销毁\n");
            } else {
                printf("    ✗ 意外错误\n");
            }
        }
    } else {
        printf("    ✗ 创建上下文失败: %s\n", strerror(-ret));
    }
    
    // 3. 销毁正在使用的上下文
    printf("\n3. 销毁正在使用的上下文:\n");
    
    // 创建新的上下文
    printf("  创建新上下文:\n");
    ctx = 0;
    ret = io_setup(64, &ctx);
    if (ret == 0) {
        printf("    ✓ 新上下文创建成功: ID=%llu\n", (unsigned long long)ctx);
        
        // 创建测试文件
        const char *filename = "in_use_test.txt";
        int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (fd != -1) {
            printf("    ✓ 测试文件创建成功\n");
            
            // 准备长时间写入操作
            char *large_data = malloc(1024 * 1024);  // 1MB数据
            if (large_data) {
                // 填充测试数据
                for (int i = 0; i < 1024 * 1024; i++) {
                    large_data[i] = 'A' + (i % 26);
                }
                
                // 准备异步写入操作
                struct iocb iocb;
                memset(&iocb, 0, sizeof(iocb));
                iocb.aio_data = 999;
                iocb.aio_lio_opcode = IOCB_CMD_PWRITE;
                iocb.aio_fildes = fd;
                iocb.aio_buf = (uint64_t)(uintptr_t)large_data;
                iocb.aio_nbytes = 1024 * 1024;
                iocb.aio_offset = 0;
                
                // 提交异步操作
                struct iocb *iocbs[1] = {&iocb};
                ret = io_submit(ctx, 1, iocbs);
                if (ret == 1) {
                    printf("    ✓ 异步写入操作提交成功\n");
                    
                    // 立即尝试销毁上下文
                    printf("  立即尝试销毁正在使用的上下文:\n");
                    ret = io_destroy(ctx);
                    if (ret == 0) {
                        printf("    ✓ 上下文销毁成功\n");
                    } else {
                        printf("    %s\n", strerror(-ret));
                        if (ret == -EAGAIN) {
                            printf("    ✓ 预期结果:上下文正在使用中\n");
                        } else if (ret == -EINVAL) {
                            printf("    ✓ 预期结果:上下文无效\n");
                        } else {
                            printf("    ✗ 意外错误\n");
                        }
                    }
                } else {
                    printf("    ✗ 提交异步操作失败: %s\n", strerror(-ret));
                }
                
                free(large_data);
            }
            
            close(fd);
            unlink(filename);
        }
    } else {
        printf("    ✗ 创建新上下文失败: %s\n", strerror(-ret));
    }
    
    // 4. 错误恢复演示
    printf("\n4. 错误恢复演示:\n");
    
    // 模拟错误恢复场景
    printf("  错误恢复策略:\n");
    printf("    1. 验证上下文ID有效性\n");
    printf("    2. 检查上下文使用状态\n");
    printf("    3. 尝试取消未完成操作\n");
    printf("    4. 安全销毁上下文\n");
    printf("    5. 记录错误日志\n");
    printf("    6. 通知上层应用\n");
    
    // 5. 资源清理验证
    printf("\n5. 资源清理验证:\n");
    
    // 创建多个上下文用于清理验证
    aio_context_t test_contexts[5];
    int created_count = 0;
    
    printf("  创建测试上下文:\n");
    for (int i = 0; i < 5; i++) {
        test_contexts[i] = 0;
        ret = io_setup(16, &test_contexts[i]);
        if (ret == 0) {
            printf("    ✓ 上下文 %d 创建成功: ID=%llu\n", 
                   i + 1, (unsigned long long)test_contexts[i]);
            created_count++;
        } else {
            printf("    ✗ 上下文 %d 创建失败: %s\n", i + 1, strerror(-ret));
            test_contexts[i] = 0;  // 标记为未创建
        }
    }
    
    printf("  清理所有测试上下文:\n");
    int destroyed_count = 0;
    int error_count = 0;
    
    for (int i = 0; i < 5; i++) {
        if (test_contexts[i] != 0) {
            ret = io_destroy(test_contexts[i]);
            if (ret == 0) {
                printf("    ✓ 上下文 %d 销毁成功\n", i + 1);
                destroyed_count++;
            } else {
                printf("    ✗ 上下文 %d 销毁失败: %s\n", i + 1, strerror(-ret));
                error_count++;
            }
        }
    }
    
    printf("  清理结果:\n");
    printf("    成功销毁: %d 个\n", destroyed_count);
    printf("    销毁失败: %d 个\n", error_count);
    printf("    总计处理: %d 个\n", destroyed_count + error_count);
    
    // 6. 最终状态检查
    printf("\n6. 最终状态检查:\n");
    printf("  系统状态:\n");
    printf("    ✓ 错误处理机制正常\n");
    printf("    ✓ 资源清理机制正常\n");
    printf("    ✓ 状态恢复机制正常\n");
    printf("    ✓ 日志记录机制正常\n");
    
    return 0;
}

int main() {
    return demo_exception_handling();
}

示例5:生产环境使用模式

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>

/**
 * 生产环境AIO上下文管理器
 */
typedef struct {
    aio_context_t ctx;
    int is_initialized;
    int is_destroyed;
    time_t create_time;
    time_t destroy_time;
    unsigned long operations_submitted;
    unsigned long operations_completed;
    unsigned long operations_cancelled;
    pthread_mutex_t mutex;
    volatile int shutdown_requested;
} production_aio_context_t;

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbsp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 初始化生产环境AIO上下文
 */
int init_production_aio_context(production_aio_context_t *ctx, unsigned nr_events) {
    // 初始化结构体
    memset(ctx, 0, sizeof(production_aio_context_t));
    ctx->create_time = time(NULL);
    ctx->is_initialized = 0;
    ctx->is_destroyed = 0;
    ctx->shutdown_requested = 0;
    
    // 初始化互斥锁
    if (pthread_mutex_init(&ctx->mutex, NULL) != 0) {
        printf("初始化互斥锁失败\n");
        return -1;
    }
    
    // 创建AIO上下文
    ctx->ctx = 0;
    int ret = io_setup(nr_events, &ctx->ctx);
    if (ret != 0) {
        printf("创建AIO上下文失败: %s\n", strerror(-ret));
        pthread_mutex_destroy(&ctx->mutex);
        return -1;
    }
    
    ctx->is_initialized = 1;
    printf("生产环境AIO上下文初始化成功:\n");
    printf("  上下文ID: %llu\n", (unsigned long long)ctx->ctx);
    printf("  缓冲区大小: %u 事件\n", nr_events);
    printf("  创建时间: %s", ctime(&ctx->create_time));
    
    return 0;
}

/**
 * 安全销毁生产环境AIO上下文
 */
int destroy_production_aio_context(production_aio_context_t *ctx) {
    if (!ctx) {
        printf("上下文指针无效\n");
        return -1;
    }
    
    // 获取互斥锁
    if (pthread_mutex_lock(&ctx->mutex) != 0) {
        printf("获取互斥锁失败\n");
        return -1;
    }
    
    // 检查是否已经销毁
    if (ctx->is_destroyed) {
        printf("上下文已销毁,无需重复操作\n");
        pthread_mutex_unlock(&ctx->mutex);
        return 0;
    }
    
    // 设置关闭请求标志
    ctx->shutdown_requested = 1;
    
    // 取消所有未完成的操作
    printf("取消所有未完成的操作...\n");
    // 这里应该实现取消未完成操作的逻辑
    
    // 销毁AIO上下文
    printf("销毁AIO上下文: %llu\n", (unsigned long long)ctx->ctx);
    int ret = io_destroy(ctx->ctx);
    if (ret != 0) {
        printf("销毁AIO上下文失败: %s\n", strerror(-ret));
        if (ret == -EAGAIN) {
            printf("  原因:上下文中仍有未完成的操作\n");
        } else if (ret == -EINVAL) {
            printf("  原因:无效的上下文ID\n");
        }
        
        // 即使销毁失败也要清理资源
        ctx->is_destroyed = 1;
        ctx->destroy_time = time(NULL);
        pthread_mutex_unlock(&ctx->mutex);
        pthread_mutex_destroy(&ctx->mutex);
        return -1;
    }
    
    ctx->is_destroyed = 1;
    ctx->destroy_time = time(NULL);
    
    // 显示统计信息
    printf("AIO上下文销毁成功:\n");
    printf("  销毁时间: %s", ctime(&ctx->destroy_time));
    printf("  运行时间: %ld 秒\n", 
           (long)difftime(ctx->destroy_time, ctx->create_time));
    printf("  提交操作数: %lu\n", ctx->operations_submitted);
    printf("  完成操作数: %lu\n", ctx->operations_completed);
    printf("  取消操作数: %lu\n", ctx->operations_cancelled);
    
    pthread_mutex_unlock(&ctx->mutex);
    pthread_mutex_destroy(&ctx->mutex);
    
    return 0;
}

/**
 * 优雅关闭信号处理
 */
void graceful_shutdown_handler(int sig) {
    printf("\n收到关闭信号 %d,开始优雅关闭...\n", sig);
    // 这里应该通知所有AIO上下文管理器开始关闭
}

/**
 * 演示生产环境使用模式
 */
int demo_production_usage_pattern() {
    production_aio_context_t aio_ctx;
    struct sigaction sa;
    
    printf("=== 生产环境使用模式演示 ===\n");
    
    // 设置信号处理
    printf("1. 设置信号处理:\n");
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = graceful_shutdown_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if (sigaction(SIGINT, &sa, NULL) == 0) {
        printf("  ✓ SIGINT信号处理设置成功\n");
    } else {
        printf("  ✗ SIGINT信号处理设置失败: %s\n", strerror(errno));
    }
    
    if (sigaction(SIGTERM, &sa, NULL) == 0) {
        printf("  ✓ SIGTERM信号处理设置成功\n");
    } else {
        printf("  ✗ SIGTERM信号处理设置失败: %s\n", strerror(errno));
    }
    
    // 初始化AIO上下文
    printf("\n2. 初始化生产环境AIO上下文:\n");
    if (init_production_aio_context(&aio_ctx, 1024) != 0) {
        printf("初始化生产环境AIO上下文失败\n");
        return -1;
    }
    
    printf("  ✓ 生产环境AIO上下文初始化成功\n");
    
    // 模拟生产环境操作
    printf("\n3. 模拟生产环境操作:\n");
    
    // 模拟提交操作
    printf("  模拟提交异步操作:\n");
    for (int i = 0; i < 100; i++) {
        // 这里应该是实际的异步操作提交
        aio_ctx.operations_submitted++;
        
        if (i % 20 == 0) {
            printf("    已提交 %d 个操作\n", i);
        }
        
        // 模拟操作完成
        if (i % 3 == 0) {
            aio_ctx.operations_completed++;
        }
        
        // 模拟操作取消
        if (i % 7 == 0) {
            aio_ctx.operations_cancelled++;
        }
    }
    
    printf("  ✓ 模拟操作完成\n");
    printf("    总提交: %lu\n", aio_ctx.operations_submitted);
    printf("    总完成: %lu\n", aio_ctx.operations_completed);
    printf("    总取消: %lu\n", aio_ctx.operations_cancelled);
    
    // 模拟运行期间的状态监控
    printf("\n4. 模拟运行期间状态监控:\n");
    
    // 显示当前状态
    printf("  当前AIO上下文状态:\n");
    printf("    已初始化: %s\n", aio_ctx.is_initialized ? "是" : "否");
    printf("    已销毁: %s\n", aio_ctx.is_destroyed ? "是" : "否");
    printf("    关闭请求: %s\n", aio_ctx.shutdown_requested ? "是" : "否");
    printf("    运行时间: %ld 秒\n", 
           (long)difftime(time(NULL), aio_ctx.create_time));
    
    // 模拟健康检查
    printf("  健康检查:\n");
    printf("    ✓ 上下文ID有效性检查\n");
    printf("    ✓ 内存使用情况检查\n");
    printf("    ✓ 操作队列状态检查\n");
    printf("    ✓ 错误率统计检查\n");
    
    // 模拟优雅关闭
    printf("\n5. 模拟优雅关闭:\n");
    
    // 设置关闭标志
    printf("  设置关闭标志:\n");
    aio_ctx.shutdown_requested = 1;
    printf("    ✓ 关闭请求已设置\n");
    
    // 等待未完成操作
    printf("  等待未完成操作:\n");
    printf("    当前未完成操作数: %lu\n", 
           aio_ctx.operations_submitted - 
           aio_ctx.operations_completed - 
           aio_ctx.operations_cancelled);
    
    // 取消未完成操作
    printf("  取消未完成操作:\n");
    unsigned long pending_operations = aio_ctx.operations_submitted - 
                                      aio_ctx.operations_completed - 
                                      aio_ctx.operations_cancelled;
    printf("    取消 %lu 个未完成操作\n", pending_operations);
    aio_ctx.operations_cancelled += pending_operations;
    
    // 销毁AIO上下文
    printf("  销毁AIO上下文:\n");
    if (destroy_production_aio_context(&aio_ctx) == 0) {
        printf("    ✓ AIO上下文销毁成功\n");
    } else {
        printf("    ✗ AIO上下文销毁失败\n");
    }
    
    // 显示最终统计
    printf("\n6. 最终统计:\n");
    printf("  生命周期统计:\n");
    printf("    创建时间: %s", ctime(&aio_ctx.create_time));
    if (aio_ctx.is_destroyed) {
        printf("    销毁时间: %s", ctime(&aio_ctx.destroy_time));
        printf("    运行时间: %ld 秒\n", 
               (long)difftime(aio_ctx.destroy_time, aio_ctx.create_time));
    }
    
    printf("  操作统计:\n");
    printf("    总提交操作: %lu\n", aio_ctx.operations_submitted);
    printf("    总完成操作: %lu\n", aio_ctx.operations_completed);
    printf("    总取消操作: %lu\n", aio_ctx.operations_cancelled);
    
    // 显示生产环境最佳实践
    printf("\n=== 生产环境最佳实践 ===\n");
    printf("1. 初始化管理:\n");
    printf("   ✓ 延迟初始化\n");
    printf("   ✓ 配置验证\n");
    printf("   ✓ 资源预留\n");
    printf("   ✓ 错误恢复\n");
    
    printf("\n2. 运行时管理:\n");
    printf("   ✓ 状态监控\n");
    printf("   ✓ 性能统计\n");
    printf("   ✓ 错误处理\n");
    printf("   ✓ 资源池管理\n");
    
    printf("\n3. 优雅关闭:\n");
    printf("   ✓ 信号处理\n");
    printf("   ✓ 操作清理\n");
    printf("   ✓ 资源释放\n");
    printf("   ✓ 状态保存\n");
    
    printf("\n4. 错误恢复:\n");
    printf("   ✓ 重试机制\n");
    printf("   ✓ 降级处理\n");
    printf("   ✓ 告警通知\n");
    printf("   ✓ 日志记录\n");
    
    printf("\n5. 监控告警:\n");
    printf("   ✓ 性能指标\n");
    printf("   ✓ 错误率统计\n");
    printf("   ✓ 资源使用率\n");
    printf("   ✓ 健康检查\n");
    
    return 0;
}

int main() {
    return demo_production_usage_pattern();
}

io_destroy 使用注意事项

系统要求:

  1. 内核版本: 支持AIO的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

销毁时机:

  1. 使用完成后: AIO上下文不再需要时立即销毁
  2. 程序退出前: 确保所有上下文都被正确销毁
  3. 异常处理: 异常情况下也要销毁上下文

错误处理:

  1. EINVAL: 无效的上下文ID
  2. EAGAIN: 上下文中仍有未完成的操作
  3. EFAULT: 上下文指针无效

资源管理:

  1. 及时销毁: 避免资源泄漏
  2. 批量销毁: 提高销毁效率
  3. 状态检查: 销毁前检查上下文状态
  4. 错误恢复: 处理销毁失败的情况

性能考虑:

  1. 销毁开销: 销毁操作有一定开销
  2. 批量操作: 批量销毁比单独销毁更高效
  3. 异步销毁: 大量上下文时考虑异步销毁
  4. 资源回收: 及时回收系统资源

安全考虑:

  1. 权限验证: 确保有权限销毁上下文
  2. 参数验证: 验证上下文ID的有效性
  3. 状态检查: 检查上下文使用状态
  4. 并发控制: 多线程环境下的同步控制

最佳实践:

  1. RAII原则: 使用完立即销毁
  2. 异常安全: 异常情况下也能正确销毁
  3. 资源统计: 统计和监控资源使用情况
  4. 日志记录: 记录销毁操作日志
  5. 错误处理: 妥善处理销毁失败的情况

io_destroy vs 相似函数对比

io_destroy vs close:

// io_destroy: 销毁AIO上下文
io_destroy(ctx);

// close: 关闭文件描述符
close(fd);

io_destroy vs free:

// io_destroy: 销毁内核资源
io_destroy(ctx);

// free: 释放用户空间内存
free(ptr);

常见使用场景

1. 服务器应用:

// 服务器退出时销毁所有AIO上下文
void server_shutdown() {
    for (int i = 0; i < context_count; i++) {
        io_destroy(contexts[i]);
    }
}

2. 批处理应用:

// 批处理完成后销毁AIO上下文
void batch_cleanup() {
    batch_destroy_aio_contexts(&manager);
}

3. 插件系统:

// 插件卸载时销毁相关AIO上下文
void plugin_unload() {
    destroy_plugin_aio_contexts();
}

系统限制和约束

1. 上下文数量限制:

// 系统限制可通过以下方式查看:
cat /proc/sys/fs/aio-max-nr    # 最大AIO上下文数
cat /proc/sys/fs/aio-nr        # 当前AIO上下文数

2. 内存限制:

// 每个AIO上下文都有内存开销
// 需要合理控制上下文数量

3. 文件描述符限制:

// AIO操作涉及的文件描述符也需要管理
// 避免文件描述符泄漏

错误恢复策略

1. 重试机制:

int safe_io_destroy(aio_context_t ctx) {
    int retries = 3;
    int result;
    
    while (retries-- > 0) {
        result = io_destroy(ctx);
        if (result == 0) {
            return 0;  // 成功
        }
        
        if (result == -EAGAIN) {
            // 等待未完成操作
            usleep(100000);  // 100ms
            continue;
        }
        
        break;  // 其他错误不重试
    }
    
    return result;
}

2. 状态检查:

int validate_context_before_destroy(aio_context_t ctx) {
    // 检查上下文是否有效
    if (ctx == 0) {
        return -1;  // 无效上下文
    }
    
    // 检查上下文是否已被销毁
    // 这需要应用层维护状态
    
    return 0;
}

3. 资源清理:

int comprehensive_cleanup(aio_context_t ctx) {
    // 取消所有未完成操作
    cancel_pending_operations(ctx);
    
    // 等待操作完成
    wait_for_operations_completion(ctx);
    
    // 销毁上下文
    return io_destroy(ctx);
}

总结

io_destroy 是Linux AIO框架中重要的资源清理函数,提供了:

  1. 资源回收: 释放AIO上下文相关的内核资源
  2. 内存管理: 回收环形缓冲区和其他数据结构
  3. 状态清理: 清理上下文相关的等待队列和状态
  4. 安全保障: 确保系统资源得到正确回收

通过合理使用 io_destroy,可以构建健壮的异步I/O应用。在实际应用中,需要注意资源管理、错误处理和性能优化等关键问题。特别是在生产环境中,需要实现完善的错误恢复和优雅关闭机制,确保系统的稳定性和可靠性。

发表在 linux文章 | 留下评论

io_cancel系统调用及示例

io_cancel 函数详解

1. 函数介绍

io_cancel 是Linux传统异步I/O (AIO) 系统调用,用于取消先前提交但尚未完成的异步I/O操作。它是AIO框架的重要组成部分,允许应用程序在必要时取消正在进行的异步操作,提供更灵活的I/O控制能力。

2. 函数原型

#include <linux/aio_abi.h>
int io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);

3. 功能

io_cancel 尝试取消指定的异步I/O操作。如果操作仍在排队或尚未开始执行,则可以成功取消;如果操作已经开始执行或已经完成,则取消可能失败。成功取消的操作会返回一个带有ECANCELED错误码的完成事件。

4. 参数

  • aio_context_t ctx_id: AIO上下文ID(由io_setup创建)
  • *struct iocb iocb: 要取消的异步I/O控制块指针
  • *struct io_event result: 用于存储取消结果的事件结构指针

5. 返回值

  • 成功: 返回0
  • 失败: 返回负的错误码

6. 相似函数,或关联函数

  • io_setup: 初始化AIO上下文
  • io_destroy: 销毁AIO上下文
  • io_submit: 提交异步I/O操作
  • io_getevents: 获取完成的异步I/O事件
  • io_pgetevents: 带信号处理的事件获取

7. 示例代码

示例1:基础io_cancel使用

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 演示基础io_cancel使用方法
 */
int demo_io_cancel_basic() {
    aio_context_t ctx;
    struct iocb iocb;
    struct io_event result;
    int fd;
    int ret;
    
    printf("=== 基础io_cancel使用示例 ===\n");
    
    // 初始化AIO上下文
    printf("1. 初始化AIO上下文:\n");
    ctx = 0;
    ret = io_setup(128, &ctx);
    if (ret < 0) {
        printf("  初始化AIO上下文失败: %s\n", strerror(-ret));
        return -1;
    }
    printf("  ✓ AIO上下文初始化成功\n");
    printf("  上下文ID: %llu\n", (unsigned long long)ctx);
    
    // 创建测试文件
    printf("\n2. 创建测试文件:\n");
    const char *filename = "aio_cancel_test.txt";
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("  创建测试文件失败");
        io_destroy(ctx);
        return -1;
    }
    printf("  ✓ 测试文件创建成功: %s\n", filename);
    
    // 准备异步写入操作
    printf("\n3. 准备异步写入操作:\n");
    char *test_data = "This is test data for AIO cancel operation.\n";
    size_t data_size = strlen(test_data);
    
    // 初始化iocb结构
    memset(&iocb, 0, sizeof(iocb));
    iocb.aio_data = 12345;  // 用户数据
    iocb.aio_key = 0;
    iocb.aio_rw_flags = 0;
    iocb.aio_lio_opcode = IOCB_CMD_PWRITE;  // 异步写入
    iocb.aio_reqprio = 0;
    iocb.aio_fildes = fd;
    iocb.aio_buf = (uint64_t)(uintptr_t)test_data;
    iocb.aio_nbytes = data_size;
    iocb.aio_offset = 0;
    iocb.aio_flags = 0;
    iocb.aio_resfd = 0;
    
    printf("  准备异步写入操作:\n");
    printf("    文件描述符: %d\n", fd);
    printf("    数据大小: %zu 字节\n", data_size);
    printf("    用户数据: %llu\n", (unsigned long long)iocb.aio_data);
    
    // 提交异步操作
    printf("\n4. 提交异步操作:\n");
    struct iocb *iocbs[1] = {&iocb};
    ret = io_submit(ctx, 1, iocbs);
    if (ret != 1) {
        printf("  提交异步操作失败: %s\n", strerror(-ret));
        close(fd);
        unlink(filename);
        io_destroy(ctx);
        return -1;
    }
    printf("  ✓ 异步操作提交成功\n");
    
    // 立即尝试取消操作
    printf("\n5. 立即尝试取消操作:\n");
    ret = io_cancel(ctx, &iocb, &result);
    if (ret == 0) {
        printf("  ✓ 操作取消成功\n");
        printf("  取消结果:\n");
        printf("    用户数据: %llu\n", (unsigned long long)result.data);
        printf("    结果: %ld\n", result.res);
        printf("    结果2: %ld\n", result.res2);
        if (result.res == -ECANCELED) {
            printf("    状态: 操作已取消 (ECANCELED)\n");
        }
    } else if (ret == -EAGAIN) {
        printf("  ℹ 操作无法取消 (可能已开始执行或已完成)\n");
        
        // 等待操作完成
        printf("  等待操作完成...\n");
        struct io_event events[1];
        ret = io_getevents(ctx, 1, 1, events, NULL);
        if (ret > 0) {
            printf("  ✓ 操作完成\n");
            printf("    用户数据: %llu\n", (unsigned long long)events[0].data);
            printf("    结果: %ld 字节\n", events[0].res);
        }
    } else {
        printf("  ✗ 取消操作失败: %s\n", strerror(-ret));
    }
    
    // 演示延时取消
    printf("\n6. 演示延时取消:\n");
    
    // 创建一个更大的写入操作
    char *large_data = malloc(1024 * 1024);  // 1MB数据
    if (large_data) {
        // 填充数据
        for (int i = 0; i < 1024 * 1024; i++) {
            large_data[i] = 'A' + (i % 26);
        }
        
        // 准备新的iocb
        struct iocb large_iocb;
        memset(&large_iocb, 0, sizeof(large_iocb));
        large_iocb.aio_data = 54321;
        large_iocb.aio_lio_opcode = IOCB_CMD_PWRITE;
        large_iocb.aio_fildes = fd;
        large_iocb.aio_buf = (uint64_t)(uintptr_t)large_data;
        large_iocb.aio_nbytes = 1024 * 1024;
        large_iocb.aio_offset = data_size;  // 在之前数据之后
        
        printf("  准备大块数据写入操作 (1MB)\n");
        
        struct iocb *large_iocbs[1] = {&large_iocb};
        ret = io_submit(ctx, 1, large_iocbs);
        if (ret == 1) {
            printf("  ✓ 大块数据写入操作提交成功\n");
            
            // 短暂延迟后尝试取消
            printf("  等待100ms后尝试取消...\n");
            usleep(100000);  // 100ms
            
            struct io_event cancel_result;
            ret = io_cancel(ctx, &large_iocb, &cancel_result);
            if (ret == 0) {
                printf("  ✓ 大块数据写入操作取消成功\n");
            } else if (ret == -EAGAIN) {
                printf("  ℹ 大块数据写入操作无法取消\n");
                
                // 等待操作完成
                struct io_event events[1];
                int wait_ret = io_getevents(ctx, 1, 1, events, NULL);
                if (wait_ret > 0) {
                    printf("  ✓ 大块数据写入完成: %ld 字节\n", events[0].res);
                }
            } else {
                printf("  ✗ 取消大块数据写入失败: %s\n", strerror(-ret));
            }
        }
        
        free(large_data);
    }
    
    // 清理资源
    printf("\n7. 清理资源:\n");
    close(fd);
    unlink(filename);
    io_destroy(ctx);
    printf("  ✓ 资源清理完成\n");
    
    return 0;
}

int main() {
    return demo_io_cancel_basic();
}

示例2:批量操作取消

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 批量操作取消演示
 */
int demo_batch_cancel() {
    aio_context_t ctx;
    const int batch_size = 8;
    struct iocb iocbs[batch_size];
    struct io_event results[batch_size];
    int fd;
    int ret;
    char *test_files[batch_size];
    
    printf("=== 批量操作取消演示 ===\n");
    
    // 初始化AIO上下文
    printf("1. 初始化AIO上下文:\n");
    ctx = 0;
    ret = io_setup(128, &ctx);
    if (ret < 0) {
        printf("  初始化AIO上下文失败: %s\n", strerror(-ret));
        return -1;
    }
    printf("  ✓ AIO上下文初始化成功\n");
    
    // 创建测试文件
    printf("\n2. 创建测试文件:\n");
    for (int i = 0; i < batch_size; i++) {
        char filename[32];
        snprintf(filename, sizeof(filename), "batch_cancel_%d.txt", i);
        test_files[i] = strdup(filename);
        
        fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        if (fd != -1) {
            // 写入一些初始数据使文件不为空
            char initial_data[256];
            memset(initial_data, 'A' + i, sizeof(initial_data) - 1);
            initial_data[sizeof(initial_data) - 1] = '\0';
            write(fd, initial_data, sizeof(initial_data) - 1);
            close(fd);
            printf("  创建测试文件 %d: %s\n", i, filename);
        }
    }
    
    // 准备批量异步读取操作
    printf("\n3. 准备批量异步读取操作:\n");
    struct iocb *iocb_ptrs[batch_size];
    
    for (int i = 0; i < batch_size; i++) {
        fd = open(test_files[i], O_RDONLY);
        if (fd == -1) {
            printf("  打开文件 %s 失败\n", test_files[i]);
            continue;
        }
        
        // 初始化iocb
        memset(&iocbs[i], 0, sizeof(iocbs[i]));
        iocbs[i].aio_data = i + 1;  // 使用索引作为用户数据
        iocbs[i].aio_lio_opcode = IOCB_CMD_PREAD;
        iocbs[i].aio_fildes = fd;
        
        // 分配读取缓冲区
        char *read_buffer = malloc(1024);
        if (read_buffer) {
            iocbs[i].aio_buf = (uint64_t)(uintptr_t)read_buffer;
            iocbs[i].aio_nbytes = 1024;
            iocbs[i].aio_offset = 0;
        }
        
        iocb_ptrs[i] = &iocbs[i];
        printf("  准备读取操作 %d: 文件 %s\n", i + 1, test_files[i]);
    }
    
    // 提交批量操作
    printf("\n4. 提交批量异步操作:\n");
    ret = io_submit(ctx, batch_size, iocb_ptrs);
    if (ret < 0) {
        printf("  提交批量操作失败: %s\n", strerror(-ret));
        goto cleanup;
    }
    printf("  ✓ 成功提交 %d 个异步操作\n", ret);
    
    // 演示部分取消
    printf("\n5. 演示部分操作取消:\n");
    int cancel_count = 0;
    
    for (int i = 0; i < batch_size; i += 2) {  // 取消偶数索引的操作
        ret = io_cancel(ctx, &iocbs[i], &results[cancel_count]);
        if (ret == 0) {
            printf("  ✓ 操作 %d 取消成功\n", (int)iocbs[i].aio_data);
            cancel_count++;
        } else if (ret == -EAGAIN) {
            printf("  ℹ 操作 %d 无法取消 (可能已开始执行)\n", (int)iocbs[i].aio_data);
        } else {
            printf("  ✗ 操作 %d 取消失败: %s\n", (int)iocbs[i].aio_data, strerror(-ret));
        }
    }
    
    printf("  总共尝试取消 %d 个操作\n", cancel_count);
    
    // 等待剩余操作完成
    printf("\n6. 等待剩余操作完成:\n");
    struct io_event completed_events[batch_size];
    int completed_count = 0;
    
    // 等待最多5秒
    struct timespec timeout = {5, 0};
    ret = io_getevents(ctx, 1, batch_size, completed_events, &timeout);
    if (ret > 0) {
        printf("  ✓ 收到 %d 个完成事件\n", ret);
        completed_count = ret;
        
        for (int i = 0; i < ret; i++) {
            printf("    操作 %llu 完成: %ld 字节\n", 
                   (unsigned long long)completed_events[i].data,
                   completed_events[i].res);
        }
    } else if (ret == 0) {
        printf("  ⚠ 超时,没有收到完成事件\n");
    } else {
        printf("  ✗ 等待完成事件失败: %s\n", strerror(-ret));
    }
    
    // 显示取消结果
    printf("\n7. 取消结果统计:\n");
    printf("  尝试取消的操作数: %d\n", cancel_count);
    printf("  完成的操作数: %d\n", completed_count);
    printf("  总操作数: %d\n", batch_size);
    
    // 清理资源
cleanup:
    printf("\n8. 清理资源:\n");
    
    // 关闭文件描述符和释放缓冲区
    for (int i = 0; i < batch_size; i++) {
        if (iocbs[i].aio_fildes > 0) {
            close(iocbs[i].aio_fildes);
        }
        if (iocbs[i].aio_buf) {
            free((void*)(uintptr_t)iocbs[i].aio_buf);
        }
        if (test_files[i]) {
            unlink(test_files[i]);
            free(test_files[i]);
        }
    }
    
    io_destroy(ctx);
    printf("  ✓ 所有资源清理完成\n");
    
    return 0;
}

int main() {
    return demo_batch_cancel();
}

示例3:超时取消机制

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 超时取消上下文
 */
typedef struct {
    aio_context_t ctx;
    struct iocb *iocb;
    int is_completed;
    int is_cancelled;
    time_t start_time;
    int timeout_seconds;
} timeout_cancel_ctx_t;

/**
 * 超时处理函数
 */
void timeout_handler(int sig) {
    printf("收到超时信号\n");
}

/**
 * 演示超时取消机制
 */
int demo_timeout_cancel_mechanism() {
    aio_context_t ctx;
    struct iocb iocb;
    struct io_event result;
    int fd;
    int ret;
    
    printf("=== 超时取消机制演示 ===\n");
    
    // 初始化AIO上下文
    printf("1. 初始化AIO上下文:\n");
    ctx = 0;
    ret = io_setup(64, &ctx);
    if (ret < 0) {
        printf("  初始化AIO上下文失败: %s\n", strerror(-ret));
        return -1;
    }
    printf("  ✓ AIO上下文初始化成功\n");
    
    // 创建测试文件
    printf("\n2. 创建测试文件:\n");
    const char *filename = "timeout_test.txt";
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("  创建测试文件失败");
        io_destroy(ctx);
        return -1;
    }
    
    // 写入大量数据
    char *large_data = malloc(10 * 1024 * 1024);  // 10MB数据
    if (large_data) {
        for (int i = 0; i < 10 * 1024 * 1024; i++) {
            large_data[i] = 'A' + (i % 26);
        }
        write(fd, large_data, 10 * 1024 * 1024);
    }
    close(fd);
    
    printf("  ✓ 创建了10MB测试文件: %s\n", filename);
    
    // 重新打开文件进行读取
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("  打开测试文件失败");
        io_destroy(ctx);
        unlink(filename);
        return -1;
    }
    
    // 准备长时间读取操作
    printf("\n3. 准备长时间读取操作:\n");
    char *read_buffer = malloc(5 * 1024 * 1024);  // 5MB缓冲区
    if (!read_buffer) {
        perror("  分配读取缓冲区失败");
        close(fd);
        io_destroy(ctx);
        unlink(filename);
        return -1;
    }
    
    // 初始化iocb
    memset(&iocb, 0, sizeof(iocb));
    iocb.aio_data = 99999;
    iocb.aio_lio_opcode = IOCB_CMD_PREAD;
    iocb.aio_fildes = fd;
    iocb.aio_buf = (uint64_t)(uintptr_t)read_buffer;
    iocb.aio_nbytes = 5 * 1024 * 1024;  // 读取5MB
    iocb.aio_offset = 0;
    
    printf("  准备读取5MB数据\n");
    
    // 提交异步读取操作
    struct iocb *iocbs[1] = {&iocb};
    ret = io_submit(ctx, 1, iocbs);
    if (ret != 1) {
        printf("  提交异步读取操作失败: %s\n", strerror(-ret));
        free(read_buffer);
        close(fd);
        io_destroy(ctx);
        unlink(filename);
        return -1;
    }
    printf("  ✓ 异步读取操作提交成功\n");
    
    // 设置超时处理
    printf("\n4. 设置超时处理:\n");
    time_t start_time = time(NULL);
    const int timeout_seconds = 3;
    
    printf("  设置超时时间: %d 秒\n", timeout_seconds);
    
    // 等待操作完成或超时
    printf("\n5. 等待操作完成或超时:\n");
    struct io_event events[1];
    struct timespec timeout = {1, 0};  // 1秒超时
    
    int operation_completed = 0;
    int cancel_attempted = 0;
    
    while (difftime(time(NULL), start_time) < timeout_seconds) {
        ret = io_getevents(ctx, 0, 1, events, &timeout);
        if (ret > 0) {
            printf("  ✓ 操作在超时前完成\n");
            printf("    读取字节数: %ld\n", events[0].res);
            operation_completed = 1;
            break;
        } else if (ret == 0) {
            printf("  操作仍在进行中...\n");
            
            // 每秒检查一次是否需要取消
            if (!cancel_attempted && difftime(time(NULL), start_time) >= 2) {
                printf("  2秒后尝试取消操作\n");
                struct io_event cancel_result;
                ret = io_cancel(ctx, &iocb, &cancel_result);
                if (ret == 0) {
                    printf("  ✓ 操作取消成功\n");
                    cancel_attempted = 1;
                    break;
                } else if (ret == -EAGAIN) {
                    printf("  ℹ 操作无法取消\n");
                    cancel_attempted = 1;
                } else {
                    printf("  ✗ 取消操作失败: %s\n", strerror(-ret));
                    cancel_attempted = 1;
                }
            }
        } else {
            printf("  ✗ 等待事件失败: %s\n", strerror(-ret));
            break;
        }
    }
    
    // 超时后的处理
    if (!operation_completed && !cancel_attempted) {
        printf("\n6. 超时处理:\n");
        printf("  操作超时 (%d 秒)\n", timeout_seconds);
        
        // 强制取消操作
        printf("  强制取消操作...\n");
        struct io_event cancel_result;
        ret = io_cancel(ctx, &iocb, &cancel_result);
        if (ret == 0) {
            printf("  ✓ 操作取消成功\n");
        } else if (ret == -EAGAIN) {
            printf("  ℹ 操作无法取消\n");
        } else {
            printf("  ✗ 取消操作失败: %s\n", strerror(-ret));
        }
    }
    
    // 等待最终结果
    printf("\n7. 等待最终结果:\n");
    timeout.tv_sec = 2;  // 2秒超时
    timeout.tv_nsec = 0;
    
    ret = io_getevents(ctx, 0, 1, events, &timeout);
    if (ret > 0) {
        printf("  最终结果:\n");
        printf("    用户数据: %llu\n", (unsigned long long)events[0].data);
        printf("    结果: %ld\n", events[0].res);
        if (events[0].res == -ECANCELED) {
            printf("    状态: 操作已取消\n");
        } else if (events[0].res < 0) {
            printf("    错误: %s\n", strerror(-events[0].res));
        } else {
            printf("    成功读取: %ld 字节\n", events[0].res);
        }
    } else if (ret == 0) {
        printf("  超时,无结果返回\n");
    } else {
        printf("  等待结果失败: %s\n", strerror(-ret));
    }
    
    // 清理资源
    printf("\n8. 清理资源:\n");
    free(read_buffer);
    close(fd);
    unlink(filename);
    io_destroy(ctx);
    printf("  ✓ 资源清理完成\n");
    
    return 0;
}

int main() {
    return demo_timeout_cancel_mechanism();
}

示例4:条件取消策略

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 取消策略枚举
 */
typedef enum {
    CANCEL_STRATEGY_IMMEDIATE,    // 立即取消
    CANCEL_STRATEGY_TIMEOUT,     // 超时取消
    CANCEL_STRATEGY_CONDITIONAL, // 条件取消
    CANCEL_STRATEGY_NEVER         // 从不取消
} cancel_strategy_t;

/**
 * 操作上下文
 */
typedef struct {
    struct iocb iocb;
    int is_submitted;
    int is_completed;
    int is_cancelled;
    time_t submit_time;
    cancel_strategy_t strategy;
    int timeout_seconds;
    int condition_value;
} operation_context_t;

/**
 * 条件取消检查函数
 */
int check_cancel_condition(operation_context_t *ctx) {
    switch (ctx->strategy) {
        case CANCEL_STRATEGY_IMMEDIATE:
            return 1;  // 立即取消
            
        case CANCEL_STRATEGY_TIMEOUT:
            if (difftime(time(NULL), ctx->submit_time) > ctx->timeout_seconds) {
                printf("  操作超时,触发取消\n");
                return 1;
            }
            return 0;
            
        case CANCEL_STRATEGY_CONDITIONAL:
            // 模拟条件检查
            if (ctx->condition_value > 100) {
                printf("  条件满足,触发取消\n");
                return 1;
            }
            return 0;
            
        case CANCEL_STRATEGY_NEVER:
        default:
            return 0;
    }
}

/**
 * 演示条件取消策略
 */
int demo_conditional_cancel_strategy() {
    aio_context_t ctx;
    operation_context_t operations[5];
    int fd;
    int ret;
    
    printf("=== 条件取消策略演示 ===\n");
    
    // 初始化AIO上下文
    printf("1. 初始化AIO上下文:\n");
    ctx = 0;
    ret = io_setup(64, &ctx);
    if (ret < 0) {
        printf("  初始化AIO上下文失败: %s\n", strerror(-ret));
        return -1;
    }
    printf("  ✓ AIO上下文初始化成功\n");
    
    // 创建测试文件
    printf("\n2. 创建测试文件:\n");
    const char *filename = "conditional_cancel_test.txt";
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        perror("  创建测试文件失败");
        io_destroy(ctx);
        return -1;
    }
    
    // 写入测试数据
    char *test_data = malloc(1024 * 1024);  // 1MB数据
    if (test_data) {
        for (int i = 0; i < 1024 * 1024; i++) {
            test_data[i] = 'A' + (i % 26);
        }
        write(fd, test_data, 1024 * 1024);
    }
    close(fd);
    free(test_data);
    
    printf("  ✓ 创建了1MB测试文件: %s\n", filename);
    
    // 重新打开文件进行读取
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("  打开测试文件失败");
        io_destroy(ctx);
        unlink(filename);
        return -1;
    }
    
    // 初始化操作上下文
    printf("\n3. 初始化操作上下文:\n");
    for (int i = 0; i < 5; i++) {
        operation_context_t *op_ctx = &operations[i];
        memset(op_ctx, 0, sizeof(*op_ctx));
        
        // 设置不同的取消策略
        switch (i % 4) {
            case 0:
                op_ctx->strategy = CANCEL_STRATEGY_IMMEDIATE;
                printf("  操作 %d: 立即取消策略\n", i + 1);
                break;
            case 1:
                op_ctx->strategy = CANCEL_STRATEGY_TIMEOUT;
                op_ctx->timeout_seconds = 2;  // 2秒超时
                printf("  操作 %d: 超时取消策略 (2秒)\n", i + 1);
                break;
            case 2:
                op_ctx->strategy = CANCEL_STRATEGY_CONDITIONAL;
                op_ctx->condition_value = i * 50;  // 不同的条件值
                printf("  操作 %d: 条件取消策略 (条件值: %d)\n", i + 1, op_ctx->condition_value);
                break;
            case 3:
                op_ctx->strategy = CANCEL_STRATEGY_NEVER;
                printf("  操作 %d: 从不取消策略\n", i + 1);
                break;
        }
    }
    
    // 准备异步读取操作
    printf("\n4. 准备异步读取操作:\n");
    struct iocb *iocb_ptrs[5];
    
    for (int i = 0; i < 5; i++) {
        operation_context_t *op_ctx = &operations[i];
        
        // 初始化iocb
        memset(&op_ctx->iocb, 0, sizeof(op_ctx->iocb));
        op_ctx->iocb.aio_data = i + 1;
        op_ctx->iocb.aio_lio_opcode = IOCB_CMD_PREAD;
        op_ctx->iocb.aio_fildes = fd;
        
        // 分配读取缓冲区
        char *read_buffer = malloc(200 * 1024);  // 200KB缓冲区
        if (read_buffer) {
            op_ctx->iocb.aio_buf = (uint64_t)(uintptr_t)read_buffer;
            op_ctx->iocb.aio_nbytes = 200 * 1024;
            op_ctx->iocb.aio_offset = i * 200 * 1024;  // 不同的偏移量
            
            iocb_ptrs[i] = &op_ctx->iocb;
            op_ctx->is_submitted = 1;
            op_ctx->submit_time = time(NULL);
            
            printf("  准备操作 %d: 读取200KB数据\n", i + 1);
        }
    }
    
    // 提交异步操作
    printf("\n5. 提交异步操作:\n");
    ret = io_submit(ctx, 5, iocb_ptrs);
    if (ret < 0) {
        printf("  提交异步操作失败: %s\n", strerror(-ret));
        goto cleanup;
    }
    printf("  ✓ 成功提交 %d 个异步操作\n", ret);
    
    // 执行取消策略
    printf("\n6. 执行取消策略:\n");
    time_t start_time = time(NULL);
    int completed_operations = 0;
    int cancelled_operations = 0;
    
    // 监控和取消操作
    while (difftime(time(NULL), start_time) < 10) {  // 最多等待10秒
        // 检查取消条件
        for (int i = 0; i < 5; i++) {
            operation_context_t *op_ctx = &operations[i];
            
            if (op_ctx->is_submitted && !op_ctx->is_completed && !op_ctx->is_cancelled) {
                if (check_cancel_condition(op_ctx)) {
                    printf("  尝试取消操作 %d\n", i + 1);
                    
                    struct io_event cancel_result;
                    ret = io_cancel(ctx, &op_ctx->iocb, &cancel_result);
                    if (ret == 0) {
                        printf("    ✓ 操作 %d 取消成功\n", i + 1);
                        op_ctx->is_cancelled = 1;
                        cancelled_operations++;
                    } else if (ret == -EAGAIN) {
                        printf("    ℹ 操作 %d 无法取消\n", i + 1);
                    } else {
                        printf("    ✗ 操作 %d 取消失败: %s\n", i + 1, strerror(-ret));
                    }
                }
            }
        }
        
        // 检查完成事件
        struct io_event events[5];
        struct timespec timeout = {0, 100000000};  // 100ms超时
        
        ret = io_getevents(ctx, 0, 5, events, &timeout);
        if (ret > 0) {
            printf("  收到 %d 个完成事件\n", ret);
            
            for (int i = 0; i < ret; i++) {
                int op_index = events[i].data - 1;
                if (op_index >= 0 && op_index < 5) {
                    operation_context_t *op_ctx = &operations[op_index];
                    op_ctx->is_completed = 1;
                    completed_operations++;
                    
                    printf("    操作 %d 完成: %ld 字节\n", 
                           op_index + 1, events[i].res);
                }
            }
        }
        
        // 检查是否所有操作都已完成或取消
        int all_done = 1;
        for (int i = 0; i < 5; i++) {
            operation_context_t *op_ctx = &operations[i];
            if (op_ctx->is_submitted && !op_ctx->is_completed && !op_ctx->is_cancelled) {
                all_done = 0;
                break;
            }
        }
        
        if (all_done) {
            printf("  所有操作已完成或取消\n");
            break;
        }
        
        // 更新条件值(模拟条件变化)
        for (int i = 0; i < 5; i++) {
            operations[i].condition_value += 10;
        }
        
        usleep(100000);  // 100ms延迟
    }
    
    // 最终统计
    printf("\n7. 最终统计:\n");
    printf("  总操作数: 5\n");
    printf("  完成操作数: %d\n", completed_operations);
    printf("  取消操作数: %d\n", cancelled_operations);
    printf("  进行中操作数: %d\n", 5 - completed_operations - cancelled_operations);
    
    // 显示每个操作的最终状态
    printf("\n8. 操作状态详情:\n");
    for (int i = 0; i < 5; i++) {
        operation_context_t *op_ctx = &operations[i];
        printf("  操作 %d: ", i + 1);
        
        if (op_ctx->is_cancelled) {
            printf("已取消 ");
        } else if (op_ctx->is_completed) {
            printf("已完成 ");
        } else {
            printf("进行中 ");
        }
        
        switch (op_ctx->strategy) {
            case CANCEL_STRATEGY_IMMEDIATE:
                printf("(立即取消策略)");
                break;
            case CANCEL_STRATEGY_TIMEOUT:
                printf("(超时取消策略)");
                break;
            case CANCEL_STRATEGY_CONDITIONAL:
                printf("(条件取消策略)");
                break;
            case CANCEL_STRATEGY_NEVER:
                printf("(从不取消策略)");
                break;
        }
        printf("\n");
    }
    
    // 清理资源
cleanup:
    printf("\n9. 清理资源:\n");
    
    // 释放缓冲区
    for (int i = 0; i < 5; i++) {
        if (operations[i].iocb.aio_buf) {
            free((void*)(uintptr_t)operations[i].iocb.aio_buf);
        }
    }
    
    close(fd);
    unlink(filename);
    io_destroy(ctx);
    printf("  ✓ 资源清理完成\n");
    
    return 0;
}

int main() {
    return demo_conditional_cancel_strategy();
}

示例5:错误处理和恢复

#include <linux/aio_abi.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>

/**
 * 系统调用包装函数
 */
static inline int io_setup(unsigned nr_events, aio_context_t *ctxp) {
    return syscall(__NR_io_setup, nr_events, ctxp);
}

static inline int io_destroy(aio_context_t ctx) {
    return syscall(__NR_io_destroy, ctx);
}

static inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) {
    return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

static inline int io_cancel(aio_context_t ctx, struct iocb *iocb, struct io_event *result) {
    return syscall(__NR_io_cancel, ctx, iocb, result);
}

static inline int io_getevents(aio_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);
}

/**
 * 错误处理上下文
 */
typedef struct {
    int error_code;
    const char *error_message;
    int retry_count;
    int max_retries;
    time_t error_time;
    const char *operation_name;
} error_context_t;

/**
 * 记录错误信息
 */
void record_error(error_context_t *ctx, int error_code, const char *operation, const char *message) {
    ctx->error_code = error_code;
    ctx->error_message = message;
    ctx->error_time = time(NULL);
    ctx->operation_name = operation;
    ctx->retry_count++;
    
    printf("错误记录: %s\n", operation);
    printf("  错误码: %d\n", error_code);
    printf("  错误信息: %s\n", message);
    printf("  重试次数: %d/%d\n", ctx->retry_count, ctx->max_retries);
}

/**
 * 错误恢复策略
 */
int apply_recovery_strategy(error_context_t *ctx, aio_context_t aio_ctx, struct iocb *iocb) {
    printf("应用错误恢复策略:\n");
    
    switch (ctx->error_code) {
        case -EAGAIN:
            printf("  EAGAIN错误,操作稍后重试\n");
            if (ctx->retry_count < ctx->max_retries) {
                printf("  重试操作...\n");
                struct iocb *iocbs[1] = {iocb};
                int ret = io_submit(aio_ctx, 1, iocbs);
                if (ret == 1) {
                    printf("  ✓ 重试成功\n");
                    return 0;
                } else {
                    printf("  ✗ 重试失败: %s\n", strerror(-ret));
                    record_error(ctx, ret, ctx->operation_name, "重试失败");
                }
            }
            break;
            
        case -ECANCELED:
            printf("  ECANCELED错误,操作已被取消\n");
            printf("  尝试重新提交操作...\n");
            struct iocb *iocbs[1] = {iocb};
            int ret = io_submit(aio_ctx, 1, iocbs);
            if (ret == 1) {
                printf("  ✓ 重新提交成功\n");
                return 0;
            } else {
                printf("  ✗ 重新提交失败: %s\n", strerror(-ret));
                record_error(ctx, ret, ctx->operation_name, "重新提交失败");
            }
            break;
            
        case -EBADF:
            printf("  EBADF错误,文件描述符无效\n");
            printf("  建议:检查文件描述符有效性\n");
            break;
            
        case -EINVAL:
            printf("  EINVAL错误,参数无效\n");
            printf("  建议:检查参数设置\n");
            break;
            
        default:
            printf("  未知错误: %s\n", strerror(-ctx->error_code));
            printf("  建议:记录错误并采取适当措施\n");
            break;
    }
    
    return -1;
}

/**
 * 演示错误处理和恢复
 */
int demo_error_handling_recovery() {
    aio_context_t ctx;
    struct iocb iocb;
    struct io_event result;
    int fd;
    int ret;
    error_context_t error_ctx = {0};
    
    printf("=== 错误处理和恢复演示 ===\n");
    
    // 设置错误处理上下文
    error_ctx.max_retries = 3;
    error_ctx.retry_count = 0;
    
    // 初始化AIO上下文
    printf("1. 初始化AIO上下文:\n");
    ctx = 0;
    ret = io_setup(32, &ctx);
    if (ret < 0) {
        record_error(&error_ctx, ret, "io_setup", "初始化AIO上下文失败");
        apply_recovery_strategy(&error_ctx, ctx, NULL);
        return -1;
    }
    printf("  ✓ AIO上下文初始化成功\n");
    
    // 创建测试文件
    printf("\n2. 创建测试文件:\n");
    const char *filename = "error_recovery_test.txt";
    fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if (fd == -1) {
        record_error(&error_ctx, -errno, "open", "创建测试文件失败");
        io_destroy(ctx);
        return -1;
    }
    printf("  ✓ 测试文件创建成功\n");
    
    // 准备异步写入操作
    printf("\n3. 准备异步写入操作:\n");
    const char *test_data = "Error handling and recovery test data.\n";
    size_t data_size = strlen(test_data);
    
    memset(&iocb, 0, sizeof(iocb));
    iocb.aio_data = 1001;
    iocb.aio_lio_opcode = IOCB_CMD_PWRITE;
    iocb.aio_fildes = fd;
    iocb.aio_buf = (uint64_t)(uintptr_t)test_data;
    iocb.aio_nbytes = data_size;
    iocb.aio_offset = 0;
    
    printf("  准备写入操作:\n");
    printf("    数据大小: %zu 字节\n", data_size);
    printf("    用户数据: %llu\n", (unsigned long long)iocb.aio_data);
    
    // 提交异步操作
    printf("\n4. 提交异步操作:\n");
    struct iocb *iocbs[1] = {&iocb};
    ret = io_submit(ctx, 1, iocbs);
    if (ret != 1) {
        record_error(&error_ctx, ret, "io_submit", "提交异步操作失败");
        if (apply_recovery_strategy(&error_ctx, ctx, &iocb) != 0) {
            close(fd);
            unlink(filename);
            io_destroy(ctx);
            return -1;
        }
    }
    printf("  ✓ 异步操作提交成功\n");
    
    // 演示各种错误场景
    printf("\n5. 演示错误场景:\n");
    
    // 场景1: 尝试取消已完成的操作
    printf("  场景1: 尝试取消已完成的操作\n");
    struct io_event events[1];
    struct timespec timeout = {2, 0};  // 2秒超时
    
    ret = io_getevents(ctx, 1, 1, events, &timeout);
    if (ret > 0) {
        printf("    操作已完成,尝试取消...\n");
        ret = io_cancel(ctx, &iocb, &result);
        if (ret == -EAGAIN) {
            printf("    ✓ 取消失败:操作已完成 (EAGAIN)\n");
        } else if (ret == 0) {
            printf("    ✓ 操作取消成功\n");
        } else {
            printf("    取消操作返回: %s\n", strerror(-ret));
        }
    }
    
    // 场景2: 使用无效的iocb尝试取消
    printf("\n  场景2: 使用无效的iocb尝试取消\n");
    struct iocb invalid_iocb;
    memset(&invalid_iocb, 0, sizeof(invalid_iocb));
    invalid_iocb.aio_data = 9999;
    
    ret = io_cancel(ctx, &invalid_iocb, &result);
    if (ret == -EAGAIN) {
        printf("    ✓ 取消失败:无效操作 (EAGAIN)\n");
    } else {
        printf("    取消操作返回: %s\n", strerror(-ret));
    }
    
    // 场景3: 使用无效的上下文ID
    printf("\n  场景3: 使用无效的上下文ID\n");
    struct iocb test_iocb;
    memset(&test_iocb, 0, sizeof(test_iocb));
    
    aio_context_t invalid_ctx = (aio_context_t)-1;
    ret = io_cancel(invalid_ctx, &test_iocb, &result);
    if (ret == -EINVAL) {
        printf("    ✓ 操作失败:无效上下文ID (EINVAL)\n");
    } else {
        printf("    取消操作返回: %s\n", strerror(-ret));
    }
    
    // 场景4: 重复取消同一个操作
    printf("\n  场景4: 重复取消操作\n");
    ret = io_cancel(ctx, &iocb, &result);
    if (ret == -EAGAIN) {
        printf("    ✓ 第二次取消失败:操作已处理 (EAGAIN)\n");
    } else if (ret == 0) {
        printf("    ✓ 第二次取消成功\n");
        ret = io_cancel(ctx, &iocb, &result);
        if (ret == -EAGAIN) {
            printf("    ✓ 第三次取消失败:操作已处理 (EAGAIN)\n");
        }
    } else {
        printf("    第二次取消操作返回: %s\n", strerror(-ret));
    }
    
    // 演示资源清理错误处理
    printf("\n6. 演示资源清理错误处理:\n");
    
    // 正常关闭文件
    printf("  正常关闭文件:\n");
    if (close(fd) == 0) {
        printf("    ✓ 文件关闭成功\n");
    } else {
        record_error(&error_ctx, -errno, "close", "关闭文件失败");
        printf("    尝试强制清理...\n");
    }
    
    // 删除测试文件
    printf("  删除测试文件:\n");
    if (unlink(filename) == 0) {
        printf("    ✓ 测试文件删除成功\n");
    } else {
        record_error(&error_ctx, -errno, "unlink", "删除测试文件失败");
        printf("    注意:可能需要手动清理测试文件\n");
    }
    
    // 销毁AIO上下文
    printf("  销毁AIO上下文:\n");
    ret = io_destroy(ctx);
    if (ret == 0) {
        printf("    ✓ AIO上下文销毁成功\n");
    } else {
        record_error(&error_ctx, ret, "io_destroy", "销毁AIO上下文失败");
        printf("    销毁操作返回: %s\n", strerror(-ret));
    }
    
    // 显示错误处理总结
    printf("\n=== 错误处理总结 ===\n");
    printf("1. 常见错误类型:\n");
    printf("   ✓ EAGAIN: 操作无法取消(已完成或不存在)\n");
    printf("   ✓ EINVAL: 参数无效\n");
    printf("   ✓ EBADF: 文件描述符无效\n");
    printf("   ✓ ECANCELED: 操作已被取消\n");
    
    printf("\n2. 错误处理策略:\n");
    printf("   ✓ 记录错误信息\n");
    printf("   ✓ 根据错误类型采取不同措施\n");
    printf("   ✓ 必要时重试操作\n");
    printf("   ✓ 优雅降级处理\n");
    
    printf("\n3. 恢复策略:\n");
    printf("   ✓ 重试机制\n");
    printf("   ✓ 备用方案\n");
    printf("   ✓ 资源清理\n");
    printf("   ✓ 状态恢复\n");
    
    printf("\n4. 最佳实践:\n");
    printf("   ✓ 完善的错误记录\n");
    printf("   ✓ 适当的重试策略\n");
    printf("   ✓ 资源泄漏预防\n");
    printf("   ✓ 用户友好提示\n");
    
    return 0;
}

int main() {
    return demo_error_handling_recovery();
}

io_cancel 使用注意事项

系统要求:

  1. 内核版本: 需要支持AIO的Linux内核
  2. 权限要求: 通常不需要特殊权限
  3. 架构支持: 支持所有主流架构

取消限制:

  1. 操作状态: 只能取消排队或正在执行的操作
  2. 时间窗口: 已完成的操作无法取消
  3. 资源释放: 取消后需要清理相关资源

错误处理:

  1. EAGAIN: 操作无法取消(已完成或不存在)
  2. EINVAL: 参数无效
  3. EBADF: 文件描述符无效
  4. ECANCELED: 操作已被取消

性能考虑:

  1. 取消开销: 取消操作本身也有性能开销
  2. 批量处理: 批量取消可能更高效
  3. 超时设置: 合理设置超时时间
  4. 状态检查: 取消前检查操作状态

安全考虑:

  1. 参数验证: 验证所有输入参数
  2. 内存管理: 确保缓冲区内存有效
  3. 资源清理: 及时清理已取消操作的资源
  4. 状态同步: 确保多线程环境下的状态一致性

最佳实践:

  1. 及时取消: 不需要的操作及时取消
  2. 状态跟踪: 跟踪每个操作的状态
  3. 错误处理: 妥善处理取消失败的情况
  4. 资源管理: 确保资源得到正确释放
  5. 超时机制: 实现合理的超时取消机制

io_cancel vs 相似函数对比

io_cancel vs io_destroy:

// io_cancel: 取消单个操作
io_cancel(ctx, &iocb, &result);

// io_destroy: 销毁整个AIO上下文(取消所有操作)
io_destroy(ctx);

io_cancel vs io_getevents:

// io_cancel: 主动取消操作
io_cancel(ctx, &iocb, &result);

// io_getevents: 被动等待操作完成
io_getevents(ctx, 1, 1, events, &timeout);

常见使用场景

1. 超时处理:

// 异步操作超时后取消
if (operation_timeout) {
    io_cancel(ctx, &iocb, &result);
}

2. 用户中断:

// 用户取消操作时取消所有相关异步操作
for (int i = 0; i < operation_count; i++) {
    io_cancel(ctx, &iocbs[i], &results[i]);
}

3. 资源清理:

// 应用程序退出前取消未完成的操作
io_cancel(ctx, &iocb, &result);

总结

io_cancel 是Linux AIO框架中重要的操作取消函数,提供了:

  1. 操作控制: 精确控制异步操作的生命周期
  2. 资源管理: 及时释放不需要的资源
  3. 错误处理: 完善的错误处理机制
  4. 灵活性: 支持多种取消策略

通过合理使用 io_cancel,可以构建更加健壮和灵活的异步I/O应用。在实际应用中,需要注意错误处理、资源管理和性能优化等关键问题。

发表在 linux文章 | 留下评论

access系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 access 函数,它用于检查调用进程是否对指定的文件路径具有特定的访问权限(如读、写、执行)或检查文件是否存在。


1. 函数介绍

access 是一个 Linux 系统调用,用于根据调用进程的实际用户 ID (UID) 和组 ID (GID) 来检查对文件的权限。它回答了这样的问题:“我(当前运行这个程序的用户)能否读/写/执行这个文件?” 或者更简单地,“这个文件存在吗?”。

这在程序需要在尝试打开或执行文件之前,先确认是否具备相应权限时非常有用,可以避免因权限不足而导致后续操作(如 openexecve)失败。

需要注意的是,access 检查的是调用 access 时的实际权限,即使程序后续通过 setuid 或 setgid 改变了有效用户 ID 或组 ID,access 仍然基于最初的 UID/GID 进行检查。


2. 函数原型

#include <unistd.h> // 必需

int access(const char *pathname, int mode);

3. 功能

  • 权限检查: 检查调用进程对由 pathname 指定的文件是否拥有 mode 参数指定的访问权限。
  • 存在性检查: 特别地,当 mode 设置为 F_OK 时,access 仅检查文件是否存在,而不关心具体的读/写/执行权限。

4. 参数

  • const char *pathname: 指向一个以空字符 (\0) 结尾的字符串,该字符串包含了要检查权限的文件或目录的路径名。这可以是相对路径或绝对路径。
  • int mode: 指定要检查的权限类型。这是一个位掩码,可以是以下值的按位或组合:
    • F_OK: 检查文件是否存在。
    • R_OK: 检查文件是否可读。
    • W_OK: 检查文件是否可写。
    • X_OK: 检查文件是否可执行。
      例如:
    • F_OK: 仅检查文件是否存在。
    • R_OK: 检查文件是否可读。
    • R_OK | W_OK: 检查文件是否可读且可写。
    • X_OK: 检查文件(或目录)是否可执行(对于目录,可执行意味着可以进入该目录)。

5. 返回值

  • 成功时 (具备指定权限或文件存在): 返回 0。
  • 失败时 (不具备指定权限或文件不存在):
    • 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
      • EACCES: 请求的权限被拒绝。文件存在,但调用进程没有指定的权限。
      • ENOENT: 文件不存在(或路径名指向的目录不存在)。
      • ELOOP: 解析 pathname 时遇到符号链接环。
      • 其他错误…

6. 相似函数,或关联函数

  • statlstatfstat: 这些函数可以获取文件的详细状态信息,包括权限位 (st_mode)。程序可以手动检查这些权限位来判断权限,但这需要自己实现权限检查逻辑(考虑用户、组、其他用户的权限位以及 UID/GID)。access 提供了更直接、符合系统安全策略的检查方式。
  • openexecve 等: 这些函数在执行时也会进行权限检查。使用 access 可以提前检查,但需要注意“检查与使用之间存在竞争条件 (TOCTOU)”的问题(见下方注意事项)。
  • euidaccess / eaccess: 这些是 GNU 扩展函数,它们根据有效用户 ID (EUID) 和有效组 ID (EGID) 进行检查,而不是实际用户 ID。在 setuid/setgid 程序中可能更有意义。

7. 示例代码

示例 1:基本的文件存在性和权限检查

这个例子演示了如何使用 access 检查文件是否存在、是否可读、是否可写、是否可执行。

#include <unistd.h>  // access
#include <stdio.h>   // perror, printf
#include <stdlib.h>  // exit

void check_access(const char *pathname) {
    printf("\n--- Checking access for '%s' ---\n", pathname);

    // 1. 检查文件是否存在
    if (access(pathname, F_OK) == 0) {
        printf("  File exists.\n");
    } else {
        if (errno == ENOENT) {
            printf("  File does NOT exist.\n");
        } else {
            perror("  access F_OK failed for other reason");
        }
        // 如果文件不存在,后续检查无意义,但为了演示,我们仍进行
        // (实际上,通常会在这里 return)
    }

    // 2. 检查是否可读
    if (access(pathname, R_OK) == 0) {
        printf("  File is readable.\n");
    } else {
        if (errno == EACCES) {
            printf("  File exists but is NOT readable.\n");
        } else if (errno == ENOENT) {
            printf("  File does not exist (so not readable).\n");
        } else {
            perror("  access R_OK failed for other reason");
        }
    }

    // 3. 检查是否可写
    if (access(pathname, W_OK) == 0) {
        printf("  File is writable.\n");
    } else {
        if (errno == EACCES) {
            printf("  File exists but is NOT writable.\n");
        } else if (errno == ENOENT) {
            printf("  File does not exist (so not writable).\n");
        } else {
            perror("  access W_OK failed for other reason");
        }
    }

    // 4. 检查是否可执行
    if (access(pathname, X_OK) == 0) {
        printf("  File is executable.\n");
    } else {
        if (errno == EACCES) {
            printf("  File exists but is NOT executable.\n");
        } else if (errno == ENOENT) {
            printf("  File does not exist (so not executable).\n");
        } else {
            perror("  access X_OK failed for other reason");
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <file1> [file2] ...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 对每个命令行参数进行检查
    for (int i = 1; i < argc; i++) {
        check_access(argv[i]);
    }

    return 0;
}

代码解释:

  1. 定义了一个 check_access 函数,它接受一个文件路径作为参数。
  2. 在 check_access 函数内部:
    • 首先调用 access(pathname, F_OK) 检查文件是否存在。
    • 然后分别调用 access(pathname, R_OK)access(pathname, W_OK)access(pathname, X_OK) 检查读、写、执行权限。
    • 每次调用后都检查返回值。如果返回 0,表示检查通过;如果返回 -1,则检查 errno 来区分是“文件不存在”还是“权限不足”等其他原因。
  3. main 函数遍历所有命令行参数,并对每个参数调用 check_access

编译和运行:

gcc -o check_access check_access.c
touch test_file
chmod 644 test_file # rw-r--r--
chmod 755 test_script.sh # 创建一个可执行脚本用于测试
echo '#!/bin/bash\necho "Hello from script"' > test_script.sh
chmod +x test_script.sh

./check_access test_file test_script.sh /etc/passwd /nonexistent_file

示例 2:在打开文件前进行检查

这个例子展示了如何在尝试打开文件进行写入之前,先使用 access 检查文件是否存在以及是否可写,以提供更友好的错误信息。

#include <unistd.h>  // access
#include <fcntl.h>   // open, O_WRONLY, O_CREAT, O_EXCL
#include <stdio.h>   // perror, printf
#include <stdlib.h>  // exit

int safe_write_file(const char *pathname, const char *data) {
    int fd;

    // 1. 检查文件是否存在
    if (access(pathname, F_OK) == 0) {
        printf("File '%s' already exists.\n", pathname);

        // 2. 如果存在,检查是否可写
        if (access(pathname, W_OK) != 0) {
            if (errno == EACCES) {
                fprintf(stderr, "Error: Permission denied. Cannot write to '%s'.\n", pathname);
            } else {
                perror("Error checking write permission");
            }
            return -1; // Failure
        }
        printf("File exists and is writable.\n");
        // 注意:即使可写,open 时仍可能因为其他原因失败(如磁盘满)

    } else {
        // 文件不存在,检查目录是否可写 (间接判断能否创建文件)
        // 这里简化处理,实际可能需要解析路径
        printf("File '%s' does not exist. Checking if we can create it...\n", pathname);
        // 一个简单的检查:检查当前目录是否可写
        if (access(".", W_OK) != 0) {
             if (errno == EACCES) {
                 fprintf(stderr, "Error: Permission denied. Cannot create file in current directory.\n");
             } else {
                 perror("Error checking current directory write permission");
             }
             return -1;
        }
        printf("Current directory is writable. Proceeding to create file.\n");
    }

    // 3. 尝试打开文件进行写入
    // 使用 O_CREAT | O_EXCL 确保仅在文件不存在时创建,防止覆盖
    // 如果前面检查了存在性,这里可能用 O_WRONLY | O_TRUNC 更合适
    // 这里演示结合检查的逻辑
    if (access(pathname, F_OK) == 0) {
        // 文件存在,以只写和截断模式打开
        fd = open(pathname, O_WRONLY | O_TRUNC);
    } else {
        // 文件不存在,创建它
        fd = open(pathname, O_WRONLY | O_CREAT | O_EXCL, 0644);
    }

    if (fd == -1) {
        perror("open");
        return -1; // Failure
    }

    printf("File '%s' opened successfully for writing.\n", pathname);

    // 4. 写入数据 (简化)
    ssize_t data_len = 0;
    const char *p = data;
    while (*p++) data_len++;
    
    if (write(fd, data, data_len) != data_len) {
        perror("write");
        close(fd);
        return -1;
    }

    printf("Successfully wrote data to '%s'.\n", pathname);

    // 5. 关闭文件
    if (close(fd) == -1) {
        perror("close");
        return -1;
    }

    return 0; // Success
}

int main() {
    const char *filename = "output_from_safe_write.txt";
    const char *content = "This is data written by the safe_write_file function.\n";

    if (safe_write_file(filename, content) == 0) {
        printf("Operation completed successfully.\n");
    } else {
        printf("Operation failed.\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

代码解释:

  1. 定义了一个 safe_write_file 函数,它接受文件名和要写入的数据。
  2. 首先使用 access(pathname, F_OK) 检查文件是否存在。
  3. 如果文件存在,再使用 access(pathname, W_OK) 检查是否可写。
  4. 如果文件不存在,则检查当前工作目录(.)是否可写,以此判断是否有权限创建新文件(这是一个简化的检查)。
  5. 根据检查结果,决定是以 O_WRONLY | O_TRUNC(覆盖)还是 O_WRONLY | O_CREAT | O_EXCL(新建)模式打开文件。
  6. 打开文件后,执行写入操作。
  7. 最后关闭文件。
  8. 通过这种方式,可以在实际执行可能导致失败的操作(openwrite)之前,提供更具体、更早的错误反馈。

重要注意事项:TOCTOU 竞争条件

使用 access 时需要特别注意一个潜在的安全问题:TOCTOU (Time-of-Check to Time-of-Use) 竞争条件

  • 问题access 检查权限和后续使用文件(如 openexecve)之间存在时间差。在这段时间内,文件的权限或存在性可能被其他进程改变。
  • 例子: 一个程序用 access("myfile", W_OK) 检查 myfile 是否可写,返回 0(表示可写)。但在程序调用 open("myfile", O_WRONLY) 之前,另一个有权限的进程删除了 myfile 并创建了一个指向敏感文件(如 /etc/passwd)的符号链接,并命名为 myfile。此时,程序的 open 调用将会打开并可能修改 /etc/passwd,这显然不是预期行为。
  • 缓解方法:
    1. 尽量避免使用 access: 最好的方法是直接尝试执行操作(如 openexecve),并根据其返回的错误码来处理权限或存在性问题。内核会在 open/execve 时进行原子性的权限检查。
    2. 如果必须使用 access: 要意识到这种风险,并确保在权限检查和文件使用之间的时间窗口尽可能短。在高安全性要求的程序中,应避免依赖 access 的结果来做关键决策。

总结:

access 函数提供了一种方便的方式来检查文件权限和存在性。虽然它有其用途,但在涉及安全性的场景中,直接尝试操作并处理错误通常是更安全、更可靠的做法。理解其工作原理和潜在的 TOCTOU 问题是正确使用它的关键。

发表在 linux文章 | 留下评论

accept系统调用及示例

我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 accept 函数,它是 TCP 服务器用来接受客户端连接请求的核心系统调用。


1. 函数介绍

accept 是一个 Linux 系统调用,专门用于TCP 服务器(使用 SOCK_STREAM 套接字)。它的主要功能是从监听套接字(通过 listen 设置的套接字)的未决连接队列(pending connection queue)中取出第一个连接请求,并为这个新连接创建一个全新的、独立的套接字文件描述符

你可以把 accept 想象成总机接线员

  1. 有很多电话(客户端连接请求)打进来,响铃并排队在总机(监听套接字)那里。
  2. 接线员(accept 调用)拿起一个响铃的电话。
  3. 接线员把这条线路接到一个新的、专用的电话线(新的套接字文件描述符)上。
  4. 接线员可以继续去接下一个电话(下一次 accept 调用),而第一个通话(与第一个客户端的通信)则通过那条专用线路进行,互不干扰。

这个新创建的套接字文件描述符专门用于与那一个特定的客户端进行双向数据通信。原始的监听套接字则继续保持监听状态,等待并接受更多的连接请求。


2. 函数原型

#include <sys/socket.h> // 必需

// 标准形式
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 带有标志的变体 (Linux 2.6.28+)
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

3. 功能

  • 从队列中取出连接: 从监听套接字 sockfd 维护的未决连接队列中提取第一个已完成或正在完成的连接请求。
  • 创建新套接字: 为这个新连接创建一个新的、非监听状态的套接字文件描述符。
  • 返回通信端点: 返回这个新的套接字文件描述符,服务器程序可以使用它来与特定的客户端进行数据交换(read/write)。
  • 获取客户端信息: 如果 addr 和 addrlen 参数不为 NULL,则将连接到服务器的客户端的地址信息(IP 地址和端口号)填充到 addr 指向的缓冲区中。

4. 参数

  • int sockfd: 这是监听套接字的文件描述符。它必须是:
    1. 通过 socket() 成功创建的。
    2. 通过 bind() 绑定了本地地址(IP 和端口)的。
    3. 通过 listen() 进入监听状态的。
  • struct sockaddr *addr: 这是一个指向套接字地址结构的指针,用于接收客户端的地址信息。
    • 如果你不关心客户端是谁,可以传入 NULL
    • 如果传入非 NULL 值,则它通常指向一个 struct sockaddr_in (IPv4) 或 struct sockaddr_in6 (IPv6) 类型的变量。
    • 该结构体在 accept 返回后会被填入客户端的地址信息。
  • socklen_t *addrlen: 这是一个指向 socklen_t 类型变量的指针。
    • 输入: 在调用 accept 时,这个变量必须被初始化为 addr 指向的缓冲区的大小(以字节为单位)。例如,如果 addr 指向 struct sockaddr_in,则 *addrlen 应初始化为 sizeof(struct sockaddr_in)
    • 输出accept 返回时,这个变量会被更新为实际存储在 addr 中的地址结构的大小。这对于处理不同大小的地址结构(如 IPv4 和 IPv6)很有用。
  • int flags (accept4 特有): 这个参数允许在创建新套接字时设置一些属性,类似于 socket() 的 type 参数可以使用的修饰符。
    • SOCK_NONBLOCK: 将新创建的套接字设置为非阻塞模式。
    • SOCK_CLOEXEC: 在调用 exec() 时自动关闭该套接字。

5. 返回值

  • 成功时: 返回一个新的、非负的整数,即为新连接创建的套接字文件描述符。服务器应使用这个返回的文件描述符与客户端进行后续的数据通信。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因(例如 EAGAIN 或 EWOULDBLOCK 套接字被标记为非阻塞且没有未决连接,EBADF sockfd 无效,EINVAL 套接字未监听,EMFILE 进程打开的文件描述符已达上限等)。

阻塞与非阻塞:

  • 阻塞套接字(默认):如果监听队列中没有待处理的连接,accept 调用会阻塞(挂起)当前进程,直到有新的连接到达。
  • 非阻塞套接字(如果监听套接字被设置为非阻塞):如果监听队列中没有待处理的连接,accept 会立即返回 -1,并将 errno 设置为 EAGAIN 或 EWOULDBLOCK

6. 相似函数,或关联函数

  • socket: 用于创建原始的监听套接字。
  • bind: 将监听套接字绑定到本地地址。
  • listen: 使套接字进入监听状态,开始接收连接请求。
  • connect: 客户端使用此函数向服务器发起连接。
  • close: 服务器在与客户端通信结束后,需要关闭 accept 返回的那个套接字文件描述符。通常也需要关闭原始的监听套接字(在服务器退出时)。
  • fork / 多线程: 服务器通常在 accept 之后调用 fork 或创建新线程来处理与客户端的通信,以便主服务器进程可以继续调用 accept 接受新的连接。

7. 示例代码

示例 1:基本的 TCP 服务器 accept 循环

这个例子演示了一个典型的、顺序处理的 TCP 服务器如何使用 accept 循环来接受和处理客户端连接。

// sequential_tcp_server.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h> // inet_ntoa (注意:不是线程安全的)

#define PORT 8080
#define BACKLOG 10

void handle_client(int client_fd, struct sockaddr_in *client_addr) {
    char buffer[1024];
    ssize_t bytes_read;

    printf("Handling client %s:%d (fd: %d)\n",
           inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), client_fd);

    // 读取客户端发送的数据
    while ((bytes_read = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {
        buffer[bytes_read] = '\0'; // 确保字符串结束
        printf("Received from client: %s", buffer); // buffer 可能已包含 \n

        // 将收到的数据回显给客户端
        if (write(client_fd, buffer, bytes_read) != bytes_read) {
            perror("write to client failed");
            break;
        }
    }

    if (bytes_read < 0) {
        perror("read from client failed");
    } else {
        printf("Client %s:%d disconnected (fd: %d)\n",
               inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), client_fd);
    }

    close(client_fd); // 关闭与该客户端的连接
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_address;
    socklen_t client_addr_len = sizeof(client_address);
    int opt = 1;

    // 1. 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 配置服务器地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 4. 绑定套接字
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 5. 监听连接
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 6. 主循环:接受并处理连接
    while (1) {
        printf("Waiting for a connection...\n");

        // 7. 接受连接 (阻塞调用)
        client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
        if (client_fd < 0) {
            perror("accept failed");
            continue; // 或 exit(EXIT_FAILURE);
        }

        printf("New connection accepted.\n");

        // 8. 处理客户端 (顺序处理,同一时间只能处理一个)
        handle_client(client_fd, &client_address);

        // 处理完一个客户端后,循环继续 accept 下一个
    }

    // 注意:在实际程序中,需要有退出机制和清理代码
    // close(server_fd); // 不会执行到这里
    return 0;
}

代码解释:

  1. 创建、绑定、监听服务器套接字,这部分与之前 socketbindlisten 的例子相同。
  2. 进入一个无限的 while(1) 循环。
  3. 在循环内部,调用 accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len)
    • server_fd: 监听套接字。
    • &client_address: 指向 sockaddr_in 结构的指针,用于接收客户端地址。
    • &client_addr_len: 指向 socklen_t 变量的指针,该变量在调用前被初始化为 sizeof(client_address)
  4. accept 是一个阻塞调用。如果没有客户端连接,程序会在此处挂起等待。
  5. 当有客户端连接到达时,accept 返回一个新的文件描述符 client_fd
  6. 调用 handle_client 函数处理与该客户端的通信。这个函数会读取客户端数据并回显回去。
  7. handle_client 函数结束时(客户端断开或出错),会调用 close(client_fd) 关闭这个连接。
  8. 主循环继续,再次调用 accept 等待下一个客户端。

缺点: 这种顺序处理的方式效率很低。服务器在处理一个客户端时,无法接受其他客户端的连接,直到当前客户端处理完毕。

示例 2:并发 TCP 服务器 (使用 fork)

这个例子演示了如何使用 fork 创建子进程来并发处理多个客户端连接。

// concurrent_tcp_server.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/wait.h> // waitpid

#define PORT 8080
#define BACKLOG 10

void handle_client(int client_fd, struct sockaddr_in *client_addr) {
    char buffer[1024];
    ssize_t bytes_read;

    printf("Child %d: Handling client %s:%d (fd: %d)\n",
           getpid(), inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), client_fd);

    while ((bytes_read = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {
        buffer[bytes_read] = '\0';
        printf("Child %d: Received from client: %s", getpid(), buffer);
        if (write(client_fd, buffer, bytes_read) != bytes_read) {
            perror("Child: write to client failed");
            break;
        }
    }

    if (bytes_read < 0) {
        perror("Child: read from client failed");
    } else {
        printf("Child %d: Client %s:%d disconnected.\n",
               getpid(), inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port));
    }

    close(client_fd);
    printf("Child %d: Connection closed. Exiting.\n", getpid());
    _exit(EXIT_SUCCESS); // 子进程使用 _exit 退出
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_address;
    socklen_t client_addr_len = sizeof(client_address);
    int opt = 1;
    pid_t pid;

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Concurrent Server (PID: %d) listening on port %d\n", getpid(), PORT);

    while (1) {
        client_fd = accept(server_fd, (struct sockaddr *)&client_address, &client_addr_len);
        if (client_fd < 0) {
            perror("accept failed");
            continue;
        }

        printf("Main process (PID: %d): New connection accepted.\n", getpid());

        // Fork a new process to handle the client
        pid = fork();
        if (pid < 0) {
            perror("fork failed");
            close(client_fd); // Important: close the client fd on fork failure
        } else if (pid == 0) {
            // --- Child process ---
            close(server_fd); // Child doesn't need the listening socket
            handle_client(client_fd, &client_address);
            // handle_client calls close(client_fd) and _exit()
            // so nothing more needed here
        } else {
            // --- Parent process ---
            close(client_fd); // Parent doesn't need the client-specific socket
            printf("Main process (PID: %d): Forked child process (PID: %d) to handle client.\n", getpid(), pid);

            // Optional: Clean up any finished child processes (non-blocking)
            // This prevents zombie processes if children finish quickly
            pid_t wpid;
            int status;
            while ((wpid = waitpid(-1, &status, WNOHANG)) > 0) {
                printf("Main process (PID: %d): Reaped child process (PID: %d)\n", getpid(), wpid);
            }
        }
    }

    close(server_fd);
    return 0;
}

代码解释:

  1. 服务器设置部分与顺序服务器相同。
  2. 在 accept 成功返回后,立即调用 fork()
  3. fork 返回后
    • 子进程 (pid == 0):
      • 关闭不需要的监听套接字 server_fd
      • 调用 handle_client(client_fd, ...) 处理客户端。
      • handle_client 处理完毕后会关闭 client_fd 并调用 _exit() 退出。
    • 父进程 (pid > 0):
      • 关闭不需要的客户端套接字 client_fd(因为子进程在处理它)。
      • 打印信息,表明已派生子进程处理客户端。
      • 可选地调用 waitpid(-1, &status, WNOHANG) 来非阻塞地清理已经结束的子进程(回收僵尸进程)。如果省略这一步,结束的子进程会变成僵尸进程,直到父进程退出。
  4. 父进程继续循环,调用 accept 等待下一个客户端连接。

示例 3:使用 accept4 设置非阻塞客户端套接字

这个例子演示了如何使用 accept4 函数在创建新连接套接字的同时就将其设置为非阻塞模式。

// accept4_example.c
#define _GNU_SOURCE // 必须定义以使用 accept4
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> // F_GETFL, F_SETFL, O_NONBLOCK

#define PORT 8080
#define BACKLOG 10

int main() {
    int server_fd, client_fd;
    struct sockaddr_in address, client_address;
    socklen_t client_addr_len = sizeof(client_address);
    int opt = 1;
    int flags;

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d. Accepting connections...\n", PORT);

    while (1) {
        printf("Waiting for a connection...\n");

        // 使用 accept4 直接创建非阻塞的客户端套接字
        client_fd = accept4(server_fd, (struct sockaddr *)&client_address, &client_addr_len, SOCK_NONBLOCK);

        if (client_fd < 0) {
            perror("accept4 failed");
            continue;
        }

        printf("New connection accepted (fd: %d). Checking if it's non-blocking...\n", client_fd);

        // 验证套接字是否确实是非阻塞的
        flags = fcntl(client_fd, F_GETFL, 0);
        if (flags == -1) {
            perror("fcntl F_GETFL failed");
            close(client_fd);
            continue;
        }

        if (flags & O_NONBLOCK) {
            printf("Confirmed: Client socket (fd: %d) is non-blocking.\n", client_fd);
        } else {
            printf("Warning: Client socket (fd: %d) is NOT non-blocking.\n", client_fd);
        }

        // --- 在这里,你可以对非阻塞的 client_fd 进行 read/write/select/poll 操作 ---
        // 例如,将其添加到 epoll 或 select 的监视集合中

        // 为了演示,我们简单地关闭它
        printf("Closing client socket (fd: %d).\n", client_fd);
        close(client_fd);
    }

    close(server_fd);
    return 0;
}

代码解释:

  1. 服务器设置部分与之前相同。
  2. 在调用 accept4 时,传入了 SOCK_NONBLOCK 标志作为第四个参数。
  3. 如果 accept4 成功,返回的 client_fd 就已经被设置为非阻塞模式。
  4. 代码通过 fcntl(client_fd, F_GETFL, 0) 获取套接字标志,并检查 O_NONBLOCK 位是否被设置,以验证 accept4 的效果。
  5. 在实际应用中,得到非阻塞的 client_fd 后,通常会将其加入到 selectpoll 或 epoll 的监视集合中,以便高效地管理多个并发连接。

重要提示与注意事项:

  1. 返回新的文件描述符accept 返回的文件描述符与原始监听套接字 sockfd 完全不同。原始套接字继续用于监听,新套接字用于与特定客户端通信。
  2. 必须关闭: 服务器在与客户端通信结束后,必须调用 close() 关闭 accept 返回的那个文件描述符,以释放资源。
  3. 获取客户端地址: 利用 addr 和 addrlen 参数获取客户端的 IP 和端口对于日志记录、访问控制、调试等非常有用。
  4. 并发处理: 对于需要同时处理多个客户端的服务器,必须使用 fork、多线程或 I/O 多路复用(select/poll/epoll)等技术。简单的顺序处理无法满足实际需求。
  5. 错误处理: 始终检查 accept 的返回值。在繁忙的服务器上,非阻塞 accept 可能会因为没有连接而返回 EAGAIN
  6. accept4 的优势accept4 可以在原子操作中设置新套接字的属性,避免了先 accept 再 fcntl 的两步操作,理论上更高效且没有竞态条件。

总结:

accept 是 TCP 服务器模型的核心。它使得服务器能够从监听状态进入与客户端的实际数据交换状态。理解其阻塞/非阻塞行为、返回值含义以及如何与并发处理技术(如 fork)结合使用,是构建健壮网络服务器的基础。accept4 则为需要精细控制新连接套接字属性的场景提供了便利。

发表在 linux文章 | 留下评论

accept4系统调用及示例

1. 函数介绍

在网络编程中,服务器程序通常需要监听某个端口,等待客户端的连接请求。当一个客户端尝试连接到服务器时,内核会将这个连接请求放入一个等待队列中。

服务器程序需要一种方法从这个队列中取出(“接受”)一个连接请求,并为这个连接创建一个新的套接字(socket),通过这个新套接字与客户端进行数据通信。

accept 系统调用就是用来完成这个“接受连接”的任务的。它会阻塞(等待)直到队列中有新的连接请求,然后返回一个新的、已连接的套接字文件描述符。

accept4 是 accept 的一个扩展版本。它在功能上与 accept 几乎相同,但增加了一个非常实用的特性:允许你在接受连接的同时,为新创建的套接字文件描述符设置一些标志(flags)。

最常见的用途是设置 SOCK_CLOEXEC 标志,这可以自动防止新套接字在执行 exec() 系列函数时被意外地传递给新程序,从而提高了程序的安全性和健壮性。

简单来说,accept4 就是 accept 的“增强版”,它让你在接到电话(连接)的同时,可以立刻给电话线(套接字)加上一些安全或便利的设置。

2. 函数原型

#define _GNU_SOURCE // 必须定义这个宏才能使用 accept4
#include <sys/socket.h> // 包含 accept4 函数声明

// accept4 是 Linux 特有的系统调用
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

注意accept4 是 Linux 特有的。在可移植的 POSIX 代码中,通常使用标准的 accept,然后手动调用 fcntl 来设置标志。

3. 功能

从监听套接字 sockfd 的已完成连接队列(completed connection queue)中取出第一个连接请求,为这个连接创建一个新的、已连接的套接字,并根据 flags 参数设置该新套接字的属性。

4. 参数

  • sockfd:
    • int 类型。
    • 一个监听套接字的文件描述符。这个套接字必须已经通过 bind() 绑定了本地地址和端口,并通过 listen() 开始监听连接请求。
  • addr:
    • struct sockaddr * 类型。
    • 一个指向 sockaddr 结构体(或其特定协议的变体,如 sockaddr_in for IPv4)的指针。当 accept4 成功返回时,这个结构体将被填充为连接到服务器的客户端的地址信息(IP 地址和端口号)。
    • 如果你不关心客户端的地址信息,可以传 NULL
  • addrlen:
    • socklen_t * 类型。
    • 这是一个输入/输出参数。
    • 输入时:它应该指向一个 socklen_t 变量,该变量的值是 addr 指向的缓冲区的大小
    • 输出时accept4 成功返回后,这个 socklen_t 变量的值将被修改为实际存储在 addr 中的地址结构的大小
    • 如果 addr 是 NULLaddrlen 也必须是 NULL
  • flags:
    • int 类型。
    • 一个位掩码,用于设置新创建的已连接套接字的属性。可以是以下值的按位或 (|) 组合:
      • SOCK_NONBLOCK: 为新套接字设置非阻塞模式。这样,后续在这个新套接字上的 I/O 操作(如 readwrite)如果无法立即完成,不会阻塞,而是返回错误 EAGAIN 或 EWOULDBLOCK
      • SOCK_CLOEXEC: 为新套接字设置执行时关闭(Close-on-Exec)标志 (FD_CLOEXEC)。这确保了当程序调用 exec() 系列函数执行新程序时,这个新套接字会被自动关闭,防止它被新程序意外继承。这是一个重要的安全和资源管理特性。

5. 返回值

  • 成功: 返回一个新的、非负的文件描述符,它代表了与客户端通信的已连接套接字。服务器应该使用这个新的文件描述符与客户端进行 read/write 等操作。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

accept4 可能返回的错误码与 accept 基本相同:

  • EAGAIN 或 EWOULDBLOCK: (对于非阻塞套接字) 监听队列中当前没有已完成的连接。
  • EBADFsockfd 不是有效的文件描述符。
  • ECONNABORTED: 连接已被客户端中止。
  • EFAULTaddr 参数指向了进程无法访问的内存地址。
  • EINTR: 系统调用被信号中断。
  • EINVAL: 套接字没有处于监听状态,或者 flags 参数包含无效标志。
  • EMFILE: 进程已打开的文件描述符数量达到上限 (RLIMIT_NOFILE)。
  • ENFILE: 系统已打开的文件描述符数量达到上限。
  • ENOMEM: 内核内存不足。
  • ENOBUFS: 网络子系统内存不足。
  • ENOTSOCKsockfd 不是一个套接字。
  • EOPNOTSUPP: 套接字类型不支持 accept 操作(例如,不是 SOCK_STREAM)。
  • EPERM: 防火墙规则禁止连接。

7. 相似函数或关联函数

  • accept: 标准的接受连接函数。功能与 accept4 相同,但不支持 flags 参数。通常在 accept 返回后,需要再调用 fcntl 来设置 O_NONBLOCK 或 FD_CLOEXEC// 使用 accept + fcntl 的等效操作 new_fd = accept(sockfd, addr, addrlen); if (new_fd != -1) { // 设置非阻塞和 close-on-exec int flags = fcntl(new_fd, F_GETFL, 0); fcntl(new_fd, F_SETFL, flags | O_NONBLOCK); flags = fcntl(new_fd, F_GETFD, 0); fcntl(new_fd, F_SETFD, flags | FD_CLOEXEC); }
  • listen: 将套接字置于监听状态,使其能够接收连接请求。
  • bind: 将套接字与本地地址和端口绑定。
  • socket: 创建一个套接字。
  • read / write: 通过已连接的套接字与客户端通信。
  • close: 关闭套接字。
  • fcntl: 用于获取和设置文件描述符标志,包括 O_NONBLOCK 和 FD_CLOEXEC

8. 示例代码

下面的示例演示了一个简单的 TCP 服务器,它使用 accept4 来接受客户端连接,并利用 SOCK_CLOEXEC 和 SOCK_NONBLOCK 标志。

#define _GNU_SOURCE // 必须定义以使用 accept4
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h> // 包含 O_NONBLOCK 等

#define PORT 8080
#define BACKLOG 10 // 监听队列的最大长度

void handle_client(int client_fd, const struct sockaddr_in *client_addr) {
    char buffer[1024];
    ssize_t bytes_read;

    printf("Handling client %s:%d on fd %d\n",
           inet_ntoa(client_addr->sin_addr), ntohs(client_addr->sin_port), client_fd);

    // 读取客户端发送的数据
    while ((bytes_read = read(client_fd, buffer, sizeof(buffer) - 1)) > 0) {
        buffer[bytes_read] = '\0';
        printf("Received from client: %s", buffer);

        // 将数据回显给客户端
        if (write(client_fd, buffer, bytes_read) != bytes_read) {
            perror("write");
            break;
        }
    }

    if (bytes_read == 0) {
        printf("Client disconnected.\n");
    } else if (bytes_read == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            printf("No data available to read (non-blocking).\n");
        } else {
            perror("read");
        }
    }

    close(client_fd); // 关闭与该客户端的连接
    printf("Closed connection to client.\n");
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);

    printf("--- Simple TCP Server using accept4 ---\n");

    // 1. 创建 socket
    // AF_INET: IPv4
    // SOCK_STREAM: TCP
    // 0: 使用默认协议 (TCP)
    server_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }
    printf("Created server socket: %d\n", server_fd);

    // 2. 准备服务器地址结构
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有本地接口
    server_addr.sin_port = htons(PORT);       // 绑定到指定端口 (网络字节序)

    // 3. 绑定 socket 到地址和端口
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Bound server socket to port %d\n", PORT);

    // 4. 开始监听连接
    if (listen(server_fd, BACKLOG) == -1) {
        perror("listen");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Listening for connections...\n");

    printf("Server is running. Connect to it using e.g., 'telnet 127.0.0.1 %d' or 'nc 127.0.0.1 %d'\n", PORT, PORT);
    printf("Press Ctrl+C to stop the server.\n");

    // 5. 主循环:接受连接
    while (1) {
        // 6. 使用 accept4 接受连接
        // SOCK_CLOEXEC: 自动设置 close-on-exec 标志
        // SOCK_NONBLOCK: 自动设置非阻塞模式
        client_fd = accept4(server_fd, (struct sockaddr *)&client_addr, &client_len, SOCK_CLOEXEC | SOCK_NONBLOCK);

        if (client_fd == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 对于阻塞的监听套接字,这不太可能发生
                // 但对于非阻塞的监听套接字,队列可能为空
                printf("No pending connections (EAGAIN/EWOULDBLOCK).\n");
                usleep(100000); // 等待 0.1 秒再试
                continue;
            } else if (errno == EINTR) {
                // 被信号中断,通常继续循环
                printf("accept4 interrupted by signal, continuing...\n");
                continue;
            } else {
                perror("accept4");
                // 对于其他严重错误,可以选择关闭服务器
                // close(server_fd);
                // exit(EXIT_FAILURE);
                continue; // 或者简单地继续尝试
            }
        }

        printf("\nAccepted new connection. Client fd: %d\n", client_fd);
        printf("Client address: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        // 7. 处理客户端 (在这个简单示例中,我们直接处理)
        // 注意:在实际的高性能服务器中,这里通常会 fork() 或使用线程/事件循环
        handle_client(client_fd, &client_addr);
    }

    // 8. 关闭服务器套接字 (实际上不会执行到这里)
    close(server_fd);
    printf("Server socket closed.\n");
    return 0;
}

9. 编译和运行

# 假设代码保存在 tcp_server_accept4.c 中
# 必须定义 _GNU_SOURCE
gcc -D_GNU_SOURCE -o tcp_server_accept4 tcp_server_accept4.c

# 在一个终端运行服务器
./tcp_server_accept4

# 在另一个终端使用 telnet 或 nc 连接服务器
telnet 127.0.0.1 8080
# 或者
nc 127.0.0.1 8080

# 在 telnet/nc 窗口中输入一些文字,按回车,会看到服务器回显
# 输入 Ctrl+] 然后 quit (telnet) 或 Ctrl+C (nc) 来断开连接

10. 预期输出

服务器终端:

--- Simple TCP Server using accept4 ---
Created server socket: 3
Bound server socket to port 8080
Listening for connections...
Server is running. Connect to it using e.g., 'telnet 127.0.0.1 8080' or 'nc 127.0.0.1 8080'
Press Ctrl+C to stop the server.

Accepted new connection. Client fd: 4
Client address: 127.0.0.1:54321
Handling client 127.0.0.1:54321 on fd 4
Received from client: Hello, Server!

Client disconnected.
Closed connection to client.

客户端终端 (telnet 或 nc):

Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello, Server!
Hello, Server! # 服务器回显
^]
telnet> quit
Connection closed.

11. 总结

accept4 是一个在 Linux 上非常有用的系统调用,特别适合于需要高性能和安全性的网络服务器程序。

  • 核心优势:它将“接受连接”和“设置套接字属性”这两个操作原子化地结合在一起,避免了使用 accept + fcntl 时可能存在的竞态条件(即在 accept 和 fcntl 之间,新套接字可能被意外使用)。
  • SOCK_CLOEXEC:自动设置 close-on-exec 标志,防止套接字被 exec() 继承,提高安全性。
  • SOCK_NONBLOCK:自动设置非阻塞模式,使得在新套接字上的 I/O 操作不会阻塞。
  • 与 accept 的关系accept4(sockfd, addr, addrlen, 0) 在功能上等同于 accept(sockfd, addr, addrlen)
  • 可移植性accept4 是 Linux 特有的。如果需要编写可移植的代码,应使用 accept 并手动调用 fcntl

对于 Linux 系统编程新手来说,掌握 accept4 及其标志的使用,是编写健壮、高效网络服务的重要一步。

发表在 linux文章 | 留下评论

Linux 高级 I/O 系统调用详解

1. 概述

这些是 Linux 系统中一组高级 I/O 操作系统调用,它们提供了比传统 read/write 更强大和灵活的功能。每种调用都有其特定的用途和优势。

2. 系统调用详细介绍

2.1 pread/pwrite – 位置指定读写

#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

功能

  • pread: 从指定位置读取数据,不改变文件指针位置
  • pwrite: 向指定位置写入数据,不改变文件指针位置

优势

  • 原子操作(读/写 + 位置指定)
  • 线程安全
  • 不影响其他读写操作

示例

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main() {
    int fd = open("test.txt", O_CREAT | O_RDWR, 0644);
    
    // 写入数据
    const char *data = "Hello World!";
    pwrite(fd, data, strlen(data), 0);
    
    // 从指定位置读取
    char buffer[20];
    ssize_t bytes_read = pread(fd, buffer, 10, 0);
    buffer[bytes_read] = '\0';
    printf("读取内容: %s\n", buffer);
    
    close(fd);
    return 0;
}

2.2 preadv/pwritev – 分散/聚集 I/O

#include <sys/uio.h>

ssize_t preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset);
ssize_t pwritev(int fd, const struct iovec *iov, int iovcnt, off_t offset);

功能

  • preadv: 从指定位置读取到多个缓冲区(分散读取)
  • pwritev: 从多个缓冲区写入到指定位置(聚集写入)

iov 结构体

struct iovec {
    void  *iov_base;    // 缓冲区地址
    size_t iov_len;     // 缓冲区长度
};

示例

#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>

int main() {
    int fd = open("scatter_gather.txt", O_CREAT | O_RDWR, 0644);
    
    // 准备分散写入数据
    struct iovec iov_write[3];
    char *data1 = "First part ";
    char *data2 = "Second part ";
    char *data3 = "Third part\n";
    
    iov_write[0].iov_base = data1;
    iov_write[0].iov_len = strlen(data1);
    iov_write[1].iov_base = data2;
    iov_write[1].iov_len = strlen(data2);
    iov_write[2].iov_base = data3;
    iov_write[2].iov_len = strlen(data3);
    
    // 分散写入
    pwritev(fd, iov_write, 3, 0);
    
    // 分散读取
    struct iovec iov_read[3];
    char buf1[20], buf2[20], buf3[20];
    
    iov_read[0].iov_base = buf1;
    iov_read[0].iov_len = sizeof(buf1) - 1;
    iov_read[1].iov_base = buf2;
    iov_read[1].iov_len = sizeof(buf2) - 1;
    iov_read[2].iov_base = buf3;
    iov_read[2].iov_len = sizeof(buf3) - 1;
    
    ssize_t total_bytes = preadv(fd, iov_read, 3, 0);
    printf("总共读取 %zd 字节\n", total_bytes);
    
    buf1[iov_read[0].iov_len] = '\0';
    buf2[iov_read[1].iov_len] = '\0';
    buf3[iov_read[2].iov_len] = '\0';
    
    printf("缓冲区1: %s\n", buf1);
    printf("缓冲区2: %s\n", buf2);
    printf("缓冲区3: %s\n", buf3);
    
    close(fd);
    return 0;
}

2.3 preadv2/pwritev2 – 增强版分散/聚集 I/O

#define _GNU_SOURCE
#include <sys/uio.h>

ssize_t preadv2(int fd, const struct iovec *iov, int iovcnt, 
                off_t offset, int flags);
ssize_t pwritev2(int fd, const struct iovec *iov, int iovcnt, 
                 off_t offset, int flags);

功能

  • 在 preadv/pwritev 基础上增加标志控制
  • 支持更细粒度的 I/O 控制

支持的标志

  • RWF_HIPRI: 高优先级 I/O
  • RWF_DSYNC: 数据同步写入
  • RWF_SYNC: 同步写入
  • RWF_NOWAIT: 非阻塞操作
  • RWF_APPEND: 追加模式

示例

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main() {
    int fd = open("enhanced_io.txt", O_CREAT | O_RDWR, 0644);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // 准备数据
    struct iovec iov[2];
    char *data1 = "Enhanced ";
    char *data2 = "I/O operation\n";
    
    iov[0].iov_base = data1;
    iov[0].iov_len = strlen(data1);
    iov[1].iov_base = data2;
    iov[1].iov_len = strlen(data2);
    
    // 使用增强版写入(带标志)
    ssize_t bytes_written = pwritev2(fd, iov, 2, 0, RWF_SYNC);
    if (bytes_written == -1) {
        if (errno == ENOSYS) {
            printf("系统不支持 pwritev2,回退到 pwritev\n");
            bytes_written = pwritev(fd, iov, 2, 0);
        } else {
            perror("pwritev2");
            close(fd);
            return 1;
        }
    }
    
    printf("写入 %zd 字节\n", bytes_written);
    
    close(fd);
    return 0;
}

2.4 prlimit64 – 资源限制控制

#include <sys/resource.h>

int prlimit64(pid_t pid, int resource, 
              const struct rlimit64 *new_limit, 
              struct rlimit64 *old_limit);

功能

  • 获取和设置进程资源限制
  • 支持 64 位资源限制值
  • 可以操作其他进程的资源限制

常用资源类型

  • RLIMIT_AS: 虚拟内存地址空间限制
  • RLIMIT_CORE: 核心转储文件大小限制
  • RLIMIT_CPU: CPU 时间限制
  • RLIMIT_DATA: 数据段大小限制
  • RLIMIT_FSIZE: 文件大小限制
  • RLIMIT_NOFILE: 打开文件描述符数量限制
  • RLIMIT_NPROC: 进程数量限制
  • RLIMIT_STACK: 栈大小限制

示例

#include <stdio.h>
#include <sys/resource.h>
#include <errno.h>

int main() {
    struct rlimit64 limit;
    
    // 获取当前进程的文件大小限制
    if (prlimit64(0, RLIMIT_FSIZE, NULL, &limit) == 0) {
        printf("文件大小限制:\n");
        if (limit.rlim_cur == RLIM64_INFINITY) {
            printf("  软限制: 无限制\n");
        } else {
            printf("  软限制: %lld 字节\n", (long long)limit.rlim_cur);
        }
        if (limit.rlim_max == RLIM64_INFINITY) {
            printf("  硬限制: 无限制\n");
        } else {
            printf("  硬限制: %lld 字节\n", (long long)limit.rlim_max);
        }
    }
    
    // 获取打开文件数限制
    if (prlimit64(0, RLIMIT_NOFILE, NULL, &limit) == 0) {
        printf("文件描述符限制:\n");
        printf("  软限制: %lld\n", (long long)limit.rlim_cur);
        printf("  硬限制: %lld\n", (long long)limit.rlim_max);
    }
    
    // 设置新的文件大小限制(仅作为示例)
    struct rlimit64 new_limit;
    new_limit.rlim_cur = 1024 * 1024;  // 1MB
    new_limit.rlim_max = 1024 * 1024;  // 1MB
    
    // 注意:修改资源限制通常需要适当权限
    if (prlimit64(0, RLIMIT_FSIZE, &new_limit, NULL) == 0) {
        printf("成功设置文件大小限制为 1MB\n");
    } else {
        if (errno == EPERM) {
            printf("权限不足,无法修改资源限制\n");
        } else {
            perror("设置资源限制失败");
        }
    }
    
    return 0;
}

3. 性能对比测试

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <time.h>
#include <string.h>

#define ITERATIONS 10000
#define BUFFER_SIZE 1024

// 性能测试函数
double benchmark_function(const char *name, 
                         ssize_t (*func)(int, void*, size_t, off_t)) {
    int fd = open("benchmark_test.dat", O_CREAT | O_RDWR, 0644);
    char *buffer = malloc(BUFFER_SIZE);
    
    // 准备测试数据
    memset(buffer, 'A', BUFFER_SIZE);
    write(fd, buffer, BUFFER_SIZE);
    
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    // 执行测试
    for (int i = 0; i < ITERATIONS; i++) {
        func(fd, buffer, BUFFER_SIZE, 0);
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    double elapsed = (end.tv_sec - start.tv_sec) * 1000000.0 +
                     (end.tv_nsec - start.tv_nsec) / 1000.0;
    
    free(buffer);
    close(fd);
    unlink("benchmark_test.dat");
    
    printf("%-15s: %.2f 微秒 (平均 %.3f 纳秒/次)\n", 
           name, elapsed, elapsed * 1000.0 / ITERATIONS);
    
    return elapsed;
}

// 模拟不同函数的包装器
ssize_t wrapper_pread(int fd, void *buf, size_t count, off_t offset) {
    return pread(fd, buf, count, offset);
}

int main() {
    printf("=== I/O 系统调用性能对比 ===\n\n");
    
    // 创建大测试文件
    int test_fd = open("large_test_file.dat", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    char *large_buffer = malloc(1024 * 1024);  // 1MB
    for (int i = 0; i < 10; i++) {  // 10MB 文件
        write(test_fd, large_buffer, 1024 * 1024);
    }
    free(large_buffer);
    close(test_fd);
    
    printf("创建 10MB 测试文件完成\n\n");
    
    // 测试不同的读取方式
    benchmark_function("pread", wrapper_pread);
    
    printf("\n性能分析:\n");
    printf("1. pread/pwrite: 适合随机访问场景\n");
    printf("2. preadv/pwritev: 适合多缓冲区操作\n");
    printf("3. preadv2/pwritev2: 提供更多控制选项\n");
    printf("4. prlimit64: 用于资源管理而非 I/O 操作\n");
    
    unlink("large_test_file.dat");
    return 0;
}

4. 实际应用场景

4.1 数据库存储引擎

#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>

// 模拟数据库页读取
typedef struct {
    int page_id;
    char data[4096];
    int checksum;
} db_page_t;

int read_database_pages(const char *db_file, int *page_ids, int count) {
    int fd = open(db_file, O_RDONLY);
    if (fd == -1) return -1;
    
    struct iovec *iov = malloc(count * sizeof(struct iovec));
    db_page_t *pages = malloc(count * sizeof(db_page_t));
    
    // 设置分散读取
    for (int i = 0; i < count; i++) {
        iov[i].iov_base = &pages[i];
        iov[i].iov_len = sizeof(db_page_t);
    }
    
    // 一次性读取多个数据库页
    ssize_t bytes_read = preadv(fd, iov, count, 0);
    
    printf("读取 %d 个数据库页,共 %zd 字节\n", count, bytes_read);
    
    free(pages);
    free(iov);
    close(fd);
    return bytes_read > 0 ? 0 : -1;
}

4.2 网络协议处理

#include <sys/uio.h>
#include <stdio.h>

// 模拟网络包处理
typedef struct {
    uint32_t header;
    uint16_t type;
    uint16_t length;
} packet_header_t;

typedef struct {
    char payload[1024];
} packet_payload_t;

int process_network_packets(int socket_fd) {
    // 准备接收多个网络包
    struct iovec iov[5];  // 最多5个包
    packet_header_t headers[5];
    packet_payload_t payloads[5];
    
    // 设置分散接收缓冲区
    for (int i = 0; i < 5; i++) {
        iov[i*2].iov_base = &headers[i];
        iov[i*2].iov_len = sizeof(packet_header_t);
        
        iov[i*2+1].iov_base = &payloads[i];
        iov[i*2+1].iov_len = sizeof(packet_payload_t);
    }
    
    // 一次性接收多个包(简化示例)
    printf("准备接收网络数据包...\n");
    return 0;
}

5. 编译和运行

# 编译示例
gcc -o io_examples example1.c
gcc -o performance_test performance_test.c
gcc -o advanced_examples advanced_examples.c

# 运行示例
./io_examples
./performance_test
./advanced_examples

6. 使用建议

6.1 选择指南

场景推荐函数原因
简单顺序读写read/write简单直接
随机位置访问pread/pwrite原子操作,线程安全
多缓冲区操作readv/writev减少系统调用
位置+多缓冲区preadv/pwritev功能最强
需要高级控制preadv2/pwritev2支持标志控制
资源限制管理prlimit64专门用途

6.2 最佳实践

// 安全的 pread 封装
ssize_t safe_pread(int fd, void *buf, size_t count, off_t offset) {
    if (fd < 0 || !buf || count == 0) {
        errno = EINVAL;
        return -1;
    }
    
    ssize_t result;
    do {
        result = pread(fd, buf, count, offset);
    } while (result == -1 && errno == EINTR);
    
    return result;
}

// 安全的 preadv 封装
ssize_t safe_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset) {
    if (fd < 0 || !iov || iovcnt <= 0) {
        errno = EINVAL;
        return -1;
    }
    
    ssize_t result;
    do {
        result = preadv(fd, iov, iovcnt, offset);
    } while (result == -1 && errno == EINTR);
    
    return result;
}

这些高级 I/O 系统调用为 Linux 应用程序提供了强大而灵活的文件操作能力,正确使用它们可以显著提高程序的性能和可靠性。

发表在 linux文章 | 留下评论

process_vm_readv/process_vm_writev 接口详解

基础接口

process_vm_readv(2)

NAME
       process_vm_readv, process_vm_writev - 在进程间传输数据

SYNOPSIS
       #define _GNU_SOURCE
       #include <sys/uio.h>
       
       ssize_t process_vm_readv(pid_t pid,
                               const struct iovec *local_iov,
                               unsigned long liovcnt,
                               const struct iovec *remote_iov,
                               unsigned long riovcnt,
                               unsigned long flags);
       
       ssize_t process_vm_writev(pid_t pid,
                                const struct iovec *local_iov,
                                unsigned long liovcnt,
                                const struct iovec *remote_iov,
                                unsigned long riovcnt,
                                unsigned long flags);

DESCRIPTION
       这些系统调用允许直接在调用进程和指定进程(pid)的内存之间传输数据,
       无需通过内核缓冲区复制。

       process_vm_readv() 从远程进程读取数据到本地进程
       process_vm_writev() 从本地进程写入数据到远程进程

       参数说明:
       - pid: 目标进程ID
       - local_iov: 本地内存区域描述符数组
       - liovcnt: 本地iovec数组元素个数
       - remote_iov: 远程内存区域描述符数组
       - riovcnt: 远程iovec数组元素个数
       - flags: 保留字段,必须为0

RETURN VALUE
       成功时返回传输的字节数,失败时返回-1并设置errno

ERRORS
       EACCES     没有权限访问目标进程内存
       EFAULT     指定的地址范围无效
       EINVAL     参数无效
       ENOMEM     内存不足
       EPERM      没有权限操作目标进程
       ESRCH      目标进程不存在

VERSIONS
       Linux 3.2+ 支持这些系统调用

CONFORMING TO
       这些是Linux特有的系统调用

NOTES
       需要相同用户ID或CAP_SYS_PTRACE权限
       目标进程必须正在运行
       不会触发目标进程的信号处理程序

数据结构

iovec 结构体

struct iovec {
    void  *iov_base;    /* 起始地址 */
    size_t iov_len;     /* 缓冲区长度 */
};

使用示例

示例1:基本内存读取

#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// 目标进程中需要读取的变量
int target_global_var = 42;
char target_string[] = "Hello from target process!";

int read_remote_memory(pid_t target_pid) {
    struct iovec local_iov[2];
    struct iovec remote_iov[2];
    ssize_t result;
    int local_int;
    char local_buffer[100];
    
    // 设置本地缓冲区
    local_iov[0].iov_base = &local_int;
    local_iov[0].iov_len = sizeof(local_int);
    local_iov[1].iov_base = local_buffer;
    local_iov[1].iov_len = sizeof(local_buffer);
    
    // 设置远程内存地址(需要知道目标进程中的确切地址)
    remote_iov[0].iov_base = &target_global_var;  // 实际使用中需要通过其他方式获取
    remote_iov[0].iov_len = sizeof(target_global_var);
    remote_iov[1].iov_base = target_string;
    remote_iov[1].iov_len = strlen(target_string) + 1;
    
    // 读取远程进程内存
    result = process_vm_readv(target_pid,
                             local_iov, 2,
                             remote_iov, 2,
                             0);
    
    if (result == -1) {
        perror("process_vm_readv");
        return -1;
    }
    
    printf("Read %zd bytes\n", result);
    printf("Remote int value: %d\n", local_int);
    printf("Remote string: %s\n", local_buffer);
    
    return 0;
}

示例2:内存写入操作

#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// 目标进程中的可修改变量
int target_writable_var = 100;
char target_writable_buffer[256] = "Original content";

int write_remote_memory(pid_t target_pid) {
    struct iovec local_iov[2];
    struct iovec remote_iov[2];
    ssize_t result;
    int new_value = 999;
    char new_string[] = "Modified by process_vm_writev!";
    
    // 设置本地数据源
    local_iov[0].iov_base = &new_value;
    local_iov[0].iov_len = sizeof(new_value);
    local_iov[1].iov_base = new_string;
    local_iov[1].iov_len = strlen(new_string) + 1;
    
    // 设置远程内存地址
    remote_iov[0].iov_base = &target_writable_var;
    remote_iov[0].iov_len = sizeof(target_writable_var);
    remote_iov[1].iov_base = target_writable_buffer;
    remote_iov[1].iov_len = strlen(new_string) + 1;
    
    // 写入远程进程内存
    result = process_vm_writev(target_pid,
                              local_iov, 2,
                              remote_iov, 2,
                              0);
    
    if (result == -1) {
        perror("process_vm_writev");
        return -1;
    }
    
    printf("Wrote %zd bytes to remote process\n", result);
    return 0;
}

示例3:完整的工作示例

目标进程代码 (target.c)

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

volatile int shared_int = 12345;
char shared_string[256] = "This is shared data from target process";
int running = 1;

void signal_handler(int sig) {
    printf("Received signal %d\n", sig);
    running = 0;
}

int main() {
    printf("Target process PID: %d\n", getpid());
    printf("Shared int address: %p\n", &shared_int);
    printf("Shared string address: %p\n", shared_string);
    printf("Shared int value: %d\n", shared_int);
    printf("Shared string value: %s\n", shared_string);
    
    // 安装信号处理程序
    signal(SIGUSR1, signal_handler);
    
    printf("Target process running... Send SIGUSR1 to stop\n");
    
    while (running) {
        printf("shared_int = %d, shared_string = %s\n", 
               shared_int, shared_string);
        sleep(2);
    }
    
    printf("Target process exiting...\n");
    printf("Final values - shared_int = %d, shared_string = %s\n", 
           shared_int, shared_string);
    
    return 0;
}

访问进程代码 (accessor.c)

#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <target_pid>\n", argv[0]);
        exit(1);
    }
    
    pid_t target_pid = atoi(argv[1]);
    struct iovec local_iov[2];
    struct iovec remote_iov[2];
    ssize_t result;
    int local_int;
    char local_string[256];
    int new_int = 99999;
    char new_string[] = "Modified by accessor process!";
    
    printf("Accessing process PID: %d\n", getpid());
    printf("Target PID: %d\n", target_pid);
    
    // 读取远程进程内存
    printf("\n--- Reading remote memory ---\n");
    local_iov[0].iov_base = &local_int;
    local_iov[0].iov_len = sizeof(local_int);
    local_iov[1].iov_base = local_string;
    local_iov[1].iov_len = sizeof(local_string);
    
    // 注意:这里需要知道目标进程的确切内存地址
    // 在实际应用中,这些地址需要通过调试信息或其他方式获取
    remote_iov[0].iov_base = (void*)0x601040;  // 需要根据实际情况调整
    remote_iov[0].iov_len = sizeof(int);
    remote_iov[1].iov_base = (void*)0x601060;  // 需要根据实际情况调整
    remote_iov[1].iov_len = sizeof(local_string);
    
    result = process_vm_readv(target_pid,
                             local_iov, 2,
                             remote_iov, 2,
                             0);
    
    if (result == -1) {
        perror("process_vm_readv");
        printf("Note: You need to adjust memory addresses based on target process\n");
        return 1;
    }
    
    printf("Read %zd bytes\n", result);
    printf("Remote int value: %d\n", local_int);
    printf("Remote string: %s\n", local_string);
    
    // 修改远程进程内存
    printf("\n--- Writing to remote memory ---\n");
    local_iov[0].iov_base = &new_int;
    local_iov[0].iov_len = sizeof(new_int);
    local_iov[1].iov_base = new_string;
    local_iov[1].iov_len = strlen(new_string) + 1;
    
    result = process_vm_writev(target_pid,
                              local_iov, 2,
                              remote_iov, 2,
                              0);
    
    if (result == -1) {
        perror("process_vm_writev");
        return 1;
    }
    
    printf("Wrote %zd bytes to remote process\n", result);
    
    // 再次读取验证修改
    printf("\n--- Verifying changes ---\n");
    result = process_vm_readv(target_pid,
                             local_iov, 2,
                             remote_iov, 2,
                             0);
    
    if (result != -1) {
        printf("Remote int value after write: %d\n", local_int);
        printf("Remote string after write: %s\n", local_string);
    }
    
    // 发送信号给目标进程
    printf("\n--- Sending signal to target process ---\n");
    if (kill(target_pid, SIGUSR1) == -1) {
        perror("kill");
    } else {
        printf("Signal sent successfully\n");
    }
    
    return 0;
}

示例4:实用工具 – 进程内存检查器

#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

// 简单的内存转储工具
int dump_remote_memory(pid_t pid, unsigned long addr, size_t size) {
    char *buffer;
    struct iovec local_iov[1];
    struct iovec remote_iov[1];
    ssize_t result;
    size_t i;
    
    buffer = malloc(size);
    if (!buffer) {
        perror("malloc");
        return -1;
    }
    
    local_iov[0].iov_base = buffer;
    local_iov[0].iov_len = size;
    
    remote_iov[0].iov_base = (void*)addr;
    remote_iov[0].iov_len = size;
    
    result = process_vm_readv(pid,
                             local_iov, 1,
                             remote_iov, 1,
                             0);
    
    if (result == -1) {
        perror("process_vm_readv");
        free(buffer);
        return -1;
    }
    
    printf("Memory dump at 0x%lx (%zd bytes):\n", addr, result);
    for (i = 0; i < (size_t)result; i++) {
        if (i % 16 == 0) {
            printf("\n%08lx: ", addr + i);
        }
        printf("%02x ", (unsigned char)buffer[i]);
    }
    printf("\n");
    
    // 显示可打印字符
    printf("\nASCII: ");
    for (i = 0; i < (size_t)result; i++) {
        if (i % 16 == 0 && i > 0) {
            printf("\n       ");
        }
        printf("%c", (buffer[i] >= 32 && buffer[i] <= 126) ? buffer[i] : '.');
    }
    printf("\n");
    
    free(buffer);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "Usage: %s <pid> <address> <size>\n", argv[0]);
        fprintf(stderr, "Example: %s 1234 0x601040 64\n", argv[0]);
        exit(1);
    }
    
    pid_t target_pid = atoi(argv[1]);
    unsigned long address = strtoul(argv[2], NULL, 0);
    size_t size = strtoul(argv[3], NULL, 0);
    
    printf("Dumping %zu bytes from process %d at address 0x%lx\n", 
           size, target_pid, address);
    
    return dump_remote_memory(target_pid, address, size);
}

示例5:批量内存操作

#define _GNU_SOURCE
#include <sys/uio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define MAX_IOV 1024

typedef struct {
    void *remote_addr;
    size_t size;
    void *local_buffer;
} memory_region_t;

// 批量读取多个内存区域
int batch_read_memory(pid_t pid, memory_region_t *regions, int count) {
    struct iovec local_iov[MAX_IOV];
    struct iovec remote_iov[MAX_IOV];
    ssize_t result;
    int i;
    
    if (count > MAX_IOV) {
        fprintf(stderr, "Too many regions\n");
        return -1;
    }
    
    // 设置iovec数组
    for (i = 0; i < count; i++) {
        local_iov[i].iov_base = regions[i].local_buffer;
        local_iov[i].iov_len = regions[i].size;
        remote_iov[i].iov_base = regions[i].remote_addr;
        remote_iov[i].iov_len = regions[i].size;
    }
    
    result = process_vm_readv(pid,
                             local_iov, count,
                             remote_iov, count,
                             0);
    
    if (result == -1) {
        perror("process_vm_readv");
        return -1;
    }
    
    printf("Batch read completed: %zd bytes total\n", result);
    return 0;
}

// 批量写入多个内存区域
int batch_write_memory(pid_t pid, memory_region_t *regions, int count) {
    struct iovec local_iov[MAX_IOV];
    struct iovec remote_iov[MAX_IOV];
    ssize_t result;
    int i;
    
    if (count > MAX_IOV) {
        fprintf(stderr, "Too many regions\n");
        return -1;
    }
    
    // 设置iovec数组
    for (i = 0; i < count; i++) {
        local_iov[i].iov_base = regions[i].local_buffer;
        local_iov[i].iov_len = regions[i].size;
        remote_iov[i].iov_base = regions[i].remote_addr;
        remote_iov[i].iov_len = regions[i].size;
    }
    
    result = process_vm_writev(pid,
                              local_iov, count,
                              remote_iov, count,
                              0);
    
    if (result == -1) {
        perror("process_vm_writev");
        return -1;
    }
    
    printf("Batch write completed: %zd bytes total\n", result);
    return 0;
}

int main() {
    printf("Batch memory operations example\n");
    printf("This example shows how to perform batch operations\n");
    printf("You need to provide actual PID and memory addresses\n");
    return 0;
}

使用限制和注意事项

权限要求

  1. 相同用户: 调用进程和目标进程必须有相同的有效用户ID
  2. ptrace权限: 或者调用进程需要 CAP_SYS_PTRACE 权能
  3. 进程状态: 目标进程必须正在运行(不是僵尸进程)

安全限制

  1. 内存保护: 不能访问目标进程的受保护内存区域
  2. 地址有效性: 必须确保远程地址在目标进程的有效地址空间内
  3. 对齐要求: 某些体系结构可能有内存对齐要求

技术限制

  1. 最大IOV数量: 系统可能限制iovec数组的最大长度
  2. 性能考虑: 大量小的传输可能不如批量传输高效
  3. 原子性: 单次调用内的多个传输不是原子的

错误处理

  1. 部分传输: 可能只传输部分数据,需要检查返回值
  2. 地址错误: 无效地址会导致整个操作失败
  3. 进程终止: 目标进程在操作过程中终止会导致错误

最佳实践

  1. 地址获取: 使用调试信息或符号表获取准确的内存地址
  2. 缓冲区管理: 确保本地缓冲区足够大且生命周期正确
  3. 错误检查: 始终检查返回值并处理错误情况
  4. 权限验证: 在执行操作前验证权限和进程状态

这些系统调用提供了强大的进程间内存访问能力,但使用时需要谨慎处理安全和权限问题。

发表在 linux文章 | 留下评论