memfd_create匿名文件被select误判可读的问题解决问询
问题根源与解决方案
为什么memfd会让select一直标记可读
memfd创建的匿名文件属于常规文件类型,而POSIX标准中select对常规文件的可读就绪判定逻辑是:只要文件当前的读写指针没有到达EOF,就会一直认为该文件描述符可读——哪怕没有新的内容写入。这就是你的select始终被唤醒的核心原因,常规文件并不适合用来做事件触发的通知源。
正确的替代方案
要实现“写入时唤醒select”的事件通知,应该使用专门设计的IPC/事件通知机制:
- 管道(pipe):最经典的跨线程/进程事件通知方式,读端仅在有数据写入时才会被select标记为可读,读取数据后恢复未就绪状态。
- eventfd:Linux特有的轻量级事件通知fd,比管道更高效,仅用于传递事件计数,同样符合select的触发规则。
管道实现示例代码
替换原来的memfd创建逻辑,改用管道:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> int main() { int notify_pipe[2]; // 创建管道 if (pipe(notify_pipe) == -1) { perror("pipe failed"); exit(EXIT_FAILURE); } fd_set read_fds; FD_ZERO(&read_fds); FD_SET(notify_pipe[0], &read_fds); int max_fd = notify_pipe[0]; // 模拟异步写入(实际场景可能是其他线程/进程) pid_t pid = fork(); if (pid == 0) { sleep(2); char buf[] = "wake up"; write(notify_pipe[1], buf, sizeof(buf)); close(notify_pipe[1]); exit(EXIT_SUCCESS); } printf("Waiting for event...\n"); int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ret == -1) { perror("select failed"); exit(EXIT_FAILURE); } if (FD_ISSET(notify_pipe[0], &read_fds)) { char buf[1024]; ssize_t n = read(notify_pipe[0], buf, sizeof(buf)); printf("Got event, read %zd bytes: %s\n", n, buf); } close(notify_pipe[0]); return 0; }
eventfd实现示例代码
如果用更轻量的eventfd:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/eventfd.h> #include <inttypes.h> int main() { int efd = eventfd(0, EFD_NONBLOCK); if (efd == -1) { perror("eventfd failed"); exit(EXIT_FAILURE); } fd_set read_fds; FD_ZERO(&read_fds); FD_SET(efd, &read_fds); int max_fd = efd; pid_t pid = fork(); if (pid == 0) { sleep(2); eventfd_write(efd, 1); // 写入事件计数 close(efd); exit(EXIT_SUCCESS); } printf("Waiting for event...\n"); int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ret == -1) { perror("select failed"); exit(EXIT_FAILURE); } if (FD_ISSET(efd, &read_fds)) { uint64_t value; ssize_t n = eventfd_read(efd, &value); printf("Got event, count: %"PRIu64"\n", value); } close(efd); return 0; }
运行效果说明
以上两种实现中,select会在写入操作完成后才被唤醒,读取数据/事件计数后,fd会恢复为未就绪状态,下次只有新的写入操作才会再次触发select。
内容的提问来源于stack exchange,提问作者Peter Petigru
相关产品推荐
相关产品推荐

