【实战指南】记一次定位fd泄漏问题

简介: 本文记录了一次文件描述符(fd)泄漏问题的排查过程。在项目压测中,进程因打开过多文件导致fd资源耗尽,最终无法创建新文件。通过分析错误码、查看/proc/pid/fd路径下的文件句柄信息,定位到临时文件未正确关闭的问题根源,并修复代码中遗漏的close调用。同时总结了Linux下进程资源限制的相关知识点,强调开发中应关注资源使用情况,避免类似问题发生。

记一次定位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中,进程资源限制主要有如下几项(括号内查看限制值):

  1. 进程打开的文件数量限制(ulimit -n):限制进程能够同时打开的文件数量,可以在/etc/security/limits.conf文件中设置。
  2. 进程内存使用限制(ulimit -m):限制进程在虚拟内存中使用的最大字节数,可以在/etc/security/limits.conf文件中设置。
  3. 进程CPU时间限制(ulimit -t):限制进程可以使用的CPU时间,可以在/etc/security/limits.conf文件中设置。
  4. 进程堆栈大小限制(ulimit -s):限制进程堆栈的大小,可以在/etc/security/limits.conf文件中设置。
  5. 进程可打开文件的最大大小限制(ulimit -f):限制进程可以创建的最大文件大小,可以在/etc/security/limits.conf文件中设置。
  6. 进程最大用户进程数限制(ulimit -u):限制进程可以创建的最大用户进程数,可以在/etc/security/limits.conf文件中设置。
  7. 进程最大打开文件描述符数限制(ulimit -Hn):限制进程可以同时打开的文件描述符数,可以在/etc/security/limits.conf文件中设置。
  8. 进程最大线程数限制(ulimit -i):限制进程可以创建的最大线程数,可以在/etc/security/limits.conf文件中设置。

以上限制都可以通过ulimit命令来查看和修改,也可以通过修改/etc/security/limits.conf文件来永久修改。

相关文章
|
人工智能 监控 供应链
AI技术创业有哪些机会?
本文探讨了AI技术创业的多个机会,包括提供行业解决方案、开发智能产品和服务以及教育和培训,为创业者在医疗保健、金融服务、零售、教育等多个领域提供了丰富的机遇。
831 2
|
7月前
|
缓存 编译器 Shell
【实战指南】 CMake搭建编译环境总结
本文总结了使用CMake搭建编译环境的技巧,涵盖单个及多个源文件的编译、CMakeLists嵌套管理、变量设置、交叉编译配置、常用编译选项及警告处理等内容。通过实例说明了如何高效组织工程结构,并利用CMake灵活控制编译流程,适用于嵌入式开发场景。
1034 47
|
7月前
|
缓存 NoSQL Unix
【实战指南】守护进程服务实现
本文介绍了在Linux系统中实现守护进程异常重启的几种方案。通过理解僵死进程和信号处理机制,提出了基于SIGCHLD信号监听、轮询proc文件系统及waitpid接口的三种方法,并给出了C++实现代码。最终选择轮询方式以提升稳定性,确保服务进程在崩溃后能自动重启,保障系统可靠性。
400 66
|
消息中间件 Java 中间件
秒懂消息队列MQ,万字总结带你全面了解消息队列MQ
消息队列是大型分布式系统不可缺少的中间件,也是高并发系统的基石中间件,所以掌握好消息队列MQ就变得极其重要。接下来我就将从零开始介绍什么是消息队列?消息队列的应用场景?如何进行选型?如何在Spring Boot项目中整合集成消息队列。
25930 10
秒懂消息队列MQ,万字总结带你全面了解消息队列MQ
|
移动开发 Linux 开发工具
git diff ^M不同操作系统下换行符统一的小知识
不同操作系统下的换行不一致,当一个项目的开发者分别再linux/mac/win下做了代码提交改动之后,使用git diff命令时,可能会发现,即便啥也没改,当时有很多变动,显示^M的差别 主要原因就是换行的问题 解决这个问题的一个办法就是统一换行 今天介绍的一个小技巧就是通过设置全局的换行来实现多操作系统的换行符统一
1739 0
|
7月前
|
设计模式 人工智能
单例模式中的隐藏陷阱:你真的了解单例吗?
本文通过一个实际案例揭示了单例模式中常见的隐藏陷阱——在析构函数中调用其他单例对象导致程序崩溃的问题。代码示例展示了因单例析构顺序不确定而引发的 `crash`,并结合 AI 的分析提出两种规避策略:一是避免析构、二是通过全局状态检测对象存活。文章指出,依赖不确定的语言特性并非良策,真正的解决之道在于合理设计,确保程序行为可预期、可控制。
263 71
|
缓存 Linux 开发工具
CentOS 7- 配置阿里镜像源
阿里镜像官方地址http://mirrors.aliyun.com/ 1、点击官方提供的相应系统的帮助 :2、查看不同版本的系统操作: 下载源1、安装wget yum install -y wget2、下载CentOS 7的repo文件wget -O /etc/yum.
266656 0
|
7月前
|
C语言 C++
【实战指南】 C/C++ 枚举转字符串实现
本文介绍了在C/C++中实现枚举转字符串的实用技巧,通过宏定义与统一管理枚举名,提升代码调试效率并减少维护错误。
441 66
|
7月前
|
程序员 编译器 C++
【实战指南】C++ lambda表达式使用总结
Lambda表达式是C++11引入的特性,简洁灵活,可作为匿名函数使用,支持捕获变量,提升代码可读性与开发效率。本文详解其基本用法与捕获机制。
277 60
|
Ubuntu Java Linux
update-alternatives命令如何使用?
【8月更文挑战第5天】update-alternatives命令如何使用?
1404 5