eventfd系统调用及示例

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 – 创建事件文件描述符(扩展版)

函数介绍

eventfd2eventfd的扩展版本,提供了更好的标志位支持和错误处理。它是现代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;
}
此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注