内存不足引起的SIGKILL:一个缓冲区不断增长问题的定位与解决(解释SIGKILL原因)

摘要:
新版本的录音节目快完成了。添加新功能“报警记录”后,测试开始。因此,测试的结果是程序在运行一段时间后会自动重新启动,并继续;有时每隔几个小时,有时甚至每分钟重新启动一次。根据在线数据,“事实上,进程没有意识到SIGKILL信号,因为信号到达内核。到达点,init将停止进程。进程永远没有机会捕捉信号和动作”,系统似乎向无法捕捉的程序发送信号SIGKILL,然后程序退出。因此,怀疑程序占用了太多的系统资源,导致程序被OS杀死。

新版本的录制程序终于快完工了,在添加了一个新特性“报警录制”后,就开始测试了。

一开始就不顺利:程序正常运行一段时间后就会崩溃,由于程序添加了守护进程,在崩溃后会自动重启。

因此测试得到的结果就是:程序运行一段时间后就自动重启,并不断持续;有时几个小时重启一次,有时甚至每分钟都重启。

废话少说,开始定位问题:

1.去掉自动重启功能,在gdb下运行,20多分钟后就崩溃了:

 

2011-10-26 17:50:22,565: INFO  : recved alarm:[10.0.60.2-1-3905],type:12, startTime:1319622622, recvTime:1319622622

2011-10-26 17:50:22,628: INFO  : SetAlmStatus CHAN[1] = TRUE

2011-10-26 17:50:22,628: INFO  : recved alarm:[10.0.60.2-1-3905],type:12, startTime:1319622622, recvTime:1319622622

2011-10-26 17:50:22,962: NOTICE  : Open file:'/figure/data/AlarmRecord/StreamTS/1-码流_魅力音 乐主路/2011-10-26/20111026175022.ts'

[Thread 0xa7db70 (LWP 6645) exited]

[Thread 0xa3cb70 (LWP 6644) exited]

[Thread 0x5cdfb70 (LWP 6643) exited]

[Thread 0x45fdb70 (LWP 6642) exited]

[Thread 0x3bfcb70 (LWP 6641) exited]

[Thread 0x240fb70 (LWP 6640) exited]

[Thread 0x52deb70 (LWP 6638) exited]

[Thread 0x1a0eb70 (LWP 6637) exited]

[Thread 0x67b7b70 (LWP 6636) exited]

[Thread 0x31fbb70 (LWP 6635) exited]

[Thread 0x956b70 (LWP 6634) exited]

[Thread 0x935b70 (LWP 6633) exited]

[Thread 0x8f4b70 (LWP 6632) exited]

Program terminated with signalSIGKILL, Killed.

The program no longer exists.

显示程序是收到SIGKILL退出的,bt查看调用堆栈,也是空的。

2.查看APUE,说SIGKILL信号是不可被捕捉的。网上资料说“In fact, the process isn't even made aware of the SIGKILL signal since the signal goes straight to the kernel init. At that point, init will stop the process. The process never gets the opportunity to catch the signal and act on it”,看样子是系统向程序发送了无法被捕捉的信号SIGKILL,然后程序就退出了。因此怀疑是程序占用的系统资源过多,导致程序被OS给干掉了。

3.查系统日志
既然怀疑,就要去找证据。在/var/log/message中发现如下记录:

Oct 26 17:17:30 localhost kernel: Record invoked oom-killer: gfp_mask=0x280da, order=0, oomkilladj=0

Oct 26 17:17:31 localhost kernel: Record cpuset=/ mems_allowed=0

Oct 26 17:17:31 localhost kernel: Pid: 6350, comm: Record Not tainted 2.6.31.5-127.fc12.i686.PAE #1

Oct 26 17:17:31 localhost kernel: Call Trace:

Oct 26 17:17:31 localhost kernel: [<c049d891>] oom_kill_process+0x6e/0x1f2

Oct 26 17:17:32 localhost kernel: [<c049dd4f>] ? select_bad_process+0x87/0xce

Oct 26 17:17:32 localhost kernel: [<c049de09>] __out_of_memory+0x73/0x82

Oct 26 17:17:32 localhost kernel: [<c049de7b>] out_of_memory+0x63/0x88

Oct 26 17:17:32 localhost kernel: [<c04a00aa>] __alloc_pages_nodemask+0x382/0x447

Oct 26 17:17:32 localhost kernel: [<c04af062>] alloc_pages_node.clone.0+0x16/0x18

Oct 26 17:17:32 localhost kernel: [<c04afd63>] handle_mm_fault+0x1d7/0x8f2

Oct 26 17:17:32 localhost kernel: [<c04379e3>] ? finish_task_switch+0x53/0xbf

Oct 26 17:17:32 localhost kernel: [<c077a829>] do_page_fault+0x282/0x298

Oct 26 17:17:32 localhost kernel: [<c077a5a7>] ? do_page_fault+0x0/0x298

Oct 26 17:17:32 localhost kernel: [<c0778d3b>] error_code+0x73/0x78

Oct 26 17:17:32 localhost kernel: Mem-Info:

Oct 26 17:17:32 localhost kernel: DMA per-cpu:

Oct 26 17:17:32 localhost kernel: CPU    0: hi:    0, btch:   1 usd:   0

Oct 26 17:17:32 localhost kernel: CPU    1: hi:    0, btch:   1 usd:   0

Oct 26 17:17:32 localhost kernel: Normal per-cpu:

Oct 26 17:17:32 localhost kernel: CPU    0: hi:  186, btch:  31 usd: 176

Oct 26 17:17:32 localhost kernel: CPU    1: hi:  186, btch:  31 usd: 184

Oct 26 17:17:32 localhost kernel: Active_anon:96761 active_file:752 inactive_anon:96834

Oct 26 17:17:32 localhost kernel: inactive_file:1384 unevictable:0 dirty:25 writeback:695 unstable:0

Oct 26 17:17:32 localhost kernel: free:1920 slab:5066 mapped:791 pagetables:2649 bounce:0

Oct 26 17:17:32 localhost kernel: DMA free:3328kB min:68kB low:84kB high:100kB active_anon:2296kB inactive_anon:2420kB active_file:204kB inactive_file:724kB unevictable:0kB

present:15804kB pages_scanned:0 all_unreclaimable? no

Oct 26 17:17:32 localhost kernel: lowmem_reserve[]: 0 809 809 809

Oct 26 17:17:32 localhost kernel: Normal free:4352kB min:3604kB low:4504kB high:5404kB active_anon:384748kB inactive_anon:384916kB active_file:2804kB inactive_file:4812kB

unevictable:0kB present:828992kB pages_scanned:2496 all_unreclaimable? no

Oct 26 17:17:32 localhost kernel: lowmem_reserve[]: 0 0 0 0

Oct 26 17:17:32 localhost kernel: DMA: 14*4kB 15*8kB 11*16kB 7*32kB 7*64kB 2*128kB 0*256kB 0*512kB 0*1024kB 1*2048kB 0*4096kB = 3328kB

Oct 26 17:17:32 localhost kernel: Normal: 598*4kB 11*8kB 1*16kB 3*32kB 3*64kB 2*128kB 1*256kB 0*512kB 1*1024kB 0*2048kB 0*4096kB = 4320kB

Oct 26 17:17:32 localhost kernel: 2902 total pagecache pages

Oct 26 17:17:32 localhost kernel: 639 pages in swap cache

Oct 26 17:17:32 localhost kernel: Swap cache stats: add 860507, delete859868, find 46506/63512

Oct 26 17:17:32 localhost kernel: Free swap  = 0kB

Oct 26 17:17:32 localhost kernel: Total swap = 1023992kB

Oct 26 17:17:32 localhost kernel: 212976 pages RAM

Oct 26 17:17:32 localhost kernel: 0 pages HighMem

Oct 26 17:17:32 localhost kernel: 4796 pages reserved

Oct 26 17:17:32 localhost kernel: 13599 pages shared

Oct 26 17:17:32 localhost kernel: 203455 pages non-shared

Oct 26 17:17:32 localhost kernel: Out of memory: kill process 6338 (gdb) score 191002 or a child

Oct 26 17:17:32 localhost kernel: Killed process 6340 (Record)

看到最后两行:

kernel: Out of memory: kill process 6338 (gdb) score 191002 or a child

kernel: Killed process 6340 (Record)

很明显:是系统内存不够,导致程序被kernel杀掉了。

具体内存使用情况,可以看到:
Normal: 598*4kB 11*8kB 1*16kB 3*32kB 3*64kB 2*128kB 1*256kB 0*512kB 1*1024kB 0*2048kB 0*4096kB = 4320kB,可用内存只有4M,
Free swap  = 0kB
Total swap = 1023992kB,交换分区页用光了
确实是没有内存可用了。

多说一句:“Record invoked oom-killer”,这里的oom-killer是“是一层保护机制,用于避免 Linux 在内存不足的时候不至于出太严重的问题,把无关紧要的进程杀掉,有些壮士断腕的意思。”更详细的参见“http://www.dbanotes.net /database/linux_outofmemory_oom_killer.html”。

4.问题确定了,就去查原因。

(1)首先,重现问题。

启动程序,利用系统监视器查看系统内存的使用情况:

image

眼睁睁的看着程序使用的内存不断增长,内存耗尽使用交换分区,最后交换分区也用光,程序直接被系统kill掉了。

(2)查代码的过程不说,最后定位到问题在下面的代码:

//报警录制输入

intCInputTS::AlarmInput()

{

intnRet = InitTsSocket();

if( (nRet < 0) || (m_pPreloadBuff == NULL) || (m_pBuff == NULL))

{

LOG(LOG_TYPE_ERROR, "Channel:[%d-%s]. AlarmInput().nRet = %d, m_pPreloadBuff = 0X%X, m_pBuff = 0X%X. exit", m_pChannel->m_nChannelID, m_pChannel->m_strName.c_str(), nRet, m_pPreloadBuff, m_pBuff);

return-1;       

}

intrecvTime = time(NULL);

charTsData[TS_DATA_LEN] = {0};

boolbStarted = false;

while(!m_nEndFlag)

{

recvTime = time(NULL);

nRet = recvfrom(TsSock, TsData, TS_DATA_LEN, 0, NULL, NULL);

if(nRet > 0)//收到数据

{

//把收到的数据压入预录缓冲区

CPoolNode* node = m_pPreloadBuff->allocNode();

if(node != NULL)

{

node->m_nDataLen = nRet;

node->ntime = recvTime;

memcpy(node->m_pData, TsData, node->m_nDataLen);

m_pPreloadBuff->push(node);

}

else

{

LOG(LOG_TYPE_ERROR, "CHAN[%d-%s], m_pPreloadBuff->allocNode() failed.", m_pChannel->m_nChannelID, m_pChannel->m_strName.c_str());

}

//检查当前报警状态

if(bStarted)//正在进行报警录制

{

bStarted = m_pRecordModule->DetermineEndOutput(0, recvTime);

}

else

{

bStarted = m_pRecordModule->DetermineStartOutput(recvTime);

if(!bStarted)//不需要进行报警录制

{

continue;

}

DumpPreloadBuff();

}

//进行报警录制

//报警开始之后的数据压入m_pBuff(在报警持续时间内,数据同时压入m_pPreloadBuff和m_pBuff中)

CPoolNode* buffnode = m_pBuff->allocNode();

if(buffnode == NULL)

{

LOG_PERIOD(LOG_TYPE_ERROR, "Channel:[%d-%s], AllocNode failed. Lost Data:%d bytes",

m_pChannel->m_nChannelID, m_pChannel->m_strName.c_str(), nRet);

usleep(50);

continue;

}

buffnode->m_nDataLen = nRet;

buffnode->ntime = recvTime;

memcpy(buffnode->m_pData, TsData, buffnode->m_nDataLen);

m_pBuff->push(buffnode);

//检查预录缓冲区

CheckPreloadBuff(recvTime);

}

else

{

LOG_PERIOD(LOG_TYPE_ERROR,"No Input Data! Channel:[%d-%s], nRecvSize = %d, %s\n",

m_pChannel->m_nChannelID, m_pChannel->m_strName.c_str(), nRet, strerror(errno));

}

}

close(TsSock);

return0;

}

 

 1 void CInputTS::CheckPreloadBuff(time_t ltime)
2 {
3 if((m_pPreloadBuff == NULL) || (m_pBuff == NULL) || (m_pRecordModule == NULL))
4 {
5 return;
6 }
7
8 if(!m_pPreloadBuff->empty())
9 {
10 CPoolNode* node = m_pPreloadBuff->front();
11 if((int)(ltime - node->ntime) >= m_nAlarmRecordTimeBefore) //超过预录时间
12 {
13 CPoolNode* pNode = m_pPreloadBuff->pop();
14 m_pPreloadBuff->freeNode(pNode);
15 }
16 }
17 }

  先解释逻辑:

(1)AlarmInput()从socket不断接收数据,首先压入预录缓冲区m_pPreloadBuff 中,这个缓冲区m_pPreloadBuff其实就是一个队列;

(2)AlarmInput()每接收到一个数据包后调用CheckPreloadBuff()检查预录缓冲区m_pPreloadBuff 队头的数据,如果超时就丢掉;

(需求说明:本段代码要求预录缓冲区中只保留指定时长的数据,超时数据就会被丢掉,这样预录缓冲区就会维持在一个稳定的长度)。

但在测试时发现这段代码并没有按照我们预想的方式工作,预录缓冲区m_pPreloadBuff的队列长度不断增长,最后将系统内存耗尽了

罪魁祸首在这里!

分析逻辑:每接收到一个数据包后,将其push到预录缓冲区m_pPreloadBuff 的队列中,然后调用CheckPreloadBuff()检查队头的数据,如果超时就丢掉。

正常情况下,队列中push一个数据,接着就pop一个是不会有问题的;但是在这段逻辑中:

if(!bStarted)//不需要进行报警录制

{

continue;

}

程序push了数据,就直接contine返回,没有执行CheckPreloadBuff(),导致m_pPreloadBuff 中积累多个超时数据;而当下次再调用CheckPreloadBuff()时还是只检查队头的数据。这样的话,我们很可能push了10个数据,确只检查了3个队头数据,因此预录缓冲区m_pPreloadBuff就会不断增长。

问题定位就好解决了:每次执行CheckPreloadBuff()时不只检查队头的数据,而是检查所有数据,直到遇到没有超时的数据。

这样每次检查时就把所有超时数据一次清空了,只要CheckPreloadBuff()有机会执行,就不会让缓冲区无限增长。

修改如下:

 1 int CInputTS::AlarmInput()
2 {
3 ...
4 if(nRet > 0)//收到数据
5 {
6 //把收到的数据压入预录缓冲区
7 CPoolNode* node = m_pPreloadBuff->allocNode();
8 if(node != NULL)
9 {
10 node->m_nDataLen = nRet;
11 node->ntime = recvTime;
12 memcpy(node->m_pData, TsData, node->m_nDataLen);
13 m_pPreloadBuff->push(node);
14 }
15 else
16 {
17 LOG(LOG_TYPE_ERROR, "CHAN[%d-%s], m_pPreloadBuff->allocNode() failed.", m_pChannel->m_nChannelID, m_pChannel->m_strName.c_str());
18 }
19
20 //检查预录缓冲区
21 CheckPreloadBuff(recvTime);
22 ...
23 }
 1 void CInputTS::CheckPreloadBuff(time_t ltime)
2 {
3 if((m_pPreloadBuff == NULL) || (m_pBuff == NULL) || (m_pRecordModule == NULL))
4 {
5 return;
6 }
7
8 /*
9 * NOTICE:在每接收到一个数据包后,就调用CheckPreloadBuff检查预录缓冲区,一次性将所有超时的预录数据全部清除
10 * 必须清除所有超时预录数据,而不能只检查队头的数据;否则,一旦input和output速率不匹配,将导致预录缓冲区快速增长
11 * 大量超时数据无法清除。最后程序使用的内存不断增长,耗尽了系统所有的可用内存和交换分区,最后导致OS的oom-killer保护机制
12 * 启动init进程向录制程序发送SIGKILL信号,最后录制程序退出。
13 */
14 //检查预录缓冲区中的最旧数据
15 while(!m_pPreloadBuff->empty())
16 {
17 CPoolNode* node = m_pPreloadBuff->front();//清除所有超时数据,确保缓冲区中只保留指定时长的数据
18 if((int)(ltime - node->ntime) >= m_nAlarmRecordTimeBefore) //超过预录时间
19 {
20 CPoolNode* pNode = m_pPreloadBuff->pop();
21 m_pPreloadBuff->freeNode(pNode);
22 }
23 else//后续不再处理
24 {
25 break;
26 }
27 }

免责声明:文章转载自《内存不足引起的SIGKILL:一个缓冲区不断增长问题的定位与解决(解释SIGKILL原因)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇你知道什么是Grunt么?【转载】eclipse调试arm裸机程序下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

js如何读写txt文件?(曲线救国篇)

。emmm,不存在的。做不到的。 但是,你可以继续往下阅读,或许能实现你想要的功能。 前言:一般我们需要用js来实现文件读写,都是一些比较小的,离线的应用(因为如果可以联网,什么json什么db都任意操作再也没那么多麻烦事情了)。 但是,就是只能离线,还就想要把js运行过程中生产的数据保存到文本文件中,要怎么办呢? js是不能直接读写文件的。我先下面要介绍...

解决mySQL占用内存超大问题

为了装mysql环境测试,装上后发现启动后mysql占用了很大的虚拟内存,达8百多兆。网上搜索了一下,得到高人指点my.ini。再也没见再详细的了..只好打开my.ini逐行的啃,虽然英文差了点,不过多少M还是看得明的^-^ 更改后如下: innodb_buffer_pool_size=576M ->256M InnoDB引擎缓冲区占了大头,首要就是...

[日志log] 常用log日志记录方式对比和详解

1、现在都有哪些记录日志的方法 A.java.util.logging.Logger - 使用详解 B.log4j - 使用详解 C.SLF4J(simple logging Facade for Java)-使用详解 SLF4J是一个通用的日志框架,它并不是一种具体的日志系统,而是一个用户日志系统的facade,允许用户在部署最终应用时方便的变更其日...

zabbix利用percona-toolkit工具监控Mysql主从同步状态

一、下载percona-toolkit工具包 percona-toolkit是一组高级命令行工具的集合,可以查看当前服务的摘要信息,磁盘检测,分析慢查询日志,查找重复索引,实现表同步等等。 [root@push-5-221 src]# cd /usr/local/src/ [root@push-5-221 src]# wget https://www.pe...

Linux 之 rsyslog

Linux 之 rsyslog 系统日志转发一、rsyslog 介绍     ryslog 是一个快速处理系统日志的程序,提供了高性能、安全功能和模块化设计。rsyslog 是syslog 的升级版,它将多种来源输入输出转换结果到目的地,据官网介绍,现在可以处理100万条信息。     特性:1.多线程        2.支持加密协议:ssl,tls,re...

动态配置log4j2.xml日志输出文件的位置

目标:根据启动jar时传进main()的参数动态修改日志位置 一、修改启动项  MainMapLookup.setMainArguments(args);注:不要在lookup设置之前初始化log(如:private static final Logger log = LoggerFactory.getLogger(HttpServer.class);)...