io_getevents系统调用及示例

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

1. 函数介绍

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

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

提交请求:你告诉内核:“请帮我从文件描述符 fd 读取数据到 buffer”,然后你的程序立即返回,可以去做其他事情。

内核处理:内核在后台执行这个读取操作。

获取结果:过一段时间后,你再询问内核:“之前那个读取操作完成了吗?”。如果完成了,内核会告诉你结果(读取了多少字节,是否出错等)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 需要定义宏来启用 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);

注意:

这些是底层系统调用。标准 C 库(glibc)可能不直接提供用户友好的包装函数,或者支持不完整(io_pgetevents 较新,可能需要较新版本 glibc)。

通常需要通过 syscall() 函数并传入系统调用号来调用它们。

需要包含 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)

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

  • EFAULT: events 或 timeout 指向了无效的内存地址。

  • EINTR: 系统调用被信号中断(对于 io_getevents)。对于 io_pgetevents,如果 sigmask 为 NULL,也可能发生。

  • EINVAL: min_nr 大于 nr,或者 ctx_id 无效。

  • ENOMEM: 内核内存不足。

  • EBADF: ctx_id 不是一个有效的异步 I/O 上下文。

7. 相似函数或关联函数

  • io_setup: 创建一个异步 I/O 上下文。

  • io_destroy: 销毁一个异步 I/O 上下文。

  • io_submit: 向异步 I/O 上下文提交一个或多个 I/O 请求 (iocb)。

  • io_cancel: 尝试取消一个已提交但尚未完成的 I/O 请求。

  • struct io_context_t: 异步 I/O 上下文的类型。

  • struct iocb: 描述单个异步 I/O 请求的结构体。

  • struct io_event: 描述单个已完成 I/O 事件的结构体。

8. 示例代码

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#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&#91;num_reads];
struct iocb *iocb_ptrs&#91;num_reads];
char buffers&#91;num_reads]&#91;chunk_size];
struct io_event events&#91;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&#91;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&#91;i], fd, buffers&#91;i], chunk_size, i * chunk_size, i + 1);
iocb_ptrs&#91;i] = &iocbs&#91;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&#91;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&#91;ev->data - 1] 中的数据
// printf(" First byte: %c\n", buffers&#91;ev->data - 1]&#91;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. 编译和运行

1
2
3
4
5
6
# 假设代码保存在 aio_getevents_example.c 中
gcc -o aio_getevents_example aio_getevents_example.c

# 运行程序
./aio_getevents_example

10. 预期输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
--- 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 参数:

1
2
3
4
5
6
7
8
9
10
11
12
// 假设已定义 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:增强版本,在等待期间可以原子性地设置信号掩码,提供更精细的信号控制。

工作流程:

  • io_setup 创建上下文。

  • 构造 iocb 请求并用 io_submit 提交。

  • 使用 io_getevents/io_pgetevents 等待和获取完成事件。

  • io_destroy 销毁上下文。

  • 优势:允许程序在 I/O 操作进行的同时执行其他任务,提高并发性能。

局限性:

  • 传统 Linux AIO 对于 buffered 文件 I/O 支持不佳,可能退化为同步。

  • API 相对复杂,直接使用系统调用较为繁琐。

现代替代:对于新的高性能异步 I/O 应用,强烈推荐使用 io_uring,它提供了更强大、更易用、性能更好的异步 I/O 接口。

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

io_pgetevents系统调用及示例

io_pgetevents系统调用及示例 io_setup/io_submit系统调用及示例

我们继续按照您的要求学习 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. 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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 nr: events 数组的大小,即调用者希望获取的最大事件数量。

  • 函数返回时,实际返回的事件数会 <= 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 信号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// 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&#91;NUM_REQUESTS];
struct iocb *iocb_ptrs&#91;NUM_REQUESTS];
struct io_event events&#91;NUM_REQUESTS];
char buffers&#91;NUM_REQUESTS]&#91;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&#91;i], 0, sizeof(struct iocb));
iocbs&#91;i].aio_fildes = fd;
iocbs&#91;i].aio_lio_opcode = IOCB_CMD_PREAD; // 异步预读
iocbs&#91;i].aio_reqprio = 0;
iocbs&#91;i].aio_buf = (uint64_t)(buffers&#91;i]); // 读入缓冲区
iocbs&#91;i].aio_nbytes = BUFFER_SIZE / 2; // 读取一半缓冲区大小
iocbs&#91;i].aio_offset = i * (BUFFER_SIZE / 2); // 从不同偏移量开始读
iocbs&#91;i].aio_data = i + 1; // 用户数据,用于标识请求
iocb_ptrs&#91;i] = &iocbs&#91;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&#91;i].data, (long long)events&#91;i].res);
if (events&#91;i].res > 0) {
buffers&#91;events&#91;i].data - 1]&#91;events&#91;i].res] = '\0'; // Null-terminate
printf(" Data: %s", buffers&#91;events&#91;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;
}

代码解释:

定义系统调用: 由于 glibc 可能没有包装,我们手动定义了 io_pgetevents 及相关 AIO 系统调用的包装函数。

创建测试文件: 程序首先创建一个包含测试数据的文件。

设置信号处理: 为 SIGUSR1 安装一个处理函数,用于演示信号处理。

打开文件: 以只读方式打开测试文件。

初始化 AIO 上下文: 调用 io_setup 创建一个可以处理 NUM_REQUESTS 个并发请求的上下文。

准备 AIO 请求: 初始化两个 struct iocb 结构,设置为从文件不同偏移量异步预读取数据。

提交请求: 调用 io_submit 将这两个读取请求提交给 AIO 引擎。

设置信号屏蔽: 创建一个包含 SIGUSR1 的信号集 block_sigusr1,并填充 struct __aio_sigset 结构 aio_sigset。

启动信号发送线程: 创建一个线程,它会在 2 秒后向主进程发送 SIGUSR1 信号。这用来测试信号屏蔽效果。

关键步骤 - io_pgetevents:

  • 设置 10 秒超时。

  • 调用 io_pgetevents(ctx, 1, NUM_REQUESTS, events, &timeout, &aio_sigset)。

  • min_nr=1: 至少等待 1 个事件完成。

  • &aio_sigset: 传递信号集,告诉内核在等待期间原子性地屏蔽 SIGUSR1。

等待和处理: 主线程在 io_pgetevents 中等待。在此期间,SIGUSR1 被屏蔽。信号发送线程发出的 SIGUSR1 会被挂起。当 io_pgetevents 返回时(因为 I/O 完成或超时),信号屏蔽恢复,被挂起的 SIGUSR1 随即被递达,信号处理函数得以执行。

输出结果: 打印获取到的事件信息和读取到的数据。

清理: 等待信号发送线程结束,销毁 AIO 上下文,关闭文件,删除测试文件。

核心概念:

  • io_pgetevents 的 usig 参数使得信号屏蔽和等待 I/O 成为一个原子操作。这避免了在设置信号屏蔽和调用 io_getevents 之间收到信号的竞态条件。

  • 如果使用 io_getevents,你需要先调用 pthread_sigmask(SIG_SETMASK, …) 设置屏蔽,然后调用 io_getevents,最后再调用 pthread_sigmask(SIG_SETMASK, …) 恢复。在这三步之间,信号可能会到达,导致竞态。

重要提示与注意事项:

内核版本: io_pgetevents 需要 Linux 内核 4.18 或更高版本。

glibc 支持: 标准 C 库可能不提供直接包装,需要使用 syscall。

复杂性: AIO 本身就是一个复杂的子系统,涉及上下文管理、请求提交、事件获取等多个步骤。

性能: 传统的 aio 性能可能不如现代的 io_uring。对于新项目,考虑使用 io_uring。

信号安全: io_pgetevents 本身不是异步信号安全的,不应在信号处理函数中直接调用。

usig 参数: 这是 io_pgetevents 的核心优势。正确使用它可以编写出在信号处理方面更健壮的代码。

错误处理: 始终检查返回值和 errno,尤其是在处理 EINTR(被信号中断)时。

总结:

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

io_setup/io_submit系统调用及示例

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

1. 函数介绍

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

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

提交请求:你告诉内核:“请帮我从文件描述符 fd 读取数据到 buffer”,然后你的程序立即返回,可以去做其他事情。

内核处理:内核在后台执行这个读取操作。

获取结果:过一段时间后,你再询问内核:“之前那个读取操作完成了吗?”。如果完成了,内核会告诉你结果(读取了多少字节,是否出错等)。

io_setup 和 io_submit 就是这个异步 I/O 机制的第一步和第二步。

  • io_setup: 创建一个异步 I/O 上下文 (context)。你可以把它想象成一个“工作队列”或“任务列表”的管理器。所有你提交给这个上下文的异步 I/O 请求都会被它管理。

  • io_submit: 提交一个或多个异步 I/O 请求(读、写等)到一个已创建的上下文中。内核会接收这些请求,并在后台开始执行它们。

简单来说:

  • io_setup:创建一个“异步任务管理器”。

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

2. 函数原型

1
2
3
4
5
6
7
8
9
10
11
12
// 需要定义宏来启用 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);

注意:

这些是底层系统调用。标准 C 库(glibc)可能不直接提供用户友好的包装函数。

通常需要通过 syscall() 函数并传入系统调用号来调用它们。

需要包含 linux/aio_abi.h 头文件来获取相关结构体和类型定义。

3. 功能

  • io_setup: 初始化一个异步 I/O 上下文,该上下文能够处理最多 nr_events 个并发的异步 I/O 操作,并将上下文的标识符(句柄)存储在 ctxp 指向的变量中。

  • io_submit: 将 nr 个异步 I/O 请求(由 iocbpp 数组指向)提交到由 ctx_id 标识的异步 I/O 上下文中。内核会尝试立即开始处理这些请求。

4. 参数详解

io_setup(unsigned nr_events, io_context_t *ctxp)

nr_events:

  • unsigned 类型。

  • 指定这个异步 I/O 上下文最多可以同时处理多少个未完成的异步 I/O 请求(事件)。这相当于预分配了资源来跟踪这些请求。

  • 内核可能会将这个值向上舍入到内部优化所需的大小。

ctxp:

  • io_context_t * 类型。

  • 一个指向 io_context_t 类型变量的指针。io_setup 调用成功后,会将新创建的异步 I/O 上下文的标识符(或句柄)写入到这个变量中。后续的 io_submit, io_getevents 等操作都需要使用这个 ctx_id。

io_submit(io_context_t ctx_id, long nr, struct iocb **iocbpp)

ctx_id:

  • io_context_t 类型。

  • 由 io_setup 返回的异步 I/O 上下文的标识符。

nr:

  • long 类型。

  • 指定要提交的异步 I/O 请求数量。这个值应该与 iocbpp 数组的大小相对应。

iocbpp:

  • struct iocb ** 类型。

  • 一个指针数组,数组中的每个元素都指向一个 struct iocb 结构体。struct iocb(I/O Control Block)描述了一个单独的异步 I/O 请求。

  • 数组的大小至少为 nr。

5. struct iocb 结构体

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 这是 linux/aio_abi.h 中定义的简化版结构
struct iocb {
__u64 aio_data; // 用户定义的数据,通常用于匹配请求和完成事件
__u32 aio_key, aio_reserved1;

__u16 aio_lio_opcode; // 操作类型:IOCB_CMD_PREAD, IOCB_CMD_PWRITE 等
__s16 aio_reqprio; // 请求优先级 (通常保留为 0)
__u32 aio_fildes; // 文件描述符

__u64 aio_buf; // 缓冲区地址 (用户空间指针)
__u64 aio_nbytes; // 传输字节数
__s64 aio_offset; // 文件偏移量
// ... 还有其他字段,用于更高级的功能
};

关键字段:

aio_lio_opcode: 指定操作类型。

  • IOCB_CMD_PREAD: 异步预读(Positioned Read)。

  • IOCB_CMD_PWRITE: 异步预写(Positioned Write)。

  • IOCB_CMD_FSYNC: 异步同步文件数据和元数据。

  • IOCB_CMD_FDSYNC: 异步同步文件数据。

aio_fildes: 进行 I/O 操作的文件描述符。

aio_buf: 用户空间缓冲区的地址。

aio_nbytes: 要读取或写入的字节数。

aio_offset: 文件中的偏移量(类似 pread/pwrite)。

aio_data: 用户自定义数据。当这个请求完成后,对应的完成事件 (io_event) 会包含这个值,方便你识别是哪个请求完成了。

6. 返回值

io_setup:

  • 成功: 返回 0。

  • 失败: 返回 -1,并设置 errno。

io_submit:

  • 成功: 返回实际提交成功的请求数(一个非负整数,可能小于或等于 nr)。

  • 失败: 返回 -1,并设置 errno。如果返回值是 0 到 nr 之间的正数 m,则表示只有前 m 个请求被成功提交,后面的请求提交失败。

7. 错误码 (errno)

io_setup

  • EAGAIN: 内核资源不足,无法创建新的上下文,或者达到用户/系统范围内的 AIO 上下文数量限制。

  • EINVAL: nr_events 为 0。

  • ENOMEM: 内存不足。

io_submit

  • EAGAIN: 资源暂时不可用,例如提交队列已满。

  • EBADF: ctx_id 无效,或者 iocbpp 中某个 iocb 的 aio_fildes 是无效的文件描述符。

  • EINVAL: ctx_id 无效,或者 iocbpp 中某个 iocb 的参数无效(例如 aio_lio_opcode 未知)。

  • ENOMEM: 内存不足。

8. 相似函数或关联函数

  • io_getevents: 用于从异步 I/O 上下文中获取已完成的 I/O 事件。

  • io_destroy: 用于销毁一个异步 I/O 上下文。

  • io_cancel: 用于尝试取消一个已提交但尚未完成的 I/O 请求。

  • struct io_context_t: 异步 I/O 上下文的类型。

  • struct iocb: 描述单个异步 I/O 请求的结构体。

  • struct io_event: 描述单个已完成 I/O 事件的结构体。

  • io_uring: Linux 5.1+ 引入的更现代、更高效的异步 I/O 接口。

9. 示例代码

下面的示例演示了如何使用 io_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 的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#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&#91;num_ops];
struct iocb *iocb_ptrs&#91;num_ops];
char write_buffers&#91;2]&#91;chunk_size];
char read_buffers&#91;2]&#91;chunk_size];
struct io_event events&#91;num_ops];
int ret, i;

printf("--- Demonstrating io_setup and io_submit (Linux AIO) ---\n");

// 1. 初始化要写入的数据
memset(write_buffers&#91;0], 'A', chunk_size);
memset(write_buffers&#91;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&#91;0], fd, write_buffers&#91;0], chunk_size, 0);
prep_pwrite(&iocbs&#91;1], fd, write_buffers&#91;1], chunk_size, chunk_size);
iocb_ptrs&#91;0] = &iocbs&#91;0];
iocb_ptrs&#91;1] = &iocbs&#91;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&#91;i].res < 0) {
fprintf(stderr, "Write error: %s\n", strerror(-events&#91;i].res));
} else {
printf("Write %d completed: %lld bytes written.\n", i+1, (long long)events&#91;i].res);
}
}
}

// 7. 准备读取请求
prep_pread(&iocbs&#91;2], fd, read_buffers&#91;0], chunk_size, 0);
prep_pread(&iocbs&#91;3], fd, read_buffers&#91;1], chunk_size, chunk_size);
iocb_ptrs&#91;0] = &iocbs&#91;2]; // 重用指针数组
iocb_ptrs&#91;1] = &iocbs&#91;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&#91;i].res < 0) {
fprintf(stderr, "Read error: %s\n", strerror(-events&#91;i].res));
} else {
printf("Read %d completed: %lld bytes read.\n", i+1, (long long)events&#91;i].res);
// 简单验证读取的数据
char expected_char = (i == 0) ? 'A' : 'B';
if (((char*)(events&#91;i].data))&#91;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. 编译和运行

1
2
3
4
5
6
# 假设代码保存在 aio_setup_submit_example.c 中
gcc -o aio_setup_submit_example aio_setup_submit_example.c

# 运行程序
./aio_setup_submit_example

11. 预期输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
--- 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:描述单个异步请求(操作类型、文件、缓冲区、偏移等)。

工作流程:

调用 io_setup 创建上下文。

为每个 I/O 操作填充一个 iocb 结构体。

调用 io_submit 提交这些 iocb。

(稍后)调用 io_getevents 获取完成状态。

(最后)调用 io_destroy 销毁上下文。

局限性:

  • 传统 Linux AIO 对 buffered 文件 I/O 支持不佳,性能可能不理想。

  • API 相对底层和复杂。

现代替代:对于新的高性能异步 I/O 应用,强烈推荐使用 io_uring,它提供了更强大、更易用、性能更好的异步 I/O 接口。

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

io_submit系统调用及示例

io_submit系统调用及示例

1. 函数介绍

在使用 io_setup 创建了异步 I/O 上下文之后,下一步就是向这个上下文提交实际的 I/O 请求。

io_submit 系统调用的作用就是将一个或多个异步 I/O 请求提交到指定的异步 I/O 上下文中。每个请求都由一个 struct iocb(I/O Control Block)结构体描述,该结构体包含了操作类型(读/写/同步)、文件描述符、缓冲区地址、读写字节数、文件偏移量等所有必需的信息。

提交后,内核会接管这些请求,并在后台(可能使用专门的线程或机制)执行这些 I/O 操作。调用 io_submit 的进程可以立即继续执行,无需等待 I/O 完成。

简单来说,io_submit 就是把写好的“异步任务清单”(iocb 结构体)交给之前创建的“任务管理器”(io_context_t),让它开始执行这些任务。

2. 函数原型

1
2
3
4
5
6
7
8
9
// 需要定义宏来启用 AIO 相关定义
#define _GNU_SOURCE
#include <linux/aio_abi.h> // 包含 iocb 等定义
#include <sys/syscall.h> // 包含系统调用号
#include <unistd.h> // 包含 syscall 函数

// io_submit 系统调用的实际接口
long syscall(SYS_io_submit, io_context_t ctx_id, long nr, struct iocb **iocbpp);

注意:这也是一个底层系统调用,通常需要通过 syscall() 函数调用。

3. 功能

将 nr 个异步 I/O 请求(由 iocbpp 指向的数组描述)提交到由 ctx_id 标识的异步 I/O 上下文中。内核会尝试立即开始处理这些请求。

4. 参数

ctx_id:

  • io_context_t 类型。

  • 由 io_setup 返回的、有效的异步 I/O 上下文的标识符。

nr:

  • long 类型。

  • 指定要提交的异步 I/O 请求数量。这个值应该与 iocbpp 数组的大小相对应。

iocbpp:

  • struct iocb ** 类型。

  • 一个指针数组,数组中的每个元素都指向一个 struct iocb 结构体。struct iocb 描述了一个单独的异步 I/O 请求。

  • 数组的大小至少为 nr。

5. struct iocb 结构体 (关键部分)

这是描述单个异步 I/O 请求的核心结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 简化版,实际定义在 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: 异步预读(指定偏移量的读取)。

  • IOCB_CMD_PWRITE: 异步预写(指定偏移量的写入)。

  • IOCB_CMD_FSYNC: 异步文件数据和元数据同步。

  • IOCB_CMD_FDSYNC: 异步文件数据同步。

aio_fildes: 进行 I/O 操作的目标文件描述符。

aio_buf: 用户空间缓冲区的地址(读取时存放数据,写入时提供数据)。

aio_nbytes: 要传输(读取或写入)的字节数。

aio_offset: 文件中的偏移量(类似 pread/pwrite)。

aio_data: 用户自定义数据。当这个请求完成后,对应的完成事件 (io_event) 会包含这个值,方便程序识别是哪个请求完成了。

6. 返回值

  • 成功: 返回实际成功提交的请求数(一个非负整数,可能小于或等于 nr)。

  • 失败: 返回 -1,并设置 errno。如果返回一个 0 到 nr 之间的正数 m,则表示只有数组中前 m 个请求被成功提交,后面的提交失败了。

7. 错误码 (errno)

  • EAGAIN: 资源暂时不可用,例如内核的提交队列已满。

  • EBADF: ctx_id 无效,或者 iocbpp 中某个 iocb 的 aio_fildes 是无效的文件描述符。

  • EINVAL: ctx_id 无效,或者 iocbpp 中某个 iocb 的参数无效(例如 aio_lio_opcode 未知,或 nr 为负数)。

  • ENOMEM: 内存不足。

8. 相似函数或关联函数

  • io_setup: 创建异步 I/O 上下文,是 io_submit 的前置步骤。

  • io_getevents: 用于获取已提交请求的完成状态(事件)。

  • io_cancel: 尝试取消一个已提交但尚未完成的 I/O 请求。

  • struct iocb: 描述单个异步 I/O 请求的结构体。

9. 示例代码

下面的示例演示如何使用 io_setup 创建上下文,然后使用 io_submit 提交异步写入请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#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 my_io_setup(unsigned nr_events, io_context_t *ctxp) {
return syscall(__NR_io_setup, nr_events, ctxp);
}

// 封装 io_destroy 系统调用
static inline int my_io_destroy(io_context_t ctx) {
return syscall(__NR_io_destroy, ctx);
}

// 封装 io_submit 系统调用
static inline int my_io_submit(io_context_t ctx, long nr, struct iocb **iocbpp) {
return syscall(__NR_io_submit, ctx, nr, iocbpp);
}

// 辅助函数:初始化一个异步写入的 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 = "io_submit_test_file.txt";
const int num_writes = 3;
const size_t chunk_size = 1024;
int fd;
io_context_t ctx = 0; // 必须初始化为 0
struct iocb iocbs&#91;num_writes];
struct iocb *iocb_ptrs&#91;num_writes];
char buffers&#91;num_writes]&#91;chunk_size];
int ret, i;

printf("--- Demonstrating io_submit ---\n");

// 1. 初始化要写入的数据
for (i = 0; i < num_writes; ++i) {
memset(buffers&#91;i], 'A' + i, chunk_size); // Fill with 'A', 'B', 'C'
}

// 2. 创建并打开文件
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
printf("1. Opened/created file '%s' (fd=%d)\n", filename, fd);

// 3. 初始化异步 I/O 上下文
ret = my_io_setup(num_writes, &ctx);
if (ret < 0) {
perror("io_setup");
close(fd);
exit(EXIT_FAILURE);
}
printf("2. Initialized AIO context (ctx_id=%llu)\n", (unsigned long long)ctx);

// 4. 准备 I/O 请求 (iocb)
printf("3. Preparing %d asynchronous write requests...\n", num_writes);
for (i = 0; i < num_writes; ++i) {
prep_pwrite(&iocbs&#91;i], fd, buffers&#91;i], chunk_size, i * chunk_size);
iocb_ptrs&#91;i] = &iocbs&#91;i];
printf(" Prepared write %d: offset=%zu, size=%zu, data='%c'...\n",
i+1, i * chunk_size, chunk_size, 'A' + i);
}

// 5. 提交 I/O 请求
printf("4. Submitting %d write requests using io_submit...\n", num_writes);
ret = my_io_submit(ctx, num_writes, iocb_ptrs);
if (ret != num_writes) {
fprintf(stderr, " io_submit failed: submitted %d requests, expected %d\n", ret, num_writes);
if (ret < 0) {
perror(" io_submit error");
} else {
printf(" Only the first %d requests were submitted successfully.\n", ret);
}
// 清理并退出
my_io_destroy(ctx);
close(fd);
unlink(filename);
exit(EXIT_FAILURE);
}

printf(" io_submit succeeded. All %d requests submitted.\n", ret);

// 6. 注意:此时写入操作可能仍在进行中,我们需要用 io_getevents 来等待完成
// 这个例子只演示提交,不等待完成。
printf("5. Note: io_submit returned immediately. The writes are happening in the background.\n");
printf(" To get the results, you need to call io_getevents().\n");

// 7. 清理资源 (在真实程序中,你应该在 io_getevents 确认完成后再关闭文件)
printf("6. Cleaning up resources...\n");
my_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_submit(ctx_id, nr, iocb_ptrs) submits 'nr' AIO requests to the context 'ctx_id'.\n");
printf("2. Each request is described by an 'iocb' struct, pointed to by elements in 'iocb_ptrs'.\n");
printf("3. It returns the number of requests successfully submitted (may be < nr on partial failure).\n");
printf("4. It returns immediately; the I/O happens asynchronously in the background.\n");
printf("5. Use io_getevents() afterwards to check for completion and get results.\n");

return 0;
}

10. 编译和运行

1
2
3
4
5
6
# 假设代码保存在 io_submit_example.c 中
gcc -o io_submit_example io_submit_example.c

# 运行程序
./io_submit_example

11. 预期输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--- Demonstrating io_submit ---
1. Opened/created file 'io_submit_test_file.txt' (fd=3)
2. Initialized AIO context (ctx_id=123456789)
3. Preparing 3 asynchronous write requests...
Prepared write 1: offset=0, size=1024, data='A'...
Prepared write 2: offset=1024, size=1024, data='B'...
Prepared write 3: offset=2048, size=1024, data='C'...
4. Submitting 3 write requests using io_submit...
io_submit succeeded. All 3 requests submitted.
5. Note: io_submit returned immediately. The writes are happening in the background.
To get the results, you need to call io_getevents().
6. Cleaning up resources...
Destroyed AIO context.
Closed file descriptor.
Deleted test file 'io_submit_test_file.txt'.

--- Summary ---
1. io_submit(ctx_id, nr, iocb_ptrs) submits 'nr' AIO requests to the context 'ctx_id'.
2. Each request is described by an 'iocb' struct, pointed to by elements in 'iocb_ptrs'.
3. It returns the number of requests successfully submitted (may be < nr on partial failure).
4. It returns immediately; the I/O happens asynchronously in the background.
5. Use io_getevents() afterwards to check for completion and get resu

accept4系统调用及示例

1. 函数介绍

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

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

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

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

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

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

2. 函数原型

1
2
3
4
5
6
#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 是 NULL,addrlen 也必须是 NULL。

flags:

  • int 类型。

一个位掩码,用于设置新创建的已连接套接字的属性。可以是以下值的按位或 (|) 组合:

  • SOCK_NONBLOCK: 为新套接字设置非阻塞模式。这样,后续在这个新套接字上的 I/O 操作(如 read, write)如果无法立即完成,不会阻塞,而是返回错误 EAGAIN 或 EWOULDBLOCK。

  • SOCK_CLOEXEC: 为新套接字设置执行时关闭(Close-on-Exec)标志 (FD_CLOEXEC)。这确保了当程序调用 exec() 系列函数执行新程序时,这个新套接字会被自动关闭,防止它被新程序意外继承。这是一个重要的安全和资源管理特性。

5. 返回值

  • 成功: 返回一个新的、非负的文件描述符,它代表了与客户端通信的已连接套接字。服务器应该使用这个新的文件描述符与客户端进行 read/write 等操作。

  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 错误码 (errno)

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

  • EAGAIN 或 EWOULDBLOCK: (对于非阻塞套接字) 监听队列中当前没有已完成的连接。

  • EBADF: sockfd 不是有效的文件描述符。

  • ECONNABORTED: 连接已被客户端中止。

  • EFAULT: addr 参数指向了进程无法访问的内存地址。

  • EINTR: 系统调用被信号中断。

  • EINVAL: 套接字没有处于监听状态,或者 flags 参数包含无效标志。

  • EMFILE: 进程已打开的文件描述符数量达到上限 (RLIMIT_NOFILE)。

  • ENFILE: 系统已打开的文件描述符数量达到上限。

  • ENOMEM: 内核内存不足。

  • ENOBUFS: 网络子系统内存不足。

  • ENOTSOCK: sockfd 不是一个套接字。

  • 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 标志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#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&#91;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&#91;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. 编译和运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 假设代码保存在 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. 预期输出

服务器终端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--- 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):

1
2
3
4
5
6
7
8
9
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 及其标志的使用,是编写健壮、高效网络服务的重要一步。

Flask+YARA-Python实现文件扫描功能

以下是一个 完整的 Web API 示例,使用 Flask + YARA-Python 实现文件扫描功能,支持上传文件并返回 YARA 规则匹配结果。

✅ 功能说明

  • 提供一个 /scan 接口,支持文件上传

  • 使用预加载的 YARA 规则进行扫描

  • 返回 JSON 格式的匹配结果

  • 支持多规则、可扩展

📦 项目结构

1
2
3
4
5
6
7
8
yara-flask-api/
├── app.py # Flask 主程序
├── rules/ # YARA 规则目录
│ ├── hello.yar
│ └── suspicious_pe.yar
├── uploads/ # 临时存储上传文件(可选)
└── requirements.txt

  1. 安装依赖

创建 requirements.txt:

1
2
3
flask
yara-python

安装:

1
2
pip install -r requirements.txt

确保系统已安装 YARA 开发库:

Ubuntu: sudo apt-get install yara libyara-dev

macOS: brew install yara

  1. 编写 YARA 规则

rules/hello.yar

1
2
3
4
5
6
7
8
rule ContainsHello
{
strings:
$hello = "Hello" ascii nocase
condition:
$hello
}

rules/suspicious_pe.yar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "pe"

rule SuspiciousPEScan
{
meta:
description = "Detects common suspicious PE imports"

strings:
$create_remote_thread = "CreateRemoteThread" fullword ascii
$write_process_memory = "WriteProcessMemory" fullword ascii

condition:
pe.is_pe and
any of them
}

  1. Flask Web API 主程序 (app.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import os
import yara
from flask import Flask, request, jsonify
from werkzeug.utils import secure_filename

# 初始化 Flask 应用
app = Flask(__name__)
app.config&#91;'UPLOAD_FOLDER'] = 'uploads'
app.config&#91;'MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 # 10MB 限制

# 确保目录存在
os.makedirs(app.config&#91;'UPLOAD_FOLDER'], exist_ok=True)
os.makedirs('rules', exist_ok=True)

# 编译所有 .yar 规则
def load_yara_rules():
try:
rule_files = {}
for filename in os.listdir('rules'):
if filename.endswith('.yar'):
filepath = os.path.join('rules', filename)
rule_files&#91;f"rule_{filename}"] = filepath
rules = yara.compile(filepaths=rule_files)
print(f"&#91;+] 成功加载 {len(rule_files)} 条 YARA 规则")
return rules
except yara.Error as e:
print(f"&#91;-] YARA 规则编译失败: {e}")
return None

# 全局加载规则
yara_rules = load_yara_rules()

if not yara_rules:
print("&#91;-] 无法启动:YARA 规则加载失败")
exit(1)

# 根路径
@app.route('/')
def index():
return '''
<h3>YARA 扫描 API 服务</h3>
<p>使用 POST /scan 上传文件进行扫描</p>
'''

# 扫描接口
@app.route('/scan', methods=&#91;'POST'])
def scan_file():
if 'file' not in request.files:
return jsonify({"error": "未提供文件字段 'file'"}), 400

file = request.files&#91;'file']
if file.filename == '':
return jsonify({"error": "未选择文件"}), 400

if file:
filename = secure_filename(file.filename)
filepath = os.path.join(app.config&#91;'UPLOAD_FOLDER'], filename)
file.save(filepath)

try:
# 执行 YARA 扫描
matches = yara_rules.match(filepath)

result = {
"filename": filename,
"matches": &#91;]
}

for match in matches:
indicators = &#91;]
for string in match.strings:
indicators.append({
"offset": f"0x{string&#91;0]:X}",
"identifier": string&#91;1],
"data": string&#91;2].decode('utf-8', errors='replace')
})
result&#91;"matches"].append({
"rule": match.rule,
"tags": match.tags,
"indicators": indicators
})

os.remove(filepath) # 扫描后删除文件(可选)
return jsonify(result), 200

except Exception as e:
os.remove(filepath)
return jsonify({"error": f"扫描出错: {str(e)}"}), 500

return jsonify({"error": "未知错误"}), 500

# 启动服务
if __name__ == '__main__':
print("🚀 启动 YARA 扫描服务 http://127.0.0.1:5000")
app.run(host='0.0.0.0', port=5000, debug=False)

  1. 启动服务
1
2
python app.py

服务将运行在:http://127.0.0.1:5000

  1. 测试 API(使用 curl)

测试文本文件

1
2
3
echo "Hello, this is a test." > test.txt
curl -X POST -F "file=@test.txt" http://127.0.0.1:5000/scan

✅ 预期输出(匹配 ContainsHello):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"filename": "test.txt",
"matches": &#91;
{
"rule": "ContainsHello",
"tags": &#91;],
"indicators": &#91;
{
"offset": "0x0",
"identifier": "$hello",
"data": "Hello"
}
]
}
]
}

测试 PE 文件(如 exe)

1
2
curl -X POST -F "file=@malware.exe" http://127.0.0.1:5000/scan

如果该 PE 文件调用了 CreateRemoteThread,会触发 SuspiciousPEScan 规则。

总结

这个 Flask + YARA 的 Web API 示例可以:

  • 快速集成到 SOC、EDR、文件网关等系统

  • 用于自动化恶意软件检测流水线

  • 作为威胁情报分析的后端引擎

Flask YARA 文件扫描实现, Flask Web API 文件检测示例, YARA-Python 文件分析教程, 使用 Flask 和 YARA 开发扫描工具, Flask 集成 YARA 实现恶意文件检测, Python YARA 文件扫描代码, Web API 文件扫描 Flask 实现, YARA-Python 恶意软件检测方法, Flask 构建文件扫描服务教程, Python 实现文件内容匹配技术

libmagic库使用流程

✅ 正确流程:使用自定义 magic 的标准做法

1
2
3
4
5
6
7
8
编写 sat.magic(纯规则) 

file -C -m sat.magic → 生成 magic.mgc(二进制数据库)

C 程序中 magic_load("magic.mgc") → 成功加载

调用 magic_file() → 正确识别

✅ 第一步:创建正确的 sat.magic(仅规则,无扩展字段)

1
2
3
cd ~/satellite-analysis-asscii/smagic
nano sat.magic

✅ 内容(严格兼容 file -C):

1
2
3
4
5
6
7
8
9
10
11
12
13
# Satellite Telemetry Frame
0 belong 0xAA55CCDD
>8 string \x01\x02\x03\x04
>8 string SAT-TELEMETRY
# (Satellite Telemetry Packet)

# GBK Chinese Text Detection
0 byte > 0xA0
>&0 byte < 0xFF
>1 byte > 0xA0
>&1 byte < 0xFF
# (Chinese GBK Text)

✅ 关键:

不要写 name=, desc=, mime=

注释用 # (Description) 格式

使用英文或 ASCII

✅ 第二步:编译生成 magic.mgc

1
2
file -C -m sat.magic

✅ 正确输出:

1
2
Creating magic.mgc from sat.magic

👉 生成了 magic.mgc,这是 唯一能被 magic_load() 正确加载的文件

✅ 第三步:修改你的 C 程序,加载 magic.mgc

你的 a.out 是从某个 .c 文件编译来的,假设是 detect_encoding.c。

编辑它:

1
2
vim detect_encoding.c

修改 magic_load 部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 原代码(错误):
// if (magic_load(magic, NULL) != 0) { ... }

// 新代码:先加载默认库,再加载自定义库
if (magic_load(magic, NULL) != 0) {
fprintf(stderr, "Error loading default magic: %s\n", magic_error(magic));
magic_close(magic);
exit(1);
}

// 加载自定义 magic.mgc
if (magic_load(magic, "./smagic/magic.mgc") != 0) {
fprintf(stderr, "Error loading custom magic: %s\n", magic_error(magic));
magic_close(magic);
exit(1);
}

✅ 注意路径:./smagic/magic.mgc

✅ 第四步:重新编译并运行

1
2
gcc -o a.out detect_encoding.c -lmagic

生成测试文件(确保有 GBK 文本)

1
2
3
4
5
6
7
8
9
cd ~/satellite-analysis-asscii

# 生成 GBK 编码文件
echo "卫星状态:正常
时间:2025-04-05
消息:系统在线" > gbk.txt

iconv -f UTF-8 -t GBK gbk.txt > gbk.bin

运行程序

1
2
./a.out gbk.bin

✅ 预期输出:

1
2
3
4
5
6
🔍 启动 libmagic 文本编码识别(C 语言版)
------------------------------------------------

📄 gbk.bin:
检测编码: iso-8859-1 → ISO-8859-1(西欧)

⚠️ 注意:libmagic 仍可能将 GBK 误判为 iso-8859-1,因为两者都是“可打印扩展 ASCII”,但至少 你的自定义 magic 已正确加载。

✅ 如何让 GBK 被正确识别?—— 使用 cchardet

libmagic 不擅长文本编码识别。你应该:

1. 安装 cchardet

1
2
pip3 install cchardet

2. 创建 detect_gbk.py

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
import cchardet
import sys

with open(sys.argv&#91;1], 'rb') as f:
raw = f.read()
result = cchardet.detect(raw)
print(f"文件: {sys.argv&#91;1]}")
print(f"检测编码: {result&#91;'encoding']}")
print(f"置信度: {result&#91;'confidence']:.2f}")

3. 运行

1
2
python3 detect_gbk.py gbk.bin

✅ 输出:

1
2
3
检测编码: GBK
置信度: 0.99

🎯 成功识别!

✅ 最终建议:分工明确

任务工具识别文件类型(PNG/GZIP/ELF)✅ libmagic(C)识别文本编码(UTF-8/GBK/ASCII)✅ cchardet(Python)识别自定义协议帧(0xAA55…)✅ libmagic + 自定义 magic.mgc

🎯 你现在可以:

  • 用 file -C 生成 magic.mgc

  • 用 C 程序加载它,识别自定义卫星帧

  • 用 Python + cchardet 精确识别 GBK 编码

Linux I/O 多路复用机制对比分析poll/ppoll/epoll/select

Linux I/O 多路复用机制对比分析poll/ppoll/epoll/select

  1. 概述

I/O 多路复用是现代高性能网络编程的核心技术,它允许单个线程同时监视多个文件描述符的状态变化,从而实现高效的并发处理。Linux 提供了多种 I/O 多路复用机制,每种都有其特点和适用场景。

本文将深入分析四种主要的 I/O 多路复用机制:select、poll、ppoll 和 epoll,并通过实际示例展示它们的使用方法和性能差异。

  1. 四种机制对比分析

2.1 基本特性对比

特性selectpollppollepoll引入时间早期UnixSVR3 (1986)Linux 2.6.16Linux 2.5.44文件描述符限制FD_SETSIZE (通常1024)无理论限制无理论限制无理论限制数据结构fd_set位图pollfd数组pollfd数组epoll_event数组文件描述符拷贝每次调用都拷贝每次调用都拷贝每次调用都拷贝注册一次,多次使用事件复杂度O(n)O(n)O(n)O(1)跨平台性良好良好Linux特有Linux特有信号处理基本支持基本支持增强支持基本支持

2.2 详细特性分析

select

  • 优点: 跨平台性最好,几乎所有Unix-like系统都支持

  • 缺点: 文件描述符数量受限,每次调用都需要拷贝fd_set

poll

  • 优点: 无文件描述符数量限制,API设计更清晰

  • 缺点: 每次调用都需要遍历所有文件描述符

ppoll

  • 优点: 提供了更好的信号处理机制,避免竞态条件

  • 缺点: Linux特有,需要较新内核支持

epoll

  • 优点: 性能最优,事件驱动,支持边缘触发和水平触发

  • 缺点: Linux特有,学习成本较高

  1. 实际示例代码

3.1 select 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>

#define MAX_CLIENTS 1024
#define BUFFER_SIZE 1024

int main_select() {
fd_set read_fds, master_fds;
int max_fd = 0;
int client_sockets&#91;MAX_CLIENTS] = {0};
char buffer&#91;BUFFER_SIZE];

printf("=== select 示例 ===\n");

// 初始化文件描述符集合
FD_ZERO(&master_fds);
FD_SET(STDIN_FILENO, &master_fds);
max_fd = STDIN_FILENO;

while (1) {
read_fds = master_fds;
struct timeval timeout = {1, 0}; // 1秒超时

int activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout);

if (activity < 0) {
if (errno == EINTR) continue;
perror("select error");
break;
}

if (activity == 0) {
printf("select timeout\n");
continue;
}

// 检查标准输入
if (FD_ISSET(STDIN_FILENO, &read_fds)) {
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("stdin: %s", buffer);

if (strncmp(buffer, "quit", 4) == 0) {
break;
}
}
}
}

return 0;
}

3.2 poll 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <string.h>

#define MAX_FDS 10
#define TIMEOUT_MS 5000 // 5秒超时

int main_poll() {
struct pollfd fds&#91;MAX_FDS];
int nfds = 1;
char buffer&#91;1024];

printf("=== poll 示例 ===\n");

// 初始化 pollfd 结构
fds&#91;0].fd = STDIN_FILENO;
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

printf("监视标准输入,输入 'quit' 退出\n");

while (1) {
int ready = poll(fds, nfds, TIMEOUT_MS);

if (ready == -1) {
if (errno == EINTR) continue;
perror("poll error");
break;
}

if (ready == 0) {
printf("poll timeout\n");
continue;
}

// 处理就绪的文件描述符
for (int i = 0; i < nfds; i++) {
if (fds&#91;i].revents & POLLIN) {
if (fds&#91;i].fd == STDIN_FILENO) {
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("received: %s", buffer);

if (strncmp(buffer, "quit", 4) == 0) {
return 0;
}
}
}
}

if (fds&#91;i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("fd %d error\n", fds&#91;i].fd);
return 1;
}
}
}

return 0;
}

3.3 ppoll 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

volatile sig_atomic_t signal_received = 0;

void signal_handler(int sig) {
signal_received = sig;
printf("\nSignal %d received\n", sig);
}

int main_ppoll() {
struct pollfd fds&#91;2];
struct timespec timeout;
sigset_t sigmask;
char buffer&#91;1024];

printf("=== ppoll 示例 ===\n");

// 设置信号处理
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);

// 初始化 pollfd
fds&#91;0].fd = STDIN_FILENO;
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

// 设置超时时间
timeout.tv_sec = 3;
timeout.tv_nsec = 0;

// 设置信号屏蔽集
sigemptyset(&sigmask);

printf("Monitoring stdin with ppoll...\n");
printf("Press Ctrl+C to send signal\n");
printf("Type 'quit' to exit\n");

while (!signal_received) {
int ready = ppoll(fds, 1, &timeout, &sigmask);

if (ready == -1) {
if (errno == EINTR) {
printf("ppoll interrupted by signal\n");
if (signal_received) {
printf("Signal handling complete\n");
}
continue;
} else {
perror("ppoll error");
break;
}
}

if (ready == 0) {
printf("ppoll timeout\n");
continue;
}

if (fds&#91;0].revents & POLLIN) {
ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf("Input: %s", buffer);

if (strncmp(buffer, "quit", 4) == 0) {
break;
}
}
}

fds&#91;0].revents = 0; // 重置事件
}

printf("Program exiting normally\n");
return 0;
}

3.4 epoll 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#define MAX_EVENTS 10
#define BUFFER_SIZE 1024

int make_socket_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main_epoll() {
int epoll_fd;
struct epoll_event ev, events&#91;MAX_EVENTS];
char buffer&#91;BUFFER_SIZE];

printf("=== epoll 示例 ===\n");

// 创建 epoll 实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
return 1;
}

// 设置标准输入为非阻塞
if (make_socket_nonblocking(STDIN_FILENO) == -1) {
perror("fcntl");
close(epoll_fd);
return 1;
}

// 添加标准输入到 epoll
ev.events = EPOLLIN | EPOLLET; // 边缘触发模式
ev.data.fd = STDIN_FILENO;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
perror("epoll_ctl: stdin");
close(epoll_fd);
return 1;
}

printf("epoll monitoring stdin (edge-triggered mode)\n");
printf("Type 'quit' to exit\n");

while (1) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, 3000); // 3秒超时

if (nfds == -1) {
if (errno == EINTR) continue;
perror("epoll_wait");
break;
}

if (nfds == 0) {
printf("epoll timeout\n");
continue;
}

for (int n = 0; n < nfds; n++) {
if (events&#91;n].events & EPOLLIN) {
if (events&#91;n].data.fd == STDIN_FILENO) {
ssize_t bytes_read;
while ((bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1)) > 0) {
buffer&#91;bytes_read] = '\0';
printf("epoll input: %s", buffer);

if (strncmp(buffer, "quit", 4) == 0) {
close(epoll_fd);
return 0;
}
}

if (bytes_read == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read");
}
}
}
}

if (events&#91;n].events & (EPOLLERR | EPOLLHUP)) {
printf("epoll error on fd %d\n", events&#91;n].data.fd);
close(epoll_fd);
return 1;
}
}
}

close(epoll_fd);
return 0;
}

  1. 性能测试对比

4.1 基准测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <string.h>

#define TEST_ITERATIONS 10000
#define TEST_FDS 100

// 性能测试结构体
struct performance_test {
const char *name;
long long (*test_func)(int fd_count);
};

// select 性能测试
long long test_select_performance(int fd_count) {
fd_set read_fds;
struct timeval timeout = {0, 1000}; // 1ms 超时
struct timeval start, end;

gettimeofday(&start, NULL);

for (int i = 0; i < TEST_ITERATIONS; i++) {
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds);

select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout);
}

gettimeofday(&end, NULL);

return (end.tv_sec - start.tv_sec) * 1000000LL + (end.tv_usec - start.tv_usec);
}

// poll 性能测试
long long test_poll_performance(int fd_count) {
struct pollfd pfd;
struct timeval start, end;

pfd.fd = STDIN_FILENO;
pfd.events = POLLIN;
pfd.revents = 0;

gettimeofday(&start, NULL);

for (int i = 0; i < TEST_ITERATIONS; i++) {
poll(&pfd, 1, 1); // 1ms 超时
pfd.revents = 0;
}

gettimeofday(&end, NULL);

return (end.tv_sec - start.tv_sec) * 1000000LL + (end.tv_usec - start.tv_usec);
}

// 显示性能测试结果
void show_performance_results() {
struct performance_test tests&#91;] = {
{"select", test_select_performance},
{"poll", test_poll_performance},
{NULL, NULL}
};

printf("=== 性能测试结果 (10000 次调用) ===\n");
printf("%-10s %-15s %-15s\n", "机制", "耗时(微秒)", "平均耗时(纳秒)");
printf("%-10s %-15s %-15s\n", "----", "----------", "--------------");

for (int i = 0; tests&#91;i].name; i++) {
long long total_time = tests&#91;i].test_func(TEST_FDS);
double avg_time = (double)total_time * 1000.0 / TEST_ITERATIONS;

printf("%-10s %-15lld %-15.2f\n",
tests&#91;i].name, total_time, avg_time);
}

printf("\n性能特点:\n");
printf("1. select: 有文件描述符数量限制,每次调用需要拷贝fd_set\n");
printf("2. poll: 无文件描述符数量限制,但仍需遍历所有描述符\n");
printf("3. epoll: 事件驱动,只处理活跃的描述符,性能最优\n");
printf("4. ppoll: 提供更好的信号处理机制,避免竞态条件\n");
}

int main_performance_comparison() {
printf("=== I/O 多路复用性能对比测试 ===\n\n");

show_performance_results();

printf("\n=== 实际应用建议 ===\n");
printf("选择建议:\n");
printf("1. 跨平台应用: 使用 select\n");
printf("2. 中等并发(<1000): 使用 poll\n");
printf("3. 高并发(>1000): 使用 epoll\n");
printf("4. 需要信号处理: 使用 ppoll\n");
printf("5. Linux 专用: 使用 epoll\n");

return 0;
}

  1. 实际应用场景分析

5.1 网络服务器场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
// 简单的 HTTP 服务器示例,展示不同机制的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <poll.h>
#include <sys/epoll.h>

#define PORT 8080
#define MAX_CLIENTS 1000
#define BUFFER_SIZE 4096

// 创建监听套接字
int create_server_socket(int port) {
int server_fd;
struct sockaddr_in address;
int opt = 1;

if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
return -1;
}

if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
perror("setsockopt");
close(server_fd);
return -1;
}

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);
return -1;
}

if (listen(server_fd, 3) < 0) {
perror("listen");
close(server_fd);
return -1;
}

return server_fd;
}

// 处理 HTTP 请求
void handle_http_request(int client_fd) {
char buffer&#91;BUFFER_SIZE];
ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);

if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';

// 简单的 HTTP 响应
const char *response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n"
"\r\n"
"<html><body><h1>Hello from I/O Multiplexing Server!</h1></body></html>\r\n";

write(client_fd, response, strlen(response));
}

close(client_fd);
}

// 使用 poll 的 HTTP 服务器
int http_server_poll(int port) {
int server_fd, client_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
struct pollfd *fds;
int max_fds = MAX_CLIENTS + 1;
int nfds = 1;

printf("Starting HTTP server with poll on port %d\n", port);

server_fd = create_server_socket(port);
if (server_fd == -1) return -1;

fds = calloc(max_fds, sizeof(struct pollfd));
if (!fds) {
perror("calloc");
close(server_fd);
return -1;
}

// 添加监听套接字
fds&#91;0].fd = server_fd;
fds&#91;0].events = POLLIN;
fds&#91;0].revents = 0;

while (1) {
int ready = poll(fds, nfds, 1000); // 1秒超时

if (ready == -1) {
if (errno == EINTR) continue;
perror("poll");
break;
}

if (ready == 0) continue; // 超时

// 检查监听套接字
if (fds&#91;0].revents & POLLIN) {
client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (client_fd >= 0) {
if (nfds < max_fds) {
fds&#91;nfds].fd = client_fd;
fds&#91;nfds].events = POLLIN;
fds&#91;nfds].revents = 0;
nfds++;
printf("New connection from %s:%d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
} else {
printf("Too many connections, rejecting\n");
close(client_fd);
}
}
}

// 检查客户端连接
for (int i = 1; i < nfds; i++) {
if (fds&#91;i].revents & POLLIN) {
handle_http_request(fds&#91;i].fd);
// 移除已处理的连接
for (int j = i; j < nfds - 1; j++) {
fds&#91;j] = fds&#91;j + 1];
}
nfds--;
i--; // 重新检查当前位置
}
}

// 重置 revents
for (int i = 0; i < nfds; i++) {
fds&#91;i].revents = 0;
}
}

free(fds);
close(server_fd);
return 0;
}

// 使用 epoll 的 HTTP 服务器
int http_server_epoll(int port) {
int server_fd, client_fd, epoll_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
struct epoll_event ev, events&#91;MAX_CLIENTS];
int nfds;

printf("Starting HTTP server with epoll on port %d\n", port);

server_fd = create_server_socket(port);
if (server_fd == -1) return -1;

epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(server_fd);
return -1;
}

// 添加监听套接字到 epoll
ev.events = EPOLLIN;
ev.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) {
perror("epoll_ctl: listen");
close(server_fd);
close(epoll_fd);
return -1;
}

while (1) {
nfds = epoll_wait(epoll_fd, events, MAX_CLIENTS, 1000); // 1秒超时

if (nfds == -1) {
if (errno == EINTR) continue;
perror("epoll_wait");
break;
}

if (nfds == 0) continue; // 超时

for (int i = 0; i < nfds; i++) {
if (events&#91;i].data.fd == server_fd) {
// 新连接
client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
if (client_fd >= 0) {
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {
perror("epoll_ctl: client");
close(client_fd);
} else {
printf("New connection from %s:%d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
}
}
} else {
// 客户端数据
handle_http_request(events&#91;i].data.fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events&#91;i].data.fd, NULL);
}
}
}

close(epoll_fd);
close(server_fd);
return 0;
}

  1. 最佳实践和使用建议

6.1 选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 根据应用场景选择合适的 I/O 多路复用机制

/*
选择决策树:

1. 是否需要跨平台支持?
- 是 -> 选择 select
- 否 -> 继续下一步

2. 操作系统是什么?
- Windows -> 选择 select 或 IOCP
- Linux -> 继续下一步

3. 并发连接数是多少?
- < 100 -> select 或 poll 都可以
- 100-1000 -> poll
- > 1000 -> epoll

4. 是否需要特殊的信号处理?
- 是 -> ppoll
- 否 -> 继续下一步

5. 性能要求如何?
- 高性能 -> epoll
- 一般 -> poll
*/

// 配置结构体
struct io_multiplexing_config {
enum {
IO_SELECT,
IO_POLL,
IO_PPOLL,
IO_EPOLL
} method;

int max_connections;
int timeout_ms;
int edge_triggered; // 仅对 epoll 有效
int signal_safe; // 是否需要信号安全
};

// 根据配置推荐机制
const char* recommend_io_method(const struct io_multiplexing_config *config) {
if (config->method != 0) {
// 明确指定了方法
switch (config->method) {
case IO_SELECT: return "select";
case IO_POLL: return "poll";
case IO_PPOLL: return "ppoll";
case IO_EPOLL: return "epoll";
}
}

// 根据配置自动推荐
if (config->signal_safe) {
return "ppoll";
}

if (config->max_connections > 1000) {
return "epoll";
}

if (config->max_connections > 100) {
return "poll";
}

return "select";
}

// 显示推荐结果
void show_recommendation(const struct io_multiplexing_config *config) {
printf("=== I/O 多路复用机制推荐 ===\n");
printf("配置参数:\n");
printf(" 最大连接数: %d\n", config->max_connections);
printf(" 超时时间: %d ms\n", config->timeout_ms);
printf(" 边缘触发: %s\n", config->edge_triggered ? "是" : "否");
printf(" 信号安全: %s\n", config->signal_safe ? "是" : "否");
printf("\n");
printf("推荐机制: %s\n", recommend_io_method(config));
printf("\n");
}

6.2 错误处理最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 安全的 I/O 多路复用封装
typedef struct {
int fd;
void *data;
int (*read_handler)(int fd, void *data);
int (*write_handler)(int fd, void *data);
int (*error_handler)(int fd, void *data);
} io_handler_t;

// 通用的错误处理函数
int handle_io_errors(int fd, int revents, const char *context) {
if (revents & (POLLERR | POLLHUP | POLLNVAL)) {
fprintf(stderr, "Error on fd %d in %s: ", fd, context);

if (revents & POLLERR) {
fprintf(stderr, "POLLERR ");
}
if (revents & POLLHUP) {
fprintf(stderr, "POLLHUP ");
}
if (revents & POLLNVAL) {
fprintf(stderr, "POLLNVAL ");
}
fprintf(stderr, "\n");

return -1;
}
return 0;
}

// 安全的 poll 封装
int safe_poll(struct pollfd *fds, nfds_t nfds, int timeout_ms) {
if (!fds || nfds == 0) {
errno = EINVAL;
return -1;
}

int result;
do {
result = poll(fds, nfds, timeout_ms);
} while (result == -1 && errno == EINTR);

return result;
}

// 安全的 ppoll 封装
int safe_ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout_ts,
const sigset_t *sigmask) {
if (!fds || nfds == 0) {
errno = EINVAL;
return -1;
}

int result;
do {
result = ppoll(fds, nfds, timeout_ts, sigmask);
} while (result == -1 && errno == EINTR);

return result;
}

// 安全的 epoll 封装
int safe_epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout) {
if (!events || maxevents <= 0) {
errno = EINVAL;
return -1;
}

int result;
do {
result = epoll_wait(epfd, events, maxevents, timeout);
} while (result == -1 && errno == EINTR);

return result;
}

  1. 完整的综合示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/select.h>
#include <sys/epoll.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

// 综合测试结构体
struct comprehensive_test {
const char *name;
void (*test_func)(void);
int supported;
};

// 综合性能测试
void comprehensive_performance_test() {
printf("=== 综合性能测试 ===\n");

// 测试不同文件描述符数量下的性能
int test_sizes&#91;] = {10, 50, 100, 500, 1000};
int num_tests = sizeof(test_sizes) / sizeof(test_sizes&#91;0]);

printf("%-10s %-10s %-15s %-15s %-15s\n",
"机制", "FD数量", "select(μs)", "poll(μs)", "epoll(μs)");
printf("%-10s %-10s %-15s %-15s %-15s\n",
"----", "------", "----------", "---------", "----------");

for (int i = 0; i < num_tests; i++) {
int fd_count = test_sizes&#91;i];
// 这里简化处理,实际应该进行真实的时间测量
printf("%-10s %-10d %-15d %-15d %-15d\n",
"测试", fd_count,
fd_count * 2, // select 模拟时间
fd_count * 1.5, // poll 模拟时间
fd_count * 0.5); // epoll 模拟时间
}
}

// 实际使用场景演示
void practical_usage_scenarios() {
printf("\n=== 实际使用场景 ===\n");

printf("1. Web 服务器:\n");
printf(" - 高并发: 推荐使用 epoll\n");
printf(" - 中等并发: 可以使用 poll\n");
printf(" - 简单场景: select 也足够\n");

printf("\n2. 数据库连接池:\n");
printf(" - 连接数较少: poll 或 select\n");
printf(" - 连接数较多: epoll\n");
printf(" - 需要信号处理: ppoll\n");

printf("\n3. 实时通信应用:\n");
printf(" - 聊天服务器: epoll (支持边缘触发)\n");
printf(" - 游戏服务器: epoll (高性能)\n");
printf(" - 需要精确信号处理: ppoll\n");

printf("\n4. 系统监控工具:\n");
printf(" - 文件监控: poll (简单可靠)\n");
printf(" - 网络监控: epoll (高性能)\n");
printf(" - 需要信号处理: ppoll\n");
}

// 移植性考虑
void portability_considerations() {
printf("\n=== 移植性考虑 ===\n");

printf("跨平台支持:\n");
printf("1. select: 几乎所有 Unix-like 系统都支持\n");
printf("2. poll: POSIX 标准,广泛支持\n");
printf("3. ppoll: Linux 特有 (2.6.16+)\n");
printf("4. epoll: Linux 特有 (2.5.44+)\n");

printf("\n条件编译示例:\n");
printf("#ifdef __linux__\n");
printf(" // 使用 epoll\n");
printf("#elif defined(__FreeBSD__) || defined(__APPLE__)\n");
printf(" // 使用 kqueue\n");
printf("#else\n");
printf(" // 使用 poll 或 select\n");
printf("#endif\n");
}

int main() {
printf("=== Linux I/O 多路复用机制综合分析 ===\n\n");

// 性能测试
comprehensive_performance_test();

// 实际使用场景
practical_usage_scenarios();

// 移植性考虑
portability_considerations();

printf("\n=== 总结 ===\n");
printf("1. select: 简单可靠,跨平台性好,但有 FD 数量限制\n");
printf("2. poll: 无 FD 限制,API 清晰,适合中等并发\n");
printf("3. ppoll: 增强的信号处理,避免竞态条件,Linux 特有\n");
printf("4. epoll: 性能最优,事件驱动,Linux 特有\n");
printf("\n");
printf("选择建议:\n");
printf("- 新项目且只运行在 Linux: 首选 epoll\n");
printf("- 需要跨平台支持: 使用 poll 或 select\n");
printf("- 需要特殊信号处理: 考虑 ppoll\n");
printf("- 简单应用场景: select 也足够\n");

return 0;
}

  1. 编译和运行说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 编译所有示例
gcc -o select_example example1.c
gcc -o poll_example example2.c
gcc -o ppoll_example example3.c -D_GNU_SOURCE
gcc -o epoll_example example4.c
gcc -o performance_test performance_test.c
gcc -o comprehensive_analysis comprehensive.c

# 运行示例
./select_example
./poll_example
./ppoll_example
./epoll_example
./performance_test
./comprehensive_analysis

# 测试不同场景
echo "Testing select with 100 FDs..."
./select_example 100

echo "Testing epoll with high concurrency..."
./epoll_example 1000

echo "Running comprehensive analysis..."
./comprehensive_analysis

  1. 系统要求检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 检查内核版本
uname -r

# 检查 glibc 版本
ldd --version

# 检查 epoll 支持
grep -w epoll /usr/include/linux/eventpoll.h

# 检查 ppoll 支持
grep -w ppoll /usr/include/asm/unistd_64.h

# 查看系统调用限制
ulimit -n # 文件描述符限制
cat /proc/sys/fs/file-max # 系统最大文件描述符

  1. 最佳实践总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// 1. 错误处理模板
int robust_io_multiplexing() {
struct pollfd *fds = NULL;
int max_fds = 1024;
int nfds = 0;

// 分配内存
fds = malloc(max_fds * sizeof(struct pollfd));
if (!fds) {
return -1;
}

// 初始化
memset(fds, 0, max_fds * sizeof(struct pollfd));

// 主循环
while (1) {
int ready;

// 使用安全的 poll 调用
do {
ready = poll(fds, nfds, 1000); // 1秒超时
} while (ready == -1 && errno == EINTR);

if (ready == -1) {
if (errno != EINTR) {
perror("poll error");
break;
}
continue;
}

if (ready == 0) {
// 超时处理
continue;
}

// 处理事件
for (int i = 0; i < nfds; i++) {
if (fds&#91;i].revents != 0) {
// 检查错误
if (handle_io_errors(fds&#91;i].fd, fds&#91;i].revents, "main loop") == -1) {
// 处理错误连接
continue;
}

// 处理正常事件
if (fds&#91;i].revents & POLLIN) {
// 处理可读事件
}

if (fds&#91;i].revents & POLLOUT) {
// 处理可写事件
}
}
}
}

// 清理资源
if (fds) {
free(fds);
}

return 0;
}

// 2. 资源管理模板
typedef struct {
int epoll_fd;
int *client_fds;
int client_count;
int max_clients;
} server_context_t;

int init_server_context(server_context_t *ctx, int max_clients) {
ctx->epoll_fd = -1;
ctx->client_fds = NULL;
ctx->client_count = 0;
ctx->max_clients = max_clients;

// 创建 epoll
ctx->epoll_fd = epoll_create1(0);
if (ctx->epoll_fd == -1) {
return -1;
}

// 分配客户端数组
ctx->client_fds = malloc(max_clients * sizeof(int));
if (!ctx->client_fds) {
close(ctx->epoll_fd);
ctx->epoll_fd = -1;
return -1;
}

return 0;
}

void cleanup_server_context(server_context_t *ctx) {
if (ctx->epoll_fd != -1) {
close(ctx->epoll_fd);
ctx->epoll_fd = -1;
}

if (ctx->client_fds) {
free(ctx->client_fds);
ctx->client_fds = NULL;
}

ctx->client_count = 0;
}

通过以上详细的对比分析和示例代码,我们可以清楚地看到各种 I/O 多路复用机制的特点和适用场景。选择合适的机制对于构建高性能的网络应用程序至关重要。

https://www.calcguide.tech/2025/08/02/linux-io多路复用机制对比分析poll-ppoll-epoll-select/

Linux I/O 系统调用完整对比分析

Linux 提供了丰富的 I/O 系统调用,每种都有其特定的用途和优势。本文将详细分析这些系统调用的特点、使用场景和性能特征。

  1. 概述

Linux 提供了丰富的 I/O 系统调用,每种都有其特定的用途和优势。本文将详细分析这些系统调用的特点、使用场景和性能特征。

  1. 系统调用详细对比

2.1 基本读写函数

pread/pwrite

1
2
3
4
5
6
#include <unistd.h>

// 位置指定读取/写入
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int int fd, const void *buf, size_t count, off_t offset);

特点:

  • 原子操作(读取/写入 + 位置指定)

  • 不改变文件描述符的当前位置

  • 线程安全

read/write

1
2
3
4
5
6
#include <unistd.h>

// 基本读取/写入
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

特点:

  • 最基本的 I/O 操作

  • 会改变文件描述符的当前位置

  • 相对简单但功能有限

2.2 分散/聚集 I/O 函数

preadv/pwritev

1
2
3
4
5
6
#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);

preadv2/pwritev2

1
2
3
4
5
6
7
8
9
#define _GNU_SOURCE
#include <sys/uio.h>

// 带标志的增强版分散/聚集 I/O
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);

2.3 资源限制函数

prlimit64

1
2
3
4
5
6
7
#include <sys/resource.h>

// 获取/设置进程资源限制
int prlimit64(pid_t pid, int resource,
const struct rlimit64 *new_limit,
struct rlimit64 *old_limit);

  1. 功能对比表

函数位置指定分散/聚集标志控制原子性跨平台read/write❌❌❌⚠️✅pread/pwrite✅❌❌✅✅readv/writev❌✅❌⚠️✅preadv/pwritev✅✅❌✅✅preadv2/pwritev2✅✅✅✅❌prlimit64❌❌❌❌✅

  1. 实际示例代码

4.1 基础读写对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <string.h>
#include <errno.h>
#include <time.h>

// 创建测试文件
void create_test_file(const char *filename) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建文件失败");
return;
}

const char *content =
"Line 1: This is the first line of test data.\n"
"Line 2: This is the second line of test data.\n"
"Line 3: This is the third line of test data.\n"
"Line 4: This is the fourth line of test data.\n"
"Line 5: This is the fifth and final line.\n";

write(fd, content, strlen(content));
close(fd);
printf("创建测试文件: %s\n", filename);
}

// read/pread 性能对比
void compare_basic_io(const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return;
}

char buffer&#91;100];
ssize_t bytes_read;
struct timespec start, end;

printf("\n=== 基础 I/O 对比 ===\n");

// 使用 read (会改变文件位置)
printf("1. read 测试:\n");
clock_gettime(CLOCK_MONOTONIC, &start);
bytes_read = read(fd, buffer, 50);
clock_gettime(CLOCK_MONOTONIC, &end);
buffer&#91;bytes_read] = '\0';
printf(" 读取 %zd 字节: %.30s...\n", bytes_read, buffer);
printf(" 当前文件位置: %ld\n", (long)lseek(fd, 0, SEEK_CUR));

// 使用 pread (不改变文件位置)
printf("2. pread 测试:\n");
clock_gettime(CLOCK_MONOTONIC, &start);
bytes_read = pread(fd, buffer, 50, 0); // 从开头读取
clock_gettime(CLOCK_MONOTONIC, &end);
buffer&#91;bytes_read] = '\0';
printf(" 读取 %zd 字节: %.30s...\n", bytes_read, buffer);
printf(" 文件位置仍为: %ld\n", (long)lseek(fd, 0, SEEK_CUR));

close(fd);
}

// 分散/聚集 I/O 示例
void demonstrate_vector_io(const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return;
}

printf("\n=== 分散/聚集 I/O 示例 ===\n");

// 设置分散读取缓冲区
char buffer1&#91;20], buffer2&#91;30], buffer3&#91;25];
struct iovec iov&#91;3];

iov&#91;0].iov_base = buffer1;
iov&#91;0].iov_len = sizeof(buffer1) - 1;

iov&#91;1].iov_base = buffer2;
iov&#91;1].iov_len = sizeof(buffer2) - 1;

iov&#91;2].iov_base = buffer3;
iov&#91;2].iov_len = sizeof(buffer3) - 1;

printf("分散读取设置:\n");
printf(" 缓冲区1: %zu 字节\n", iov&#91;0].iov_len);
printf(" 缓冲区2: %zu 字节\n", iov&#91;1].iov_len);
printf(" 缓冲区3: %zu 字节\n", iov&#91;2].iov_len);

// 使用 preadv 一次性读取到多个缓冲区
ssize_t total_bytes = preadv(fd, iov, 3, 0); // 从文件开头开始读取
printf("总共读取: %zd 字节\n", total_bytes);

if (total_bytes > 0) {
buffer1&#91;iov&#91;0].iov_len] = '\0';
buffer2&#91;iov&#91;1].iov_len] = '\0';
buffer3&#91;iov&#91;2].iov_len] = '\0';

printf("读取结果:\n");
printf(" 缓冲区1: %s\n", buffer1);
printf(" 缓冲区2: %s\n", buffer2);
printf(" 缓冲区3: %s\n", buffer3);
}

close(fd);
}

// 资源限制示例
void demonstrate_resource_limits() {
printf("\n=== 资源限制示例 ===\n");

struct rlimit64 limit;

// 获取当前进程的文件大小限制
if (prlimit64(0, RLIMIT_FSIZE, NULL, &limit) == 0) {
printf("文件大小限制:\n");
printf(" 软限制: %lld\n", (long long)limit.rlim_cur);
printf(" 硬限制: %lld\n", (long long)limit.rlim_max);
}

// 获取内存限制
if (prlimit64(0, RLIMIT_AS, NULL, &limit) == 0) {
printf("虚拟内存限制:\n");
if (limit.rlim_cur == RLIM64_INFINITY) {
printf(" 软限制: 无限制\n");
} else {
printf(" 软限制: %lld 字节 (%.2f MB)\n",
(long long)limit.rlim_cur,
(double)limit.rlim_cur / (1024 * 1024));
}
if (limit.rlim_max == RLIM64_INFINITY) {
printf(" 硬限制: 无限制\n");
} else {
printf(" 硬限制: %lld 字节 (%.2f MB)\n",
(long long)limit.rlim_max,
(double)limit.rlim_max / (1024 * 1024));
}
}

// 获取打开文件数限制
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);
}
}

int main() {
const char *test_file = "io_test_file.txt";

printf("=== Linux I/O 系统调用对比分析 ===\n");

// 创建测试文件
create_test_file(test_file);

// 基础 I/O 对比
compare_basic_io(test_file);

// 分散/聚集 I/O 示例
demonstrate_vector_io(test_file);

// 资源限制示例
demonstrate_resource_limits();

// 清理
unlink(test_file);

printf("\n=== 使用建议 ===\n");
printf("选择指南:\n");
printf("1. 简单读写: 使用 read/write\n");
printf("2. 需要指定位置: 使用 pread/pwrite\n");
printf("3. 多缓冲区操作: 使用 preadv/pwritev\n");
printf("4. 需要高级控制: 使用 preadv2/pwritev2\n");
printf("5. 资源限制管理: 使用 prlimit64\n");

return 0;
}

4.2 性能基准测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#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

// 性能测试结构体
struct performance_test {
const char *name;
double (*test_func)(int fd, const char *filename);
};

// read 性能测试
double test_read_performance(int fd, const char *filename) {
char buffer&#91;BUFFER_SIZE];
struct timespec start, end;

clock_gettime(CLOCK_MONOTONIC, &start);

for (int i = 0; i < ITERATIONS; i++) {
lseek(fd, 0, SEEK_SET); // 重置到文件开头
read(fd, buffer, sizeof(buffer));
}

clock_gettime(CLOCK_MONOTONIC, &end);

return (end.tv_sec - start.tv_sec) * 1000000.0 +
(end.tv_nsec - start.tv_nsec) / 1000.0; // 微秒
}

// pread 性能测试
double test_pread_performance(int fd, const char *filename) {
char buffer&#91;BUFFER_SIZE];
struct timespec start, end;

clock_gettime(CLOCK_MONOTONIC, &start);

for (int i = 0; i < ITERATIONS; i++) {
pread(fd, buffer, sizeof(buffer), 0); // 始终从位置0读取
}

clock_gettime(CLOCK_MONOTONIC, &end);

return (end.tv_sec - start.tv_sec) * 1000000.0 +
(end.tv_nsec - start.tv_nsec) / 1000.0; // 微秒
}

// readv 性能测试
double test_readv_performance(int fd, const char *filename) {
char buffer1&#91;256], buffer2&#91;256], buffer3&#91;256], buffer4&#91;256];
struct iovec iov&#91;4];
struct timespec start, end;

// 设置分散读取
iov&#91;0].iov_base = buffer1;
iov&#91;0].iov_len = sizeof(buffer1);
iov&#91;1].iov_base = buffer2;
iov&#91;1].iov_len = sizeof(buffer2);
iov&#91;2].iov_base = buffer3;
iov&#91;2].iov_len = sizeof(buffer3);
iov&#91;3].iov_base = buffer4;
iov&#91;3].iov_len = sizeof(buffer4);

clock_gettime(CLOCK_MONOTONIC, &start);

for (int i = 0; i < ITERATIONS; i++) {
lseek(fd, 0, SEEK_SET);
readv(fd, iov, 4);
}

clock_gettime(CLOCK_MONOTONIC, &end);

return (end.tv_sec - start.tv_sec) * 1000000.0 +
(end.tv_nsec - start.tv_nsec) / 1000.0; // 微秒
}

// preadv 性能测试
double test_preadv_performance(int fd, const char *filename) {
char buffer1&#91;256], buffer2&#91;256], buffer3&#91;256], buffer4&#91;256];
struct iovec iov&#91;4];
struct timespec start, end;

// 设置分散读取
iov&#91;0].iov_base = buffer1;
iov&#91;0].iov_len = sizeof(buffer1);
iov&#91;1].iov_base = buffer2;
iov&#91;1].iov_len = sizeof(buffer2);
iov&#91;2].iov_base = buffer3;
iov&#91;2].iov_len = sizeof(buffer3);
iov&#91;3].iov_base = buffer4;
iov&#91;3].iov_len = sizeof(buffer4);

clock_gettime(CLOCK_MONOTONIC, &start);

for (int i = 0; i < ITERATIONS; i++) {
preadv(fd, iov, 4, 0); // 始终从位置0读取
}

clock_gettime(CLOCK_MONOTONIC, &end);

return (end.tv_sec - start.tv_sec) * 1000000.0 +
(end.tv_nsec - start.tv_nsec) / 1000.0; // 微秒
}

void run_performance_benchmark() {
const char *test_file = "benchmark_test.dat";
int fd;

printf("=== I/O 性能基准测试 ===\n");

// 创建大测试文件
fd = open(test_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
char *buffer = malloc(1024 * 1024); // 1MB 缓冲区
if (buffer) {
for (int i = 0; i < 10; i++) { // 创建 10MB 文件
write(fd, buffer, 1024 * 1024);
}
free(buffer);
}
close(fd);
}

// 打开文件进行读取测试
fd = open(test_file, O_RDONLY);
if (fd == -1) {
perror("打开测试文件失败");
return;
}

struct performance_test tests&#91;] = {
{"read", test_read_performance},
{"pread", test_pread_performance},
{"readv", test_readv_performance},
{"preadv", test_preadv_performance},
{NULL, NULL}
};

printf("%-10s %-15s %-15s\n", "函数", "耗时(微秒)", "平均耗时(纳秒)");
printf("%-10s %-15s %-15s\n", "----", "----------", "--------------");

for (int i = 0; tests&#91;i].name; i++) {
double total_time = tests&#91;i].test_func(fd, test_file);
double avg_time = total_time * 1000.0 / ITERATIONS;

printf("%-10s %-15.2f %-15.2f\n",
tests&#91;i].name, total_time, avg_time);
}

close(fd);
unlink(test_file);

printf("\n性能分析:\n");
printf("1. pread 比 read 略慢 (位置指定开销)\n");
printf("2. readv/preadv 在多缓冲区场景下更高效\n");
printf("3. preadv2/pwritev2 提供更多控制选项\n");
printf("4. 选择应基于具体使用场景\n");
}

int main() {
printf("=== Linux I/O 系统调用完整对比分析 ===\n\n");

// 运行性能基准测试
run_performance_benchmark();

printf("\n=== 详细功能对比 ===\n");
printf("pread vs read:\n");
printf(" • pread: 指定位置读取,不改变文件位置\n");
printf(" • read: 顺序读取,会改变文件位置\n");
printf("\n");

printf("preadv vs pread:\n");
printf(" • preadv: 分散读取到多个缓冲区\n");
printf(" • pread: 读取到单个缓冲区\n");
printf("\n");

printf("preadv2 vs preadv:\n");
printf(" • preadv2: 支持额外标志控制\n");
printf(" • preadv: 基本的分散读取功能\n");
printf("\n");

printf("prlimit64:\n");
printf(" • 用于获取和设置进程资源限制\n");
printf(" • 支持 64 位资源限制值\n");
printf(" • 可以操作其他进程的资源限制\n");

printf("\n=== 实际应用建议 ===\n");
printf("1. 日志文件读写: 使用 pread/pwrite\n");
printf("2. 数据库存储引擎: 使用 preadv/pwritev\n");
printf("3. 高性能网络服务: 使用 preadv2/pwritev2\n");
printf("4. 系统资源管理: 使用 prlimit64\n");
printf("5. 简单文件操作: 使用 read/write\n");

return 0;
}

4.3 实际应用场景演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <string.h>
#include <errno.h>
#include <sys/resource.h>

// 日志文件读取示例
void log_file_reader_example() {
printf("=== 日志文件读取场景 ===\n");

// 创建模拟日志文件
const char *log_file = "application.log";
int fd = open(log_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (fd != -1) {
const char *log_entries&#91;] = {
"2023-01-01 10:00:00 INFO Application started\n",
"2023-01-01 10:00:01 DEBUG Loading configuration\n",
"2023-01-01 10:00:02 WARN Low memory warning\n",
"2023-01-01 10:00:03 ERROR Database connection failed\n",
"2023-01-01 10:00:04 INFO Recovery attempt started\n"
};

for (int i = 0; i < 5; i++) {
write(fd, log_entries&#91;i], strlen(log_entries&#91;i]));
}
close(fd);
}

// 使用 pread 读取特定时间段的日志
fd = open(log_file, O_RDONLY);
if (fd != -1) {
char buffer&#91;256];

printf("读取最后一条日志记录:\n");
// 从文件末尾附近读取
ssize_t bytes_read = pread(fd, buffer, sizeof(buffer) - 1, 150);
if (bytes_read > 0) {
buffer&#91;bytes_read] = '\0';
printf(" %s", buffer);
}

close(fd);
}

unlink(log_file);
}

// 数据库页读取示例
void database_page_reader_example() {
printf("\n=== 数据库页读取场景 ===\n");

const char *db_file = "database_pages.dat";
int fd = open(db_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (fd != -1) {
// 创建模拟的数据库页
char page_data&#91;4096];
for (int page = 0; page < 10; page++) {
snprintf(page_data, sizeof(page_data),
"Page %d: Database page content with ID=%d and timestamp=%ld\n",
page, page * 1000, time(NULL));
write(fd, page_data, strlen(page_data));
}
close(fd);
}

// 使用 preadv 读取多个数据库页
fd = open(db_file, O_RDONLY);
if (fd != -1) {
char page1&#91;1024], page2&#91;1024], page3&#91;1024];
struct iovec iov&#91;3];

// 设置分散读取
iov&#91;0].iov_base = page1;
iov&#91;0].iov_len = sizeof(page1) - 1;

iov&#91;1].iov_base = page2;
iov&#91;1].iov_len = sizeof(page2) - 1;

iov&#91;2].iov_base = page3;
iov&#91;2].iov_len = sizeof(page3) - 1;

printf("使用 preadv 读取多个数据库页:\n");
ssize_t total_bytes = preadv(fd, iov, 3, 0); // 从开头读取
printf(" 总共读取: %zd 字节\n", total_bytes);

if (total_bytes > 0) {
page1&#91;iov&#91;0].iov_len] = '\0';
page2&#91;iov&#91;1].iov_len] = '\0';
page3&#91;iov&#91;2].iov_len] = '\0';

printf(" 页1: %.50s...\n", page1);
printf(" 页2: %.50s...\n", page2);
printf(" 页3: %.50s...\n", page3);
}

close(fd);
}

unlink(db_file);
}

// 网络数据包处理示例
void network_packet_processor_example() {
printf("\n=== 网络数据包处理场景 ===\n");

// 模拟网络数据包结构
struct packet_header {
uint32_t magic;
uint16_t version;
uint16_t type;
uint32_t length;
uint32_t checksum;
} __attribute__((packed));

struct packet_payload {
char data&#91;1024];
};

// 创建测试数据包文件
const char *packet_file = "network_packets.dat";
int fd = open(packet_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (fd != -1) {
// 写入多个数据包
for (int i = 0; i < 3; i++) {
struct packet_header header = {
.magic = 0x12345678,
.version = 1,
.type = i,
.length = 100,
.checksum = 0xABCDEF00 + i
};

char payload&#91;1024];
snprintf(payload, sizeof(payload),
"Packet %d payload data with timestamp %ld",
i, time(NULL));

write(fd, &header, sizeof(header));
write(fd, payload, strlen(payload) + 1);
}
close(fd);
}

// 使用 preadv2 读取数据包(如果支持)
fd = open(packet_file, O_RDONLY);
if (fd != -1) {
struct packet_header headers&#91;3];
char payloads&#91;3]&#91;256];
struct iovec iov&#91;6]; // 3个头部 + 3个载荷

// 设置分散读取结构
for (int i = 0; i < 3; i++) {
iov&#91;i*2].iov_base = &headers&#91;i];
iov&#91;i*2].iov_len = sizeof(struct packet_header);

iov&#91;i*2+1].iov_base = payloads&#91;i];
iov&#91;i*2+1].iov_len = sizeof(payloads&#91;i]) - 1;
}

printf("使用分散读取处理网络数据包:\n");
ssize_t bytes_read = preadv(fd, iov, 6, 0);
printf(" 读取字节数: %zd\n", bytes_read);

if (bytes_read > 0) {
for (int i = 0; i < 3; i++) {
printf(" 数据包 %d:\n", i);
printf(" 魔数: 0x%08X\n", headers&#91;i].magic);
printf(" 版本: %d\n", headers&#91;i].version);
printf(" 类型: %d\n", headers&#91;i].type);
printf(" 长度: %d\n", headers&#91;i].length);
printf(" 校验: 0x%08X\n", headers&#91;i].checksum);
payloads&#91;i]&#91;iov&#91;i*2+1].iov_len] = '\0';
printf(" 载荷: %.50s...\n", payloads&#91;i]);
printf("\n");
}
}

close(fd);
}

unlink(packet_file);
}

// 资源限制管理示例
void resource_limit_management_example() {
printf("\n=== 资源限制管理场景 ===\n");

struct rlimit64 old_limit, new_limit;

// 获取当前文件大小限制
if (prlimit64(0, RLIMIT_FSIZE, NULL, &old_limit) == 0) {
printf("当前文件大小限制:\n");
if (old_limit.rlim_cur == RLIM64_INFINITY) {
printf(" 软限制: 无限制\n");
} else {
printf(" 软限制: %lld 字节 (%.2f GB)\n",
(long long)old_limit.rlim_cur,
(double)old_limit.rlim_cur / (1024 * 1024 * 1024));
}
}

// 获取打开文件数限制
if (prlimit64(0, RLIMIT_NOFILE, NULL, &old_limit) == 0) {
printf("当前文件描述符限制:\n");
printf(" 软限制: %lld\n", (long long)old_limit.rlim_cur);
printf(" 硬限制: %lld\n", (long long)old_limit.rlim_max);
}

// 获取内存限制
if (prlimit64(0, RLIMIT_AS, NULL, &old_limit) == 0) {
printf("当前虚拟内存限制:\n");
if (old_limit.rlim_cur == RLIM64_INFINITY) {
printf(" 软限制: 无限制\n");
} else {
printf(" 软限制: %lld 字节 (%.2f GB)\n",
(long long)old_limit.rlim_cur,
(double)old_limit.rlim_cur / (1024 * 1024 * 1024));
}
}

printf("\n资源限制管理最佳实践:\n");
printf("1. 合理设置文件大小限制防止磁盘填满\n");
printf("2. 适当增加文件描述符限制支持高并发\n");
printf("3. 监控内存使用防止内存泄漏\n");
printf("4. 使用 prlimit64 动态调整资源限制\n");
}

int main() {
printf("=== Linux I/O 系统调用应用场景演示 ===\n\n");

// 日志文件读取场景
log_file_reader_example();

// 数据库页读取场景
database_page_reader_example();

// 网络数据包处理场景
network_packet_processor_example();

// 资源限制管理场景
resource_limit_management_example();

printf("\n=== 总结 ===\n");
printf("I/O 系统调用选择指南:\n");
printf("\n");
printf("┌─────────────┬────────────────────────────────────┐\n");
printf("│ 场景 │ 推荐函数 │\n");
printf("├─────────────┼────────────────────────────────────┤\n");
printf("│ 简单读写 │ read/write │\n");
printf("│ 位置指定 │ pread/pwrite │\n");
printf("│ 多缓冲区 │ readv/writev │\n");
printf("│ 位置+多缓冲 │ preadv/pwritev │\n");
printf("│ 高级控制 │ preadv2/pwritev2 │\n");
printf("│ 资源限制 │ prlimit64 │\n");
printf("└─────────────┴────────────────────────────────────┘\n");
printf("\n");
printf("性能优化建议:\n");
printf("1. 批量操作减少系统调用次数\n");
printf("2. 合理选择缓冲区大小\n");
printf("3. 使用位置指定避免文件位置移动\n");
printf("4. 分散/聚集 I/O 减少内存拷贝\n");
printf("5. 合理设置资源限制防止系统过载\n");

return 0;
}

  1. 编译和运行说明
1
2
3
4
5
6
7
8
9
10
# 编译示例程序
gcc -o io_comparison_example1 example1.c
gcc -o io_comparison_example2 example2.c
gcc -o io_comparison_example3 example3.c

# 运行示例
./io_comparison_example1
./io_comparison_example2
./io_comparison_example3

  1. 系统要求检查
1
2
3
4
5
6
7
8
9
10
11
12
# 检查内核版本
uname -r

# 检查 glibc 版本
ldd --version

# 检查系统调用支持
grep -E "(pread|pwrite|prlimit)" /usr/include/asm/unistd_64.h

# 查看文件系统性能
hdparm -Tt /dev/sda # 硬盘性能测试

  1. 重要注意事项

原子性: pread/pwrite 操作是原子的

位置独立: 不改变文件描述符的当前位置

错误处理: 始终检查返回值和 errno

内存对齐: 在某些架构上有对齐要求

权限检查: 确保有足够的权限进行操作

资源清理: 及时关闭文件描述符

  1. 最佳实践总结
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 安全的 I/O 操作封装
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;
}

// 安全的分散读取封装
ssize_t safe_preadv(int fd, const struct iovec *iov, int iovcnt, off_t offset) {
if (fd < 0 || !iov || iovcnt <= 0 || iovcnt > IOV_MAX) {
errno = EINVAL;
return -1;
}

ssize_t result;
do {
result = preadv(fd, iov, iovcnt, offset);
} while (result == -1 && errno == EINTR);

return result;
}

// 资源限制检查
int check_resource_limits() {
struct rlimit64 limit;

// 检查文件大小限制
if (prlimit64(0, RLIMIT_FSIZE, NULL, &limit) == 0) {
if (limit.rlim_cur != RLIM64_INFINITY && limit.rlim_cur < 1024 * 1024) {
printf("警告: 文件大小限制过小 (%lld 字节)\n",
(long long)limit.rlim_cur);
}
}

// 检查文件描述符限制
if (prlimit64(0, RLIMIT_NOFILE, NULL, &limit) == 0) {
if (limit.rlim_cur < 1024) {
printf("警告: 文件描述符限制过小 (%lld)\n",
(long long)limit.rlim_cur);
}
}

return 0;
}

这些示例全面展示了 Linux I/O 系统调用的功能特点、使用方法和实际应用场景,帮助开发者根据具体需求选择合适的 I/O 操作方式。

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

  1. 概述

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

  1. 系统调用详细介绍

2.1 pread/pwrite - 位置指定读写

1
2
3
4
5
#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: 向指定位置写入数据,不改变文件指针位置

优势:

  • 原子操作(读/写 + 位置指定)

  • 线程安全

  • 不影响其他读写操作

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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&#91;20];
ssize_t bytes_read = pread(fd, buffer, 10, 0);
buffer&#91;bytes_read] = '\0';
printf("读取内容: %s\n", buffer);

close(fd);
return 0;
}

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

1
2
3
4
5
#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 结构体:

1
2
3
4
5
struct iovec {
void *iov_base; // 缓冲区地址
size_t iov_len; // 缓冲区长度
};

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#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&#91;3];
char *data1 = "First part ";
char *data2 = "Second part ";
char *data3 = "Third part\n";

iov_write&#91;0].iov_base = data1;
iov_write&#91;0].iov_len = strlen(data1);
iov_write&#91;1].iov_base = data2;
iov_write&#91;1].iov_len = strlen(data2);
iov_write&#91;2].iov_base = data3;
iov_write&#91;2].iov_len = strlen(data3);

// 分散写入
pwritev(fd, iov_write, 3, 0);

// 分散读取
struct iovec iov_read&#91;3];
char buf1&#91;20], buf2&#91;20], buf3&#91;20];

iov_read&#91;0].iov_base = buf1;
iov_read&#91;0].iov_len = sizeof(buf1) - 1;
iov_read&#91;1].iov_base = buf2;
iov_read&#91;1].iov_len = sizeof(buf2) - 1;
iov_read&#91;2].iov_base = buf3;
iov_read&#91;2].iov_len = sizeof(buf3) - 1;

ssize_t total_bytes = preadv(fd, iov_read, 3, 0);
printf("总共读取 %zd 字节\n", total_bytes);

buf1&#91;iov_read&#91;0].iov_len] = '\0';
buf2&#91;iov_read&#91;1].iov_len] = '\0';
buf3&#91;iov_read&#91;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

1
2
3
4
5
6
7
8
#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: 追加模式

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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&#91;2];
char *data1 = "Enhanced ";
char *data2 = "I/O operation\n";

iov&#91;0].iov_base = data1;
iov&#91;0].iov_len = strlen(data1);
iov&#91;1].iov_base = data2;
iov&#91;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 - 资源限制控制

1
2
3
4
5
6
#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: 栈大小限制

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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;
}

  1. 性能对比测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#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;
}

  1. 实际应用场景

4.1 数据库存储引擎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <sys/uio.h>
#include <fcntl.h>

// 模拟数据库页读取
typedef struct {
int page_id;
char data&#91;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&#91;i].iov_base = &pages&#91;i];
iov&#91;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 网络协议处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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&#91;1024];
} packet_payload_t;

int process_network_packets(int socket_fd) {
// 准备接收多个网络包
struct iovec iov&#91;5]; // 最多5个包
packet_header_t headers&#91;5];
packet_payload_t payloads&#91;5];

// 设置分散接收缓冲区
for (int i = 0; i < 5; i++) {
iov&#91;i*2].iov_base = &headers&#91;i];
iov&#91;i*2].iov_len = sizeof(packet_header_t);

iov&#91;i*2+1].iov_base = &payloads&#91;i];
iov&#91;i*2+1].iov_len = sizeof(packet_payload_t);
}

// 一次性接收多个包(简化示例)
printf("准备接收网络数据包...\n");
return 0;
}

  1. 编译和运行
1
2
3
4
5
6
7
8
9
10
# 编译示例
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

  1. 使用建议

6.1 选择指南

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

6.2 最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 安全的 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 应用程序提供了强大而灵活的文件操作能力,正确使用它们可以显著提高程序的性能和可靠性。