【发布时间】:2010-09-21 00:51:55
【问题描述】:
当我们在 Unix 中执行 fork 时,打开的文件句柄会被继承,如果我们不需要使用它们,我们应该关闭它们。但是,当我们使用库时,可能会打开我们无权访问的文件句柄。我们如何检查这些打开的文件句柄?
【问题讨论】:
当我们在 Unix 中执行 fork 时,打开的文件句柄会被继承,如果我们不需要使用它们,我们应该关闭它们。但是,当我们使用库时,可能会打开我们无权访问的文件句柄。我们如何检查这些打开的文件句柄?
【问题讨论】:
正如@Louis Gerbarg 的回答中提到的,这些库可能希望文件句柄在fork() 上保持打开状态(毕竟,这应该是父进程的几乎相同的副本)。
大多数人遇到的问题是exec(),它通常跟在fork() 后面。在这里,正确的解决方案是让创建句柄的库将它们标记为 close-on-exec (FD_CLOEXEC)。
在多线程程序使用的库中,在创建文件句柄和在其上设置FD_CLOEXEC 的库之间存在竞争条件(另一个线程可以在两个操作之间fork())。为了解决这个问题,Linux 内核中引入了O_CLOEXEC。
【讨论】:
你可以从 shell 做:
lsof -P -n -p _PID_
PID 是您的进程 pid。
【讨论】:
首先,您实际上并不需要非常关心您不知道的打开文件描述符。如果你知道你不会再写信给他们,关闭它们是个好主意而且不会有什么坏处——毕竟你只是做了一个 fork(),fds 打开了两次。但同样,如果你让它们打开,它们也不会打扰你——毕竟,你不知道它们,你大概不会随意给它们写信。
至于您的第三方库会做什么,无论哪种方式都有点折腾。有些人可能不希望遇到使用 fork() 的情况,并且最终可能会意外地从两个进程写入同一个 fd 而没有任何同步。其他人可能不希望您关闭他们的 fds。你必须检查一下。这就是为什么在库中随机打开文件描述符而不将其交给调用者管理是个坏主意的原因。
话虽如此,本着回答原始问题的精神,没有特别好的方法。您可以在文件描述符上调用dup() 或dup2();如果它已关闭,调用将失败并显示EBADF。所以你可以说:
int newfd = dup(oldfd);
if (newfd > 0)
{
close(newfd);
close(oldfd);
}
但此时您最好先说 close(oldfd) 而忽略任何 EBADF。
假设您仍然想选择关闭所有内容的核选项,那么您需要找到可能的打开文件描述符的最大数量。假设 1 到 65,535 不是一个好主意。首先,fds 当然从 0 开始,但也没有定义特定的上限。为了便于移植,POSIX 的sysconf(_SC_OPEN_MAX) 应该告诉您,在任何健全的 POSIX 系统上,尽管严格来说它是可选的。如果您感到偏执,请检查 -1 的返回值,尽管此时您大多不得不退回到硬编码值(除非您正在做一些非常奇怪的事情,否则 1024 应该没问题)。或者,如果您对 Linux 特定感到满意,您可以在 /proc 中进行挖掘。
别忘了不要关闭 fds 0、1 和 2 - 这真的会让人感到困惑。
【讨论】:
这不是设计问题吗?在初始化打开这些文件的库之前,您的进程是否可以分叉?
【讨论】:
我同意其他人所说的关闭随机文件是危险的。您最终可能会为所有第三方工具提交一些非常有趣的错误报告。
也就是说,如果您知道您不需要打开这些文件,您可以随时遍历所有有效的文件描述符(1 到 65535,IIRC)并关闭您不需要的所有文件不认识。
【讨论】:
如果库正在打开你不知道的文件,你怎么知道他们在分叉后不需要它们?未导出的句柄是一个内部库细节,如果库想要关闭它们,它将注册一个 atfork() 处理程序来关闭它们。在一段代码后面走来走去关闭它的文件句柄将导致微妙的难以调试的问题,因为当库尝试使用它知道它正确打开但没有关闭的句柄时会出现意外错误。
【讨论】:
在 Linux 中,您可以检查 /proc/<pid>/fd 目录 - 每个打开的 fd 都会有一个文件,命名为句柄。我几乎可以肯定这种方式是不可移植的。
您也可以使用lsof - 根据man lsof,可用于Linux、AIX、FreeBSD 和NetBSD。
【讨论】:
只是一个链接,但它似乎很有帮助:How many open files? at netadmintools.com。似乎使用 /proc 调查来了解进程的打开文件,不确定这是唯一的方法还是有 API。为此类信息解析文件可能有点……混乱。此外,/proc 也可能被弃用,需要检查。
【讨论】:
合理的库将始终具有释放它们分配的任何资源(例如文件句柄)的函数。
【讨论】: