为何epoll要结合文件描述符编号与打开文件描述区分已注册的文件描述符?
为什么epoll不直接用fd编号作为兴趣列表的唯一键
核心原因是同一个fd编号在进程的生命周期内可能对应完全不同的打开文件实例,单独用fd编号会导致匹配逻辑错误,具体可以分为两个典型场景说明:
- fd编号复用场景
操作系统分配fd遵循「最小可用」规则,当某个fd被close后,编号会被回收,后续执行open/accept/socket等创建新fd的系统调用时,会优先分配最小的未被占用的编号。假设你先将fd=4的TCP socket注册到epoll监听EPOLLIN事件,之后主动关闭这个socket,fd=4被回收。一段时间后你打开一个本地磁盘文件,内核刚好把fd=4分配给这个新文件,如果此时你尝试给这个新的fd=4注册EPOLLOUT事件,要是epoll只用fd编号当唯一键,就会错误匹配到之前的socket注册条目,直接修改已有条目的事件掩码,完全不符合用户预期。 - 复制fd指向同一打开文件的场景
调用dup()/dup2()/fcntl(F_DUPFD)等接口可以生成多个不同编号的fd,指向同一个内核struct file实例。比如你把socket对应的fd=3复制得到fd=5,此时可以给fd=3注册EPOLLIN事件,给fd=5注册EPOLLOUT事件,两个fd的监听需求是独立的,这也要求内核不能仅靠fd编号区分不同的注册条目。
epoll在设计时就考虑到了上述问题,采用「打开文件描述指针+fd编号」的组合作为唯一键,fs/eventpoll.c中的红黑树键比较逻辑如下:
/* Compare RB tree keys */ static inline int ep_cmp_ffd(struct epoll_filefd *p1, struct epoll_filefd *p2) { return (p1->file > p2->file ? +1: (p1->file < p2->file ? -1 : p1->fd - p2->fd)); }
该逻辑先比较struct file指针(唯一标识一个内核打开文件实例),再比较fd编号,完全规避了仅用fd编号作为键的问题,这一点也在epoll(7)文档中做了明确说明:
用于区分兴趣列表中已注册文件描述符的键是文件描述符编号与打开文件描述(也称为“打开文件句柄”,是内核内部对已打开文件的表示)的组合。
内容的提问来源于stack exchange,提问作者Phil
相关产品推荐
相关产品推荐

