eventfd系统调用及示例

eventfd - 创建事件文件描述符

函数介绍

eventfd系统调用用于创建一个事件文件描述符,用于用户空间程序之间的事件通知。它提供了一个简单的计数器机制,可以用于线程间或进程间的同步。

函数原型

1
2
3
4
5
6
7
#include <sys/eventfd.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdint.h>

int eventfd(unsigned int initval, int flags);

功能

创建一个事件文件描述符,内部维护一个64位无符号整数计数器,用于事件通知和同步。

参数

  • unsigned int initval: 计数器的初始值

int flags: 控制标志

  • 0: 基本模式

  • EFD_CLOEXEC: 设置执行时关闭标志

  • EFD_NONBLOCK: 设置非阻塞模式

  • EFD_SEMAPHORE: 信号量模式(每次读取递减1而不是重置为0)

返回值

  • 成功时返回事件文件描述符(非负整数)

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

特殊限制

  • 需要Linux 2.6.22以上内核支持

  • 计数器值有最大限制(0xfffffffffffffffeULL)

相似函数

  • eventfd2(): 现代版本,更好的标志支持

  • pipe(): 管道机制

  • signalfd(): 信号文件描述符

示例代码

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>

// 系统调用包装(如果glibc不支持)
static int eventfd_wrapper(unsigned int initval, int flags) {
return syscall(__NR_eventfd2, initval, flags);
}

// 线程函数
void* thread_function(void* arg) {
int efd = *(int*)arg;
uint64_t value = 1;

printf(" 子线程: 准备发送事件通知\n");

// 发送事件通知
if (write(efd, &value, sizeof(value)) != sizeof(value)) {
perror(" 子线程: 写入eventfd失败");
} else {
printf(" 子线程: 成功发送事件通知\n");
}

return NULL;
}

int main() {
int efd;
uint64_t value;
pthread_t thread;

printf("=== Eventfd 函数示例 ===\n");

// 示例1: 基本使用
printf("\n示例1: 基本使用\n");

efd = eventfd_wrapper(0, 0);
if (efd == -1) {
perror("eventfd 创建失败");
exit(EXIT_FAILURE);
}
printf("成功创建eventfd,文件描述符: %d\n", efd);

// 检查文件描述符属性
int flags = fcntl(efd, F_GETFD);
if (flags != -1) {
printf("eventfd文件描述符验证成功\n");
}

// 关闭eventfd
close(efd);
printf("关闭eventfd\n");

// 示例2: 基本的事件通知
printf("\n示例2: 基本的事件通知\n");

efd = eventfd_wrapper(0, 0);
if (efd == -1) {
perror("eventfd 创建失败");
exit(EXIT_FAILURE);
}
printf("创建eventfd: %d\n", efd);

// 启动线程发送事件
if (pthread_create(&thread, NULL, thread_function, &efd) != 0) {
perror("创建线程失败");
close(efd);
exit(EXIT_FAILURE);
}

printf("主线程: 等待事件通知...\n");

// 等待事件通知
ssize_t bytes_read = read(efd, &value, sizeof(value));
if (bytes_read == sizeof(value)) {
printf("主线程: 收到事件通知,计数器值: %lu\n", value);
} else {
perror("主线程: 读取eventfd失败");
}

// 等待线程结束
pthread_join(thread, NULL);

close(efd);

// 示例3: 使用标志位
printf("\n示例3: 使用标志位\n");

// 使用EFD_CLOEXEC标志
efd = eventfd_wrapper(0, EFD_CLOEXEC);
if (efd != -1) {
printf("创建带EFD_CLOEXEC标志的eventfd: %d\n", efd);

// 验证标志是否设置
flags = fcntl(efd, F_GETFD);
if (flags != -1 && (flags & FD_CLOEXEC)) {
printf("EFD_CLOEXEC标志已正确设置\n");
}
close(efd);
}

// 使用EFD_NONBLOCK标志
efd = eventfd_wrapper(0, EFD_NONBLOCK);
if (efd != -1) {
printf("创建带EFD_NONBLOCK标志的eventfd: %d\n", efd);

// 尝试非阻塞读取(应该失败)
bytes_read = read(efd, &value, sizeof(value));
if (bytes_read == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("非阻塞读取正确返回EAGAIN: %s\n", strerror(errno));
}
}
close(efd);
}

// 示例4: 信号量模式
printf("\n示例4: 信号量模式\n");

efd = eventfd_wrapper(3, EFD_SEMAPHORE);
if (efd != -1) {
printf("创建信号量模式eventfd,初始值: 3\n");

// 多次读取,每次递减1
for (int i = 0; i < 5; i++) {
bytes_read = read(efd, &value, sizeof(value));
if (bytes_read == sizeof(value)) {
printf("第%d次读取,获得值: %lu\n", i+1, value);
} else {
if (errno == EAGAIN) {
printf("第%d次读取,无可用事件: %s\n", i+1, strerror(errno));
break;
}
}
}

close(efd);
}

// 示例5: 错误处理演示
printf("\n示例5: 错误处理演示\n");

// 使用无效的初始值(虽然eventfd允许大值,但有上限)
efd = eventfd_wrapper(0xffffffff, 0);
if (efd != -1) {
printf("使用大初始值创建eventfd成功: %d\n", efd);
close(efd);
}

// 尝试写入无效值
efd = eventfd_wrapper(0, 0);
if (efd != -1) {
uint64_t invalid_value = 0xffffffffffffffffULL; // 最大值
ssize_t result = write(efd, &invalid_value, sizeof(invalid_value));
if (result == -1) {
printf("写入最大值失败: %s\n", strerror(errno));
} else {
printf("写入最大值成功\n");
}
close(efd);
}

// 示例6: 计数器溢出处理
printf("\n示例6: 计数器溢出处理\n");

efd = eventfd_wrapper(0, 0);
if (efd != -1) {
// 写入接近最大值的数据
uint64_t large_value = 0xfffffffffffffffeULL; // 接近最大值
if (write(efd, &large_value, sizeof(large_value)) == sizeof(large_value)) {
printf("写入大值成功\n");

// 再次写入会导致溢出
uint64_t add_value = 2;
ssize_t result = write(efd, &add_value, sizeof(add_value));
if (result == -1) {
if (errno == EAGAIN) {
printf("计数器溢出,写入失败: %s\n", strerror(errno));
}
}
}
close(efd);
}

// 示例7: 实际应用场景
printf("\n示例7: 实际应用场景\n");

printf("eventfd的典型应用场景:\n");
printf("1. 线程池任务通知\n");
printf("2. 异步I/O完成通知\n");
printf("3. 事件驱动编程\n");
printf("4. 进程间简单通信\n");
printf("5. 与epoll配合使用\n\n");

// 演示与epoll配合使用
printf("与epoll配合使用的示例:\n");
printf("int epfd = epoll_create1(EPOLL_CLOEXEC);\n");
printf("int efd = eventfd(0, EFD_CLOEXEC);\n");
printf("struct epoll_event ev;\n");
printf("ev.events = EPOLLIN;\n");
printf("ev.data.fd = efd;\n");
printf("epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &ev);\n");
printf("// 在其他线程中: write(efd, &value, sizeof(value));\n");
printf("// 在事件循环中: epoll_wait(epfd, events, maxevents, timeout);\n\n");

// 示例8: 性能优势
printf("示例8: 性能优势\n");
printf("eventfd相比传统机制的优势:\n");
printf("1. 更少的系统调用\n");
printf("2. 更小的内存占用\n");
printf("3. 更快的通知速度\n");
printf("4. 更简单的API\n");
printf("5. 更好的可扩展性\n\n");

printf("与pipe的对比:\n");
printf("pipe: 需要两个文件描述符,缓冲区较大\n");
printf("eventfd: 只需要一个文件描述符,固定8字节计数器\n\n");

printf("总结:\n");
printf("eventfd是Linux提供的轻量级事件通知机制\n");
printf("适用于简单的同步和通知场景\n");
printf("支持多种模式和标志位\n");
printf("与epoll等机制配合使用效果更佳\n");
printf("是现代Linux编程的重要工具\n");

return 0;
}

  1. eventfd2 - 创建事件文件描述符(扩展版)

函数介绍

eventfd2是eventfd的扩展版本,提供了更好的标志位支持和错误处理。它是现代Linux系统推荐使用的eventfd创建函数。

函数原型

1
2
3
4
#include <sys/eventfd.h>

int eventfd2(unsigned int initval, int flags);

功能

创建一个事件文件描述符,功能与eventfd相同但接口更现代。

参数

  • unsigned int initval: 计数器的初始值

  • int flags: 控制标志(支持更多标志位)

返回值

  • 成功时返回事件文件描述符

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

特殊限制

  • 需要Linux 2.6.27以上内核支持

  • 某些旧系统可能不支持

相似函数

  • eventfd(): 基础版本

  • pipe(): 管道机制

示例代码

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>

int main() {
int efd1, efd2;

printf("=== Eventfd2 函数示例 ===\n");

// 示例1: 基本使用对比
printf("\n示例1: 基本使用对比\n");

// 使用eventfd
efd1 = eventfd(0, 0);
if (efd1 != -1) {
printf("eventfd创建成功: %d\n", efd1);
close(efd1);
} else {
printf("eventfd创建失败: %s\n", strerror(errno));
}

// 使用eventfd2
efd2 = eventfd2(0, 0);
if (efd2 != -1) {
printf("eventfd2创建成功: %d\n", efd2);
close(efd2);
} else {
printf("eventfd2创建失败: %s\n", strerror(errno));
printf("说明: 系统可能不支持eventfd2\n");
}

// 示例2: 标志位支持
printf("\n示例2: 标志位支持\n");

// EFD_CLOEXEC标志
efd2 = eventfd2(0, EFD_CLOEXEC);
if (efd2 != -1) {
printf("使用EFD_CLOEXEC标志创建成功: %d\n", efd2);

// 验证标志设置
int flags = fcntl(efd2, F_GETFD);
if (flags != -1 && (flags & FD_CLOEXEC)) {
printf("EFD_CLOEXEC标志验证成功\n");
}
close(efd2);
}

// EFD_NONBLOCK标志
efd2 = eventfd2(0, EFD_NONBLOCK);
if (efd2 != -1) {
printf("使用EFD_NONBLOCK标志创建成功: %d\n", efd2);

// 测试非阻塞特性
uint64_t value;
ssize_t result = read(efd2, &value, sizeof(value));
if (result == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("非阻塞读取正确返回EAGAIN\n");
}
}
close(efd2);
}

// EFD_SEMAPHORE标志
efd2 = eventfd2(5, EFD_SEMAPHORE);
if (efd2 != -1) {
printf("使用EFD_SEMAPHORE标志创建成功,初始值: 5\n");

// 测试信号量模式
for (int i = 0; i < 7; i++) {
uint64_t read_value;
ssize_t result = read(efd2, &read_value, sizeof(read_value));
if (result == sizeof(read_value)) {
printf("第%d次读取成功,值: %lu\n", i+1, read_value);
} else {
if (errno == EAGAIN) {
printf("第%d次读取失败,无可用资源: %s\n", i+1, strerror(errno));
break;
}
}
}
close(efd2);
}

// 示例3: 组合标志
printf("\n示例3: 组合标志\n");

// 组合多个标志
efd2 = eventfd2(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (efd2 != -1) {
printf("组合标志创建成功: %d\n", efd2);

// 验证所有标志
int flags = fcntl(efd2, F_GETFD);
if (flags != -1) {
if (flags & FD_CLOEXEC) {
printf("EFD_CLOEXEC标志已设置\n");
}
}

// 测试非阻塞特性
uint64_t value;
if (read(efd2, &value, sizeof(value)) == -1) {
if (errno == EAGAIN) {
printf("EFD_NONBLOCK标志生效\n");
}
}

close(efd2);
}

// 示例4: 错误处理
printf("\n示例4: 错误处理\n");

// 使用无效标志
efd2 = eventfd2(0, 0x1000); // 无效标志
if (efd2 == -1) {
if (errno == EINVAL) {
printf("无效标志错误处理正确: %s\n", strerror(errno));
}
}

// 在不支持的系统上
printf("在不支持eventfd2的系统上会返回ENOSYS错误\n");

// 示例5: 与eventfd的差异
printf("\n示例5: 与eventfd的差异\n");

printf("eventfd vs eventfd2:\n");
printf("eventfd:\n");
printf(" - 较老的接口\n");
printf(" - 标志位支持有限\n");
printf(" - 在所有支持eventfd的系统上可用\n\n");

printf("eventfd2:\n");
printf(" - 现代接口\n");
printf(" - 更好的标志位支持\n");
printf(" - 原子性设置标志\n");
printf(" - 需要Linux 2.6.27+\n\n");

// 示例6: 实际应用推荐
printf("示例6: 实际应用推荐\n");

printf("现代应用推荐使用模式:\n");
printf("#ifdef EFD_CLOEXEC\n");
printf(" int efd = eventfd2(0, EFD_CLOEXEC | EFD_NONBLOCK);\n");
printf("#else\n");
printf(" int efd = eventfd(0, 0);\n");
printf(" fcntl(efd, F_SETFD, FD_CLOEXEC);\n");
printf(" fcntl(efd, F_SETFL, fcntl(efd, F_GETFL) | O_NONBLOCK);\n");
printf("#endif\n\n");

// 示例7: 兼容性处理
printf("示例7: 兼容性处理\n");

// 兼容性包装函数
printf("兼容性处理示例:\n");
printf("int create_eventfd(unsigned int initval, int flags) {\n");
printf("#ifdef __NR_eventfd2\n");
printf(" int fd = eventfd2(initval, flags);\n");
printf(" if (fd >= 0 || errno != ENOSYS)\n");
printf(" return fd;\n");
printf("#endif\n");
printf(" // 回退到eventfd\n");
printf(" fd = eventfd(initval, 0);\n");
printf(" if (fd >= 0) {\n");
printf(" // 手动设置标志\n");
printf(" if (flags & EFD_CLOEXEC)\n");
printf(" fcntl(fd, F_SETFD, FD_CLOEXEC);\n");
printf(" if (flags & EFD_NONBLOCK)\n");
printf(" fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);\n");
printf(" }\n");
printf(" return fd;\n");
printf("}\n\n");

// 示例8: 性能考虑
printf("示例8: 性能考虑\n");
printf("eventfd2性能优势:\n");
printf("1. 原子性标志设置,避免竞态条件\n");
printf("2. 减少系统调用次数\n");
printf("3. 更好的错误处理\n");
printf("4. 现代内核优化\n\n");

printf("使用建议:\n");
printf("1. 优先使用eventfd2\n");
printf("2. 提供eventfd回退方案\n");
printf("3. 合理使用标志位\n");
printf("4. 注意资源清理\n\n");

printf("总结:\n");
printf("eventfd2是eventfd的现代替代品\n");
printf("提供了更好的标志位支持\n");
printf("在支持的系统上应优先使用\n");
printf("需要考虑向后兼容性\n");
printf("是构建高性能应用的重要工具\n");

return 0;
}
data-ad-format="auto" data-full-width-responsive="true">