事情的起因是公司产品在做稳定性测试时发现系统空闲内存一直在减少,但使用top等命令查看时又找不到内存占用高的程序。

系统占用内存持续上涨:

top看不到res top看不到的内存_top看不到res

 普罗米修的统计:

top看不到res top看不到的内存_服务器_02

 命令查看系统内存:

top看不到res top看不到的内存_服务器_03

 使用top和ps命令也没有查到有占用内存高的程序,但是发现有个vp-video的进程数量很多。

使用命令查看进程数:

ps -aux | grep vp-video | wc

发现这个进程有九百多个,但每个进程所占内存极少,大概5M左右,全部加起来也不过5~6G的内存占用,距离15G的内存占用还差的很远。

使用命令查看链接状态:

netstat -anput | sort -k 2 -r | head -n 50

这次有了重大发现,有些vp-video进程接收队列产生了数据包堆积,如图:

top看不到res top看不到的内存_linux_04

 每个进程的接收队列大概堆积了50M的数据,这样的进程有197个,算下来差不多就是10个G了。

Recv-Q 中的数据还处于内核态,所以不会被top等工具统计到。

本以为已经找到了原因,由开发的同事把数据积压的问题解决了就可以了。没想到经过多方排查之后,问题好像又指向了系统内核当中。

同事在排查问题时发现,并不是程序处理能力不够,而是某些vp-video进程会出现一种假死状态。就是程序本身已经不再进行业务处理了,但是与之对应的网络端口还没有被关闭。这就导致了上游的数据发送程序一直能够将数据包发送到该进程的Recv-Q中,也就出现了之前看到的现象。

vp-video进程只有在收到父进程发送的kill 15信号后才会结束任务并退出,子进程信号处理函数中的log日志里也确实没有收到kill 15信号的记录。难道linux系统会存在信号丢失的问题?

通过gdb跟踪到已经处于假死状态的进程,打印出栈信息,发现程序进到了kill 15的信号处理函数,但卡死在了__lll_lock_wait_private()中。

top看不到res top看不到的内存_top看不到res_05

__lll_lock_wait_private又是在malloc()时调用的,现在问题明确了,malloc是不可重入函数, 如果重入了可能会死锁。通过栈信息可以看到,在进入信号处理之前,程序的业务处理逻辑正在malloc,中断处理函数中也调用了malloc函数,这就出现了malloc的重入,从而导致进程死锁。

解决方法:删除信号处理函数中的malloc调用,设置退出标记后便返回,业务处理流程中判断退出标记,执行相应的退出流程。

最后总结:

1 Recv-Q堆积会占用系统内存,这部分内存用top查不出来,需要用netstat查看。

2 malloc是不可重入函数, 如果重入了可能会死锁。所以信号处理函数里面不能调用malloc。

3 malloc的调用有可能是间接地或者说是隐式的。所以信号处理函数应该尽可能简单, 比如只设置一个flag, 然后让其它处理流程根据这个flag来做其它事情。