【问题标题】:Is there any way to execute a callback (on Linux) when a file descriptor is closed当文件描述符关闭时,有什么方法可以执行回调(在 Linux 上)
【发布时间】:2018-02-08 22:49:23
【问题描述】:

我正在为 Linux 开发一个 kevent/kqueue emulation 库。我是这个项目的新维护者,不幸的是,以前的维护者不再参与太多(所以我无法动脑筋)。

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

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

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

【问题讨论】:

  • 观看 /proc/self/fd/N?你仍然需要监听 那个 描述符上的事件,但我猜你已经有一个 epoll() 或某处的东西......是的,超级hackish。
  • 我猜如果 libkqueue 创建了自己的监视线程来进行监视...
  • 我不熟悉 kqueue API,但你的包装器中的一部分不会调用 epoll 吗?如果是这样,只需将 inotify fd 添加到描述符列表中即可轮询并通过调用回调来处理包装器中消失的文件。
  • 但是必须调用 epoll 才能注册 kqueue FD 已关闭的事实,所以当您在最后的 kqueue fd 上close() 时,没有任何东西可以在任何地方注册该事实...因为不再调用 libkqueue API。我们不能把这个负担放在应用程序上,而且 libkqueue 中没有全局事件循环,这就是我说的原因,是的,但我们需要一个单独的线程。
  • 如果您可以访问fd 作为驱动程序,那么内核会将close 函数(连同write 和read)映射到驱动程序的close 函数.

标签: c linux file epoll kevent


【解决方案1】:

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

【讨论】:

  • 我会在接受之前测试一下这一切是如何运作的,但这听起来不错,所以请点赞!
  • 谢谢阿兰。可能有有用的 epoll() 代码here
【解决方案2】:

解决方案相当简单,但实施起来有点烦人。它依赖一个名为F_SETSIG 的fcntl 来指定用于传达FD 状态更改的信号,并依赖一个名为F_SETOWN_EX 的fcntl 来指定应该将信号传递到哪个线程。

当应用程序启动时,它会生成一个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. Calls pthread_detach 以确保它被正确清理,而无需另一个线程需要执行明确的 pthread_join。
  5. Calls sigwaitinfo 等待信号传递。

应用程序线程:

  1. Uses pthread_once 在第一次创建 FD 时启动监控线程,然后等待监控线程完全启动。
  2. Uses F_SETSIG 指定 FD 打开/关闭时发送的信号,and F_SETOWN_EX 将这些信号定向到监控线程。

当被监控的 FD 关闭时,监控线程中的sigwaitinfo 调用会返回。在我们的例子中,我们使用管道来表示 kqueue,因此我们需要将接收到信号的 FD map 指向与我们需要释放的资源 (kqueues) 关联的那个。完成此映射后,我们可能(有关详细信息,请参阅下文)清理与 FD 对关联的资源,并再次调用 sigwaitinfo 以等待更多信号。

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2012-06-18
    • 2017-08-22
    • 2014-12-20
    • 2019-02-06
    • 1970-01-01
    • 1970-01-01
    • 2017-07-08
    • 1970-01-01
    • 2013-12-11
    相关资源
    最近更新 更多