在子进程中关闭打开的文件描述符

8
有没有一种方法可以在子进程中遍历已经打开的文件描述符(由父进程打开),并逐个关闭它们?
操作系统:Unix。
关闭的原因:setrlimit()函数中的RLIMIT_NOFILE限制了一个进程可以分配的文件描述符数量。如果我们想通过设置此限制来限制子进程,它取决于已分配的文件描述符。
尝试在子进程中设置此限制受到限制,因为父进程具有某些打开的文件描述符,因此我们无法将此限制设置为小于该数字。
例如:如果父进程已分配了10个文件描述符,并且我们希望将子进程的文件描述符数量限制在小于10个(例如3个),则需要在子进程内关闭7个文件描述符。
解决此问题的方法可使所有希望限制其子进程不创建新文件或打开新网络连接的人受益。

“如果父进程分配了10个文件描述符...”:对于您来说,“分配”是什么意思?因为文件描述符要么是打开的,要么是关闭的。分配这个词通常与内存管理相关联... - thkala
更正:我想说的是父进程的10个打开文件描述符,感谢thkala的纠正。 - learner
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的,实现在后续版本中有轻微改动。)

这种方法有点像黑客技术(正如MIMEDefang代码所承认的那样)。在许多情况下,从FD 3(或STDERR_FILENO+1)开始比从0开始更有用。close()会返回EBADF,如果使用无效的FD,但这通常不会出现问题(至少在C语言中不会,在其他语言中可能会抛出异常)。

由于您可以使用getrlimit(RLIMIT_NOFILE,...)确定文件描述符上限,该函数被定义为:

RLIMIT_NOFILE

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

您可以使用此数减1作为循环的上限。 上述内容以及ulimit -ngetconf OPEN_MAXsysconf(OPEN_MAX)应该都是相同的。

由于open()总是分配最低的空闲FD,因此最大打开文件数和最高FD+1是相同的数字。

为了确定打开了哪些文件描述符,可以使用无操作的lseek(fd, 0, SEEK_CUR)代替close(),如果fd未打开,则会返回EBADF(虽然对于有条件的close()调用来说,调用lseek()没有明显的好处)。socatfilan循环遍历0.. FD_SETSIZE,调用fstat()/fstat64()libslack daemon utility是将任意进程守护化的工具,也使用这种暴力方法(在inetd下使用时确保前三个描述符保持打开状态)。
如果您的程序可以跟踪文件句柄,最好这样做,或者在可用时使用FD_CLOEXEC。但是,如果您希望进行防御性编码,可能更喜欢不信任父进程,例如对于由浏览器启动的外部处理程序/查看器进程,例如Unix平台上的长期存在的和古老的Mozilla bug

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

#!/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 后,该问题得到解决。

0
据我所知,在Unix/POSIX中没有通用的方法来迭代打开的文件描述符。处理您所描述的问题的传统方法是在自己的代码中跟踪它们,如果需要使用诸如数组或列表之类的数据结构,并在fork()但在exec()之前关闭子进程中的文件描述符。
然而,一些操作系统提供了一个潜在的解决方案,如果您在创建子进程后调用exec()。通过使用fcntl()设置FD_CLOEXEC标志或使用open()O_CLOEXEC标志,操作系统被指示在调用exec()之前关闭该特定文件描述符。您将不得不查阅目标操作系统的文档,以找出是否支持这些标志中的哪些。

我能否循环遍历从0到最大允许的文件描述符号,并根据dup(int fildes)函数的输出进行调用?因为如果dup()失败,它会返回“-1”。这是一种不好的方式还是另一种粗糙的了解我正在寻找的信息的方式? - learner
@user2136293: 你的提议中有很多假设,这些假设可能会破坏你的代码:1. 你假设你可以可靠地找出最大FD号码是多少。除了MAX_INT之外,没有其他可靠的限制。2. 你假设这个号码将是“合理的”。你要探测2^31个FD吗?3. 你假设你可以盲目地关闭你不喜欢的任何文件描述符。如果你的某个库由于某种特定原因保持打开状态,则怎么办? - thkala
@user2136293:你到底想做什么?[你真正的问题是什么?](http://mywiki.wooledge.org/XyProblem) - thkala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接