前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何排查句柄泄露问题

如何排查句柄泄露问题

作者头像
范蠡
发布2021-04-08 10:29:31
4.7K0
发布2021-04-08 10:29:31
举报
文章被收录于专栏:高性能服务器开发

一. 缘来缘起

人间四月天,bug无处钻,让bug没有藏身之地。今天,我们来聊句柄泄漏的定位。部分朋友遇到性能问题时,束手无策。别担心,我们一起实践,不信你搞不定。

另外,性能优化,也是区分初级工程师和高级工程师的标志之一。在面试的时候,经常会被问到如何做性能优化,那些只背诵过八股文考试题目的人,有可能歇菜。

遇到性能瓶颈,该如何去优化呢?本文用实战例子,带大家一起来查问题,干货满满,建议有兴趣的朋友,亲自试一下。不仅为笔试面试,更重要是为实际工作。

二. 句柄泄漏

曾经,百度笔试的一个题目为:

一个进程能打开多少文件句柄?

看到这个问题,有的人懵圈了,还说不知道啊。其实,这个问题并不是考查你的记忆能力,而是考查你有没有一定的实战经验。

我们直接用ulimit -a命令来看下:

代码语言:javascript
复制
ubuntu@VM-0-15-ubuntu:~$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 3301
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 3301
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
ubuntu@VM-0-15-ubuntu:~$

可以看到,在我的机器上,一个进程能打开的最大句柄数是1024,我们来写个简单程序测试一下:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int fun() 
{
        static int count = 0;
        count++;

        int fd = open("/dev/urandom", O_RDONLY);
        if(-1 == fd)
        {
                printf("error, %d\n", count);
                exit(1);
        }

        // close(fd);   // do not close fd, just for testing

        return 0;
}

int main()
{
        while(1)
        {
                fun();
        }
}

编译运行,结果为:

代码语言:javascript
复制
ubuntu@VM-0-15-ubuntu:~$ gcc a.c && ./a.out
error, 1022
ubuntu@VM-0-15-ubuntu:~$

想一下,这是为什么?很显然,第1022次调用出错了,也就是说,已经成功生成了1021个句柄。不是能生成1024个句柄吗?另外3个去哪里呢?显然,另外3个是:标准输入、标准输出、标准错误。

可见,进程打开的句柄最大数,确实是1024(不同环境可能不一样)。那么,如果句柄一直不关掉,持续上涨,就会造成资源泄漏,导致系统性能降低,而且会导致程序出错,这是慢性病,严重得很。

三. 定位方法

下面,我们看一段有句柄泄漏的程序:

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int getRand() 
{
        int randNum = 0;
        int fd = open("/dev/urandom", O_RDONLY);
        if(-1 == fd)
        {
                // handle error
        }

        read(fd, (char *)&randNum, sizeof(int));
        return randNum;
}

int main()
{
        while(1)
        {
                getRand();
                sleep(1);
        }
}

有的朋友要说,这简单啊,一眼就知句柄泄漏。其实不然,实际工程的代码,经常是几万几十万行,肉眼看是不行的。

而且,很多时候,压根就不知道哪个进程在泄漏句柄。所以,我们首先要搞清楚的是,到底是哪个进程正在泄漏句柄。

现在,我们编译上述程序,生成a.out,然后运行,还说什么呢?各种linux命令,来搞个组合拳啊,查出泄漏的进程。

运行a.out后,让它泄漏一会儿,然后看看:

代码语言:javascript
复制
ubuntu@VM-0-15-ubuntu:~$ lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head
    116 1175
    104 13433
     52 9786
     48 13454
     44 994
     40 1331
     40 1130
     35 8200
     32 1385
     32 13485
ubuntu@VM-0-15-ubuntu:~$ lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head
    116 1175
    104 13433
     54 9786
     48 13454
     44 994
     40 1331
     40 1130
     35 8200
     32 1385
     32 13485
ubuntu@VM-0-15-ubuntu:~$ lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head
    116 1175
    104 13433
     63 9786
     48 13454
     44 994
     40 1331
     40 1130
     35 8200
     32 1385
     32 13485
ubuntu@VM-0-15-ubuntu:~$

看到没,这个组合命令,就是统计进程打开句柄数的,然后,你看,有个进程打开句柄的数量一直在上升,显然就是进程9786了,我们进一步看看究竟是何方妖怪:

代码语言:javascript
复制
ubuntu@VM-0-15-ubuntu:~$ ps -aux | grep 9786
ubuntu    9786  0.0  0.0   4216   776 pts/0    S+   20:07   0:00 ./a.out
ubuntu   10402  0.0  0.1  13228  1084 pts/1    S+   20:10   0:00 grep 9786
ubuntu@VM-0-15-ubuntu:~$

快看,找出了是a.out进程,感觉快要成功了。那么,我们看看这个进程在打开哪些句柄,如下:

代码语言:javascript
复制
ubuntu@VM-0-15-ubuntu:~$ ll /proc/9786/fd
total 0
lrwx------ 1 ubuntu ubuntu 64 Mar 30 20:07 0 -> /dev/pts/0
lrwx------ 1 ubuntu ubuntu 64 Mar 30 20:07 1 -> /dev/pts/0
lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:07 10 -> /dev/urandom
lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 100 -> /dev/urandom
lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 101 -> /dev/urandom
lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 102 -> /dev/urandom
lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 103 -> /dev/urandom
lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 104 -> /dev/urandom

从这里,就基本知道进程在打开/dev/urandom了。于是,直接在a.out进程对应的代码中搜索一下,就知道代码的位置了,然后就知道句柄泄漏的地方了。

可是,有的朋友还不放心,想知道a.out进程到底在干啥,那也可以,strace命令搞起,直接来看进程在做什么,如下:

代码语言:javascript
复制
ubuntu@VM-0-15-ubuntu:~$ sudo strace -p 9786
strace: Process 9786 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
open("/dev/urandom", O_RDONLY)          = 331
read(331, "\2203\263\244", 4)           = 4
nanosleep({1, 0}, 0x7fff31962d30)       = 0
open("/dev/urandom", O_RDONLY)          = 332
read(332, "\20S\367 ", 4)               = 4
nanosleep({1, 0}, 0x7fff31962d30)       = 0
open("/dev/urandom", O_RDONLY)          = 333
read(333, "bE\351\267", 4)              = 4
nanosleep({1, 0}, 0x7fff31962d30)       = 0
open("/dev/urandom", O_RDONLY)          = 334
read(334, "\265\16\2273", 4)            = 4

这下就完全清楚了,原来,进程在执行:

open("/dev/urandom", O_RDONLY)

这回更清楚了,所以,还说什么呢?直接去代码中搜索吧,然后找到句柄泄漏的地方,然后就发现,原程序中,没有close(fd)的操作。So nice.

四. 修复验证

从上述分析可知,需要关掉句柄fd, 故修复后的代码为:

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int getRand() 
{
        int randNum = 0;
        int fd = open("/dev/urandom", O_RDONLY);
        if(-1 == fd)
        {
                // handle error
        }

        read(fd, (char *)&randNum, sizeof(int));
        close(fd);
        return randNum;
}

int main()
{
        while(1)
        {
                getRand();
                sleep(1);
        }
}

经用同样的方法验证,再无句柄泄漏,问题搞定。

五. 最后的话

思路和工具,都很重要。在本文中,我们可以回顾一下,是怎样一步一步打开局面的?又是怎么灵活运用各种linux命令工具的?

对于笔试面试而言,八股文刷题要搞,也要注重实战经验积累,不然一问就歇菜。在实际工作中,各种思路和工具,亦不可或缺。

希望有兴趣的朋友实际操作一下,感受一下bug的定位过程,以后笔试、面试和工作中,就有谈资啦。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能服务器开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 缘来缘起
  • 二. 句柄泄漏
  • 三. 定位方法
  • 四. 修复验证
  • 五. 最后的话
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档