eventfd – 创建事件文件描述符
函数介绍
eventfd
系统调用用于创建一个事件文件描述符,用于用户空间程序之间的事件通知。它提供了一个简单的计数器机制,可以用于线程间或进程间的同步。
函数原型
#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()
: 信号文件描述符
示例代码
#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;
}
49. eventfd2 – 创建事件文件描述符(扩展版)
函数介绍
eventfd2
是eventfd
的扩展版本,提供了更好的标志位支持和错误处理。它是现代Linux系统推荐使用的eventfd创建函数。
函数原型
#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()
: 管道机制
示例代码
#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;
}