文件描述符与上限诊断
目标
确认文件描述符限制存在几个层级,并亲手制作追踪 fd 泄漏的工具。
为什么重要
看到 Too many open files 时,几乎所有人都会执行 ulimit -n 65536。但这个值只是当前 shell 的值,对已经运行的服务毫无影响。真正需要查看的是 /proc/<PID>/limits,实际使用量则是 ls /proc/<PID>/fd | wc -l(lsof -p 还会统计 cwd、可执行文件和 mmap 库,而这些不计入限制)。提高限制很少是真正的答案——多数情况是泄漏,提高限制只是把故障推迟几个小时。泄漏还是正常负载,要根据 fd 数量的增长模式来区分。
步骤
在 /root/fd 目录中工作。
- 从
/proc/1/limits读取打开文件限制,并按以下两行写入/root/fd/limits.txt。soft=<값>hard=<값>
- 在后台启动一个同时打开
/etc/hostname至少 50 次的进程,并把 PID 写入/root/fd/holder_pid.txt。该进程必须在后续步骤中始终存活。 - 在该进程的 fd 中,统计指向
/etc/hostname的数量,以及它们所指向的不同目标数量,按以下两行写入/root/fd/distinct.txt。count=<개수>distinct=<개수>
- 在降低限制的环境中持续打开文件,重现
Too many open files错误,并把消息保存到/root/fd/emfile.txt。 - 只把整个系统的 fd 上限数字写入
/root/fd/file_max.txt。 - 在后台启动
python3 -m http.server 8099 --bind 127.0.0.1,并把 PID 写入/root/fd/http_pid.txt。 - 创建
/root/fd/fdtop.sh <N>。把打开 fd 数量最多的 N 个进程按<fd개수> <PID>格式降序输出。必须正好输出 N 行。 - 将
/etc/hostname的 inode 编号写入/root/fd/inode.txt。
提示
- 使用
python3 -c "fs=[open('/etc/hostname') for _ in range(60)]; import time; time.sleep(9999)" &可以持续占用 fd。如果不保存在列表中,文件会立即关闭。 - 可以像
bash -c 'ulimit -n 32; python3 -c "..."' 2> /root/fd/emfile.txt这样在子 shell 中降低限制。 /proc/sys/fs/file-nr包含할당된 수 / 미사용 할당분 / 최대값三个数字。- 常见错误 1:第 7 步会因
/proc/self或已经结束的进程而报错。请跳过失败的项目。 - 常见错误 2:第 4 步如果试图从标准输出获取错误,文件中会什么也没有。错误会写入标准错误。
读取真实限制
从 /proc/1/limits 读取打开文件限制,并按以下两行写入 /root/fd/limits.txt。
soft=<값>hard=<값>
不要查看当前 shell 的 ulimit,而要查看该进程的 /proc//limits。‘Max open files’行会并排显示 soft 与 hard。
占用 50 个 fd
在后台启动一个同时打开 /etc/hostname 至少 50 次的进程,并把 PID 写入 /root/fd/holder_pid.txt。该进程必须在后续步骤中始终存活。
可以用 python3 多次打开同一个文件,保存到列表中,然后 sleep。如果不保存在变量中,文件会立即关闭。
统计 fd 数量与目标数量
在该进程的 fd 中,统计指向 /etc/hostname 的数量,以及它们所指向的不同目标数量,按以下两行写入 /root/fd/distinct.txt。
count=<개수>distinct=<개수>
对 /proc//fd 中的每一项执行 readlink,确认其目标。观察多次打开同一文件时会发生什么。
重现 EMFILE
在降低限制的环境中持续打开文件,重现 Too many open files 错误,并把消息保存到 /root/fd/emfile.txt。
在子 shell 中降低 ulimit -n,然后不断打开文件。把错误消息保存到文件。
读取系统整体上限
只把整个系统的 fd 上限数字写入 /root/fd/file_max.txt。
/proc/sys/fs/file-nr 包含三个数字,最后一个是最大值。
套接字也是 fd
在后台启动 python3 -m http.server 8099 --bind 127.0.0.1,并把 PID 写入 /root/fd/http_pid.txt。
在 127.0.0.1 上启动 python3 -m http.server,再对该进程的 fd 执行 readlink。
列出 fd 数量最多的进程
创建 /root/fd/fdtop.sh <N>。把打开 fd 数量最多的 N 个进程按 <fd개수> <PID> 格式降序输出。必须正好输出 N 行。
统计 /proc/*/fd 中的项目数并排序。跳过发生权限错误的项目。
从 fd 追踪到 inode
将 /etc/hostname 的 inode 编号写入 /root/fd/inode.txt。
用 stat -L 跟随符号链接,就能得知该 fd 指向文件的 inode。