记一次定位fd泄漏问题
最近项目压测遇到一个fd创建失败的问题,仔细一查发现进程竟然使用了1027个句柄,导致fd资源耗尽无法再创建,记录一下踩坑全过程。
问题背景
某个功能模块需要创建唯一的临时文件用于处理日常事件,当事件比较频繁时,就会频繁创建文件。 模拟示例如下:
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> void createFileName() { int fd; char temp_file[]="/tmp/tmp_XXXXXX"; /*Creat a temp file.*/ if((fd = mkstemp(temp_file)) == -1){ printf("Create %s failed. errno = %d.\n", temp_file, errno); } unlink(temp_file); } int main(int argc, char *argv[]) { for (int i = 0; i < 2000; i++) { createFileName(); } while(1); return 0; }
当上述代码执行时,就会报错:
... Create /tmp/tmp_sb1Ub1 failed. errno = 24. Create /tmp/tmp_Zd8VM0 failed. errno = 24. Create /tmp/tmp_veVqq3 failed. errno = 24. Create /tmp/tmp_HFGEU2 failed. errno = 24. Create /tmp/tmp_Wp2wO1 failed. errno = 24. ...
这里的示例代码比较简单,一眼就能看出原因。假设这段代码藏在庞大工程的小角落里,分析一下如何定位问题。
问题分析
① 通过系统接口返回的错误码,分析问题。
查看Linux错误码表,可知当前进程open 太多文件。
errno: 24 Too many open files
② 查看当前进程打开了哪些文件
$ ps -ef | grep a.out dx 14101 13040 90 07:02 pts/3 00:00:14 ./a.out $ ls /proc/14101/fd | wc -l 1027 $ ls -al /proc/14101/fd ... lrwx------ 1 dx dx 64 Mar 29 07:07 989 -> '/tmp/tmp_mDhS7e (deleted)' lrwx------ 1 dx dx 64 Mar 29 07:07 99 -> '/tmp/tmp_2vNCsd (deleted)' lrwx------ 1 dx dx 64 Mar 29 07:07 990 -> '/tmp/tmp_RMAFDf (deleted)' ...
/proc/pid/fd
路径下存着当前占用的fd资源,每个fd都会指向对应的文件名。由此可以定位到哪些路径大量占用。
通过终端的输出大致可以猜出/tmp/
路径下大量的文件已删除,引用未释放。对应代码即unlink
,没有close
。
对应修改,加上close
即可:
void createFileName() { int fd; char temp_file[]="/tmp/tmp_XXXXXX"; /*Creat a temp file.*/ if((fd = mkstemp(temp_file)) == -1){ printf("Create %s failed. errno = %d.\n", temp_file, errno); } close(fd); // 及时关闭mkstemp fd unlink(temp_file); }
总结
在Linux中,给每个进程分配的资源是有限制的,为的是避免某个进程把Linux资源耗尽,导致其他进程异常。同样在开发中要时刻关注这些资源是否异常。
在Linux中,进程资源限制主要有如下几项(括号内查看限制值):
- 进程打开的文件数量限制(ulimit -n):限制进程能够同时打开的文件数量,可以在/etc/security/limits.conf文件中设置。
- 进程内存使用限制(ulimit -m):限制进程在虚拟内存中使用的最大字节数,可以在/etc/security/limits.conf文件中设置。
- 进程CPU时间限制(ulimit -t):限制进程可以使用的CPU时间,可以在/etc/security/limits.conf文件中设置。
- 进程堆栈大小限制(ulimit -s):限制进程堆栈的大小,可以在/etc/security/limits.conf文件中设置。
- 进程可打开文件的最大大小限制(ulimit -f):限制进程可以创建的最大文件大小,可以在/etc/security/limits.conf文件中设置。
- 进程最大用户进程数限制(ulimit -u):限制进程可以创建的最大用户进程数,可以在/etc/security/limits.conf文件中设置。
- 进程最大打开文件描述符数限制(ulimit -Hn):限制进程可以同时打开的文件描述符数,可以在/etc/security/limits.conf文件中设置。
- 进程最大线程数限制(ulimit -i):限制进程可以创建的最大线程数,可以在/etc/security/limits.conf文件中设置。
以上限制都可以通过ulimit命令来查看和修改,也可以通过修改/etc/security/limits.conf文件来永久修改。