当文件描述符关闭时,有什么方法可以执行回调(在 Linux 上)

Is there any way to execute a callback (on Linux) when a file descriptor is closed

我正在为 Linux 开发一个 kevent/kqueue emulation 库。我是这个项目的新维护者,不幸的是,以前的维护者不再参与太多(所以我不能就此征询他们的意见)。

在 FreeBSD 和 macOS 下,当您 close()kqeueue() 提供的文件描述符时,您释放了与之相关的任何资源和事件。

现有代码似乎没有提供类似的接口。在我向 API 添加函数(或恢复旧函数)以显式释放 kqueue 资源之前,我想知道是否有任何方法可以将触发器与 linux 中的文件描述符相关联,这样当它已关闭,我们可以清理与 FD 相关的任何内容。

文件描述符本身可以是任何类型,即由 eventfd 或 epoll 或任何其他创建文件描述符的类型提供。

当来自 pipe() 调用的最后一个写入文件描述符关闭时,epoll()/poll() 等待程序将在任何仍打开的读取文件描述符上看到 [E]POLLHUP 事件。想必任何表示连接而不是状态的 fd 也是如此。

这个问题的解决方案相当简单,尽管实施起来有点烦人。它依赖于一个名为 F_SETSIGfcntl 来指定用于传达 FD 状态更改的信号,以及一个名为 F_SETOWN_EXfcntl 来指定应将信号传递到哪个线程.

应用程序启动时会产生一个 separate monitoring thread。该线程用于接收 FD 生成的信号。

在我们的特定用例中,监控线程必须在第一次创建受监控的 FD 时隐式启动,并且在没有显式连接的情况下销毁。这是因为我们正在模拟 FreeBSD API (kqueue),它没有显式的 init 和 deinit 函数。

监控线程:

  1. Listens on a the signal 我们传到了 F_SETSIG.
  2. Gets its thread ID,并将其存储在全局中。
  3. Informs the application 使用 pthread_cond_broadcast.
  4. 表示监视线程已启动(并且全局已填充)
  5. Calls pthread_detach 以确保正确清理它而无需另一个线程需要执行显式 pthread_join.
  6. Calls sigwaitinfo 等待信号传递。

应用线程:

  1. Uses pthread_once第一次创建FD就启动监控线程,然后等待监控线程完全启动。
  2. Uses F_SETSIG to specify the signal sent when the FD is open/closed, and F_SETOWN_EX 将这些信号定向到监视线程。

当受监控的 FD 关闭时,sigwaitinfo call in the monitoring thread returns. In our case we're using a pipe to represent the kqueue, so we need to map the FD we received the signal for, to the one associated with the resources (kqueues) 我们需要释放。完成此映射后,我们可能(请参阅下文了解更多信息)清理与 FD 对关联的资源,并再次调用 sigwaitinfo 以等待更多信号。

此策略的其他关键部分之一是与 FD 关联的资源是引用计数的。这是因为信号不是同步传递的,所以可以关闭一个FD,并且可以创建一个相同编号的新FD,在指示原始FD被关闭的信号被传递并被执行之前。这显然会导致释放活动资源的大问题。

为了解决这个问题,我们维护了一个互斥同步 FD 到资源映射数组。此数组中的每个元素都包含特定 FD 的引用计数。

如果在创建新的 pipe/resource 对时重用 FD 之前未传送信号,则该特定 FD 的引用计数将 > 0。发生这种情况时,我们会立即释放资源,并重新初始化它,增加引用计数。 当指示 FD 已关闭的信号被传递时,引用计数会减少(但不会为零),并且资源不会被释放。

或者,如果在重用 FD 之前传送信号,则监视线程将减少 reference count to zero, and immediately free 相关资源。

如果此描述有点令人困惑,您可以使用上面的任何链接查看我们在现实世界中的实现。

注意:我们的实现与上面描述的不完全相同(值得注意的是,我们在创建新的 FD/resource 映射时不检查 FD 的引用计数)。我认为这是因为我们依赖这样一个事实,即关闭其中一个管道不一定会导致另一端关闭,因此开放端的 FD 不能立即重用。不幸的是,编写代码的开发人员无法查询。