在Linux中检查给定进程的打开文件描述符限制

66

最近我遇到了一个Linux进程"泄漏"文件描述符的问题:它打开了这些文件描述符,但没有正确地关闭其中一部分。

如果我监控这个进程,我可以提前得知该进程正在接近其极限。

在Ubuntu Linux系统中,是否有一种不错的Bash或Python方法来检查给定进程的FD使用率?

编辑:

我现在知道如何检查有多少个打开的文件描述符;我只需要知道一个进程允许有多少个文件描述符。有些系统(如Amazon EC2)没有/proc/pid/limits文件。


你使用的是哪个 Amazon EC2 Linux 操作系统,没有 /proc/pid/limits?它在 RHEL 5 上可用。如果你想要针对不同的操作系统解决方案,请告诉我们具体是哪一个。 - mark4o
我有一个Ubuntu EC2服务器,但我对适用于各种Linux发行版的更通用的解决方案感兴趣。 - Adam Matan
7个回答

132

统计 /proc/<pid>/fd/ 目录下的文件或目录数量。进程所应用的硬限制和软限制可以在 /proc/<pid>/limits 中找到。


我认为,这种方法可能比轮询lsof更加优雅。 - Victor Sorokin
是的,但是一些进程 - 比如Web服务器 - 正在使用ulimit请求更大的配额,我想监视它们的FD使用情况。 - Adam Matan
3
任何进程都不允许将其配额提高到硬限制之上,可以使用“ulimit -Hn”命令查看硬限制。 - caf
限制进程输入在2.6.24版本之后可用。对于一些使用较旧内核的用户来说,这真是遗憾 :(。 - kikeenrique

34
Linux内核提供的获取资源限制的唯一接口是getrlimit()/proc/pid/limitsgetrlimit()只能获取调用进程的资源限制。/proc/pid/limits允许您获取具有相同用户ID的任何进程的资源限制,并且在RHEL 5.2、RHEL 4.7、Ubuntu 9.04以及具有2.6.24或更高内核版本的任何发行版上都可用。
如果您需要支持旧的Linux系统,那么您将不得不让进程本身调用getrlimit()。当然,最简单的方法是修改程序或其使用的库。如果您正在运行该程序,则可以使用LD_PRELOAD将自己的代码加载到程序中。如果这些都不可能,那么您可以使用gdb附加到进程并让它在进程内执行该调用。您还可以使用ptrace()自己执行相同的操作,附加到进程,将调用插入其内存等,但这非常复杂且不建议使用。
在适当的权限下,其他方法涉及查看内核内存、加载内核模块或以其他方式修改内核,但我假设这些都不可能。

2

您可以尝试编写脚本,定期调用lsof -p {PID}来查看给定pid的情况。


1
lsof 给出了很多无关的条目(例如内存中的共享库)。 - Adam Matan
1
我猜,无论fds连接到共享内存库还是“通常”的应用程序特定文件,这些fds仍然使用它们的共享。 - Victor Sorokin
1
不,它们不是打开的文件描述符,也不计入文件描述符限制。 - mark4o
1
此外,lsof不会说明每个进程的FD限制。 - Adam Matan

2

你需要bash/python方法的翻译。除了手动浏览/proc/$pid/fd等文件外,ulimit是最好的bash方法。对于Python,您可以使用资源模块。

import resource

print(resource.getrlimit(resource.RLIMIT_NOFILE))
$ python test.py

(1024, 65536)

resource.getrlimit 对应于 C 程序中的 getrlimit 调用。结果表示所请求资源的当前值和最大值。在上面的示例中,当前(软)限制为 1024。这些值是现代 Linux 系统上的典型默认值。


resource.RLIMIT_NOFILE:当前进程的最大打开文件描述符数量,我想获取另一个进程的结果,而不是自己的。 - Adam Matan

2

查看使用文件句柄最多的前20个进程:

for x in `ps -eF| awk '{ print $2 }'`;do echo `ls /proc/$x/fd 2> /dev/null | wc -l` $x `cat /proc/$x/cmdline 2> /dev/null`;done | sort -n -r | head -n 20

输出格式为文件句柄计数、进程ID、命令行。

示例输出:

701 1216 /sbin/rsyslogd-n-c5
169 11835 postgres: spaceuser spaceschema [local] idle
164 13621 postgres: spaceuser spaceschema [local] idle
161 13622 postgres: spaceuser spaceschema [local] idle
161 13618 postgres: spaceuser spaceschema [local] idle

谢谢,但我有一些警告: bash: 警告:命令替换:忽略输入中的空字节 bash: 警告:命令替换:忽略输入中的空字节 bash: 警告:命令替换:忽略输入中的空字节 bash: 警告:命令替换:忽略输入中的空字节 bash: 警告:命令替换:忽略输入中的空字节 bash: 警告:命令替换:忽略输入中的空字节 799 3708 /usr/sbin/mysqld 469 12904 pveproxy worker 454 18544 pveproxy worker 442 5236 /usr/sbin/mysqld - arnolem

0
在 CentOS 6 及以下版本(使用 GCC 3 的任何版本),您可能会发现调整内核限制无法解决问题。这是因为在 GCC 中编译时设置了一个FD_SETSIZE值。因此,您需要增加该值,然后重新编译进程。
此外,如果您正在使用 libpthread 库,您可能会发现由于 已知问题 而泄漏文件描述符。此调用已在 GCC 4 / CentOS7 / RHEL 7 中集成到 GCC 中,并似乎已经解决了线程问题。

0
Python包装器使用了优秀的psutil库:
import psutil

for p in psutil.process_iter(attrs=['pid', 'name', 'username', 'num_fds']):
    try:
        soft, hard = p.rlimit(psutil.RLIMIT_NOFILE)
        cur = p.info['num_fds']
        usage = int(cur / soft * 100)
        print('{:>2d}% {}/{}/{}'.format(
            usage,
            p.info['pid'],
            p.info['username'],
            p.info['name'],
            ))
    except psutil.NoSuchProcess:
        pass

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