1、引言
本文来自 Marek’s 博客中 I/O multiplexing part 系列之三和四,主要讲 Linux 上 IO 多路复用的一些问题
- Epoll is fundamentally broken 1/2
- Epoll is fundamentally broken 2/2
4. epoll 之 file descriptor 与 file description
这一章我们主要讨论 epoll() 的另一个大问题:file descriptor 与 file description 生命周期不一致的问题。
4.1 file descriptor 和 file description 生命周期不一致的问题
Foom 在 LWN 上说道:
显然 epoll 存在巨大的设计缺陷,任何懂得 file descriptor 的人应该都能看得出来。事实上当你回望 epoll 的历史,你会发现当时实现 epoll 的人们显然并不怎么了解 file descriptor 和 file description 的区别。:
实际上,epoll() 的这个问题主要在于它混淆了用户态的 file descriptor (我们平常说的数字 fd) 和内核态中真正用于实现的 file description。当进程调用 close(2) 关闭一个 fd 时,这个问题就会体现出来。
epoll_ctl(EPOLL_CTL_ADD)
实际上并不是注册一个 file descriptor (fd),而是将 fd 和 一个指向内核 file description 的指针的对 (tuple) 一块注册给了 epoll,导致问题的根源在于,epoll 里管理的 fd 的生命周期,并不是 fd 本身的,而是内核中相应的 file description 的。
当使用 close(2) 这个系统调用关掉一个 fd 时,如果这个 fd 是内核中 file description 的唯一引用时,内核中的 file description 也会跟着一并被删除,这样是 OK 的;但是当内核中的 file description 还有其他引用时,close 并不会删除这个 file descrption。这样会导致当这个 fd 还没有从 epoll 中挪出就被直接 close 时,epoll() 还会在这个已经 close() 掉了的 fd 上上报事件。
这里以 dup(2) 系统调用为例来展示这个问题:
rfd, wfd = pipe()
write(wfd, "a") # Make the "rfd" readable
epfd = epoll_create()
epoll_ctl(efpd, EPOLL_CTL_ADD, rfd, (EPOLLIN, rfd))
rfd2 = dup(rfd)
close(rfd)
r = epoll_wait(epfd, -1ms) # What will happen?
由于 close(rfd) 关掉了这个 rfd,你可能会认为这个 epoll_wait() 会一直阻塞不返回,而实际上并不是这样。由于调用了 dup(),内核中相应的 file description 仍然还有一个引用计数而没有被删除,所以这个 file descption 的事件仍然会上报给 epoll。因此 epoll_wait()
会给一个已经不存在的 fd 上报事件。 更糟糕的是,一旦你 close() 了这个 fd,再也没有机会把这个死掉的 fd 从 epoll 上摘除了,下面的做法都不行:
epoll_ctl(efpd, EPOLL_CTL_DEL, rfd)
epoll_ctl(efpd, EPOLL_CTL_DEL, rfd2)
Marc Lehmann 也提到这个问题:
因此,存在 close 掉了一个 fd,却还一直从这个 fd 上收到 epoll 事件的可能性。并且这种情况一旦发生,不管你做什么都无法恢复了。
close()
永远记着先在调用 close() 之前,显示的调用 epoll_ctl(EPOLL_CTL_DEL)
4.2 总结
显式的将 fd 从 epoll 上面删掉在调用 close()
的话可以工作的很好,前提是你对所有的代码都有掌控力。然后在一些场景里并不一直是这样,譬如当写一个封装 epoll 的库,有时你并不能禁止用户调用 close(2) 系统调用。因此,要写一个基于 epoll 的轻量级的抽象层并不是一个轻松的事情。
另外,Illumos 也实现了一套 epoll() 机制,在他们的手册上,明确提到 Linux 上这个 epoll()/close() 的奇怪语义,并且拒绝支持。
希望本所提到的问题对于使用 Linux 上这个糟糕的 epoll() 设计的人有所帮助。
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子