8

有没有办法遍历已经打开的文件描述符(由父进程打开)并在子进程中一个一个地关闭它们?

操作系统:Unix。

关闭原因: setrlimit() 的 RLIMIT_NOFILE 限制限制了一个进程可以分配的文件描述符的数量。如果我们想通过设置这个限制来限制我们的子进程,它取决于已经分配的文件描述符。

尝试在子进程中设置此限制会受到限制,因为父进程有一些打开的文件描述符,因此我们不能将此限制设置为小于该数字。

示例:如果父进程分配了 10 个文件描述符,并且我们希望将子进程的文件描述符数限制为小于 10(例如 3),我们需要关闭子进程内的 7 个文件描述符。

对此的解决方案可以使所有希望限制其子进程创建新文件或打开新网络连接的人受益。

4

2 回答 2

5

以下成语并不少见(取自MIMEDefang的 C 部分):

/* Number of file descriptors to close when forking */
#define CLOSEFDS 256
...

static void
closefiles(void)
{
    int i;
    for (i=0; i<CLOSEFDS; i++) {
        (void) close(i);
   }
}

(那是从 mimedefang-2.78 开始的,实现在以后的版本中略有改变。)

这有点像 hack(正如 MIMEDefang 代码自由承认的那样)。STDERR_FILENO+1在许多情况下,从 FD 3(或)开始而不是 0 会更有用。close()返回EBADF无效的 FD,但这通常不会出现问题(至少在 C 中不会,在其他语言中可能会引发异常)。

由于您可以确定文件描述符上限,getrlimit(RLIMIT_NOFILE,...)定义为:

RLIMIT_NOFILE

这是一个大于系统可以分配给新创建的描述符的最大值的数字。如果超出此限制,分配文件描述符的函数将失败,并将 errno 设置为 [EMFILE]。此限制限制了进程可以分配的文件描述符的数量。

您可以使用它(减去 1)作为循环的上限。上面的和ulimit -ngetconf OPEN_MAXsysconf(OPEN_MAX)应该都同意。

由于open()总是分配最低的空闲FD,所以打开文件的最大数量和最高的FD+1是同一个数。

要确定哪些 fd 是打开的,而不是使用如果 fd 未打开则返回close()的 no-op (虽然调用条件没有明显的好处)。的循环超过 0 ..调用/ 。lseek(fd, 0, SEEK_CUR)EBADFlseek()close()socatfilanFD_SETSIZEfstat()fstat64()

守护任意进程的libslackdaemon实用程序也使用这种蛮力方法(同时确保在使用时保持前三个描述符打开inetd)。

在您的程序可以跟踪文件句柄的情况下,最好这样做,或者FD_CLOEXEC在可用的情况下使用。但是,如果您希望进行防御性编码,您可能更愿意不信任您的父进程,例如由浏览器启动的外部处理程序/查看器进程,例如 Unix 平台上这个长期存在且古老的 Mozilla 错误

对于偏执狂(您是否希望您的 PDF 查看器继承每个打开的 Firefox FD,包括您的缓存和打开的 TCP 连接?):

#!/bin/bash
# you might want to use the value of "ulimit -n" instead of picking 255
for ((fd=3; fd<=255; fd++)); do
  exec {fd}<&- # close
done
exec /usr/local/bin/xpdf "$@"

15 年后更新此问题已在 Firefox 58 (2018) 中解决,当时进程创建从 Netscape Portable Runtime (NSPR) API 更改为使用 LaunchApp。

于 2014-04-01T16:56:26.187 回答
0

据我所知,没有通用的方法来迭代 Unix/POSIX 中的打开文件描述符。处理您所描述的问题的传统方法是在您自己的代码中跟踪它们,如果需要,使用数组或列表等数据结构,并在子进程之后fork()但之前关闭它们exec()

exec()但是,如果您在创建子进程后调用,某些操作系统会提供潜在的解决方案。通过使用或与操作系统的标志一起设置FD_CLOEXEC文件描述符fcntl()的标志,指示在调用之前关闭该特定文件描述符。您必须查阅目标操作系统的文档,以了解是否支持这些标志以及哪些标志受支持。O_CLOEXECopen()exec()

于 2013-03-07T10:44:11.530 回答