统计组的同事发现的一个问题, 一个统计用脚本在运行到中途的就被系统自动kill了, 搜了下, 可能跟linux本身的oom killer机制有关系:
第一次注意到 Linux 这个多年来就存在的特性:OOM Killer 。说白了 OOM Killer 就是一层保护机制,用于避免 Linux 在内存不足的时候不至于出太严重的问题,把无关紧要的进程杀掉,有些壮士断腕的意思。
先要学习点老知识,在 32 位CPU 架构下寻址是有限制的。Linux 内核定义了三个区域:
# DMA: 0x00000000 - 0x00999999 (0 - 16 MB)
# LowMem: 0x01000000 - 0x037999999 (16 - 896 MB) - size: 880MB
# HighMem: 0x038000000 - <硬件特定>
LowMem 区 (也叫 NORMAL ZONE ) 一共 880 MB,而且不能改变(除非用 hugemem 内核)。对于高负载的系统,就可能因为 LowMem 利用不好而引发 OOM Killer 。一个可能原因是 LowFree 太少了,另外一个原因是 LowMem 里都是碎片,请求不到连续的内存区域【根据我遇到的一个案例,一个猜想是有些应用一次性请求比较大的内存,恰恰又是 880M 之内的,空闲的(LowFree)不够大,就会触发 OOM Killer 出来干活】。检查当前 LowFree 的值:
# cat /proc/meminfo |grep LowFree
检查LowMem内存碎片:
# cat /proc/buddyinfo
上面这条命令要在 2.6 Kernel 环境下有效。据说使用 SysRq 的方式更好,不过 Hang 的时候再用吧。参见 Metalink Note:228203.1 。
根据一些文档描述,OOM Killer 在 2.4 与 2.6 上表现是不一样的。2.4 的版本中是把新进来(新申请内存)的进程杀掉。而 2.6 上是杀掉占用内存最厉害的进程(这是很危险的,很容易导致系统应用瘫痪)。
对于 RHEL 4 ,新增了一个参数: vm.lower_zone_protection 。这个参数默认的单位为 MB,默认 0 的时候,LowMem 为 16MB。建议设置 vm.lower_zone_protection = 200 甚至更大以避免 LowMem 区域的碎片,是绝对能解决这个问题的(这参数就是解决这个问题出来的)。
而对于 RHEL 3 (Kernel 2.4) 似乎没什么好办法,一个是用 Hugemem 内核(天知道会不会引入新的毛病),一个是升级到 2.4.21-47 并且使用新的核心参数 vm.vm-defragment 控制碎片的数量。再就是使用 RHEL 4 (Kernel 2.6),这又绕回去了。说白了,如果遇到 OOM Killer ,基本上是低版本 Kernel 设计上有点缺陷。
其它,如果去查询 RedHat 的 Bug 库,会发现不少 Kernel 版本也有 Bug 的。尤其在使用 NFS 的场景。
Tip: OOM Killer 的关闭与激活方式:
# echo "0" > /proc/sys/vm/oom-kill
# echo "1" > /proc/sys/vm/oom-kill
更多参考信息:
* 1) OOM killer "Out of Memory: Killed process" SOLUTIONS / SUMMARY【对我遇到的案例没鸟用】
* 2) Metalink Notes : Linux Kernel Lowmem Pressure Issues and Kernel Structures
* 3) Respite from the OOM killer
--EOF--
apache 终于停止服务了,系统为2.6内核、64位操作系统、2G内存。
/var/log/messages 有这样的错误提示
Mar 8 20:18:24 2hei-net kernel: oom-killer: gfp_mask=0x1d2
Mar 8 20:18:24 2hei-net kernel: Mem-info:
Mar 8 20:18:24 2hei-net kernel: Node 0 DMA per-cpu:
Mar 8 20:18:24 2hei-net kernel: cpu 0 hot: low 2, high 6, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 0 cold: low 0, high 2, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 1 hot: low 2, high 6, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 1 cold: low 0, high 2, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 2 hot: low 2, high 6, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 2 cold: low 0, high 2, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 3 hot: low 2, high 6, batch 1
Mar 8 20:18:24 2hei-net kernel: cpu 3 cold: low 0, high 2, batch 1
Mar 8 20:18:24 2hei-net kernel: Node 0 Normal per-cpu:
Mar 8 20:18:26 2hei-net kernel: cpu 0 hot: low 32, high 96, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 0 cold: low 0, high 32, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 1 hot: low 32, high 96, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 1 cold: low 0, high 32, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 2 hot: low 32, high 96, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 2 cold: low 0, high 32, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 3 hot: low 32, high 96, batch 16
Mar 8 20:18:26 2hei-net kernel: cpu 3 cold: low 0, high 32, batch 16
Mar 8 20:18:26 2hei-net kernel: Node 0 HighMem per-cpu: empty
Mar 8 20:18:26 2hei-net kernel:
Mar 8 20:18:26 2hei-net kernel: Free pages: 17536kB (0kB HighMem)
Mar 8 20:18:26 2hei-net kernel: Active:257583 inactive:239023 dirty:0 writeback:0 unstable:0 free:4384 slab:3787 mapped:497010 pagetables:2846
Mar 8 20:18:26 2hei-net kernel: Node 0 DMA free:11832kB min:44kB low:88kB high:132kB active:0kB inactive:0kB present:16384kB pages_scanned:0 all_unreclaimable? yes
Mar 8 20:18:26 2hei-net kernel: protections[]: 0 0 0
Mar 8 20:18:26 2hei-net kernel: Node 0 Normal free:5704kB min:5720kB low:11440kB high:17160kB active:1029692kB inactive:956732kB present:2080416kB pages_scanned:3188856 all_unreclaimable? yes
Mar 8 20:18:26 2hei-net kernel: protections[]: 0 0 0
Mar 8 20:18:26 2hei-net kernel: Node 0 HighMem free:0kB min:128kB low:256kB high:384kB active:0kB inactive:0kB present:0kB pages_scanned:0 all_unreclaimable? no
Mar 8 20:18:26 2hei-net kernel: protections[]: 0 0 0
Mar 8 20:18:26 2hei-net kernel: Node 0 DMA: 6*4kB 4*8kB 2*16kB 3*32kB 2*64kB 2*128kB 2*256kB 1*512kB 0*1024kB 1*2048kB 2*4096kB = 11832kB
Mar 8 20:18:26 2hei-net kernel: Node 0 Normal: 0*4kB 1*8kB 4*16kB 0*32kB 0*64kB 0*128kB 0*256kB 1*512kB 1*1024kB 0*2048kB 1*4096kB = 5704kB
Mar 8 20:18:26 2hei-net kernel: Node 0 HighMem: empty
Mar 8 20:18:26 2hei-net kernel: Swap cache: add 1070476, delete 1070476, find 119872/179715, race 0+20
Mar 8 20:18:26 2hei-net kernel: Free swap: 0kB
Mar 8 20:18:26 2hei-net kernel: 524200 pages of RAM
Mar 8 20:18:27 2hei-net kernel: 10214 reserved pages
Mar 8 20:18:27 2hei-net kernel: 67015 pages shared
Mar 8 20:18:27 2hei-net kernel: 0 pages swap cached
Mar 8 20:18:27 2hei-net kernel: Out of Memory: Killed process 26079 (httpd).
终于发现了linux的OOM Killer(Out of Memory: Killed process)这个功能。当linux发现有进程占用内存过多时会触发OOM Killer功能,将占用内存最多的pid给杀掉,通过网上的一些惨痛的教训可以看到有mysql、oracle、apache给OOM kill掉的。
因为我的机器上只跑了apache服务,所以又看了一下我的apache配置
StartServers 2
MaxClients 2048
MinSpareThreads 25
MaxSpareThreads 75
ThreadsPerChild 128
MaxRequests
本以为想让apache支持的连接数多一点,没想到MaxClients参数的设置影响了系统的稳定。
我看到以这种配置启动时VIRT RES的值就已经显示为1702,RES从11m开始逐步升高。
PID USER PR NI %CPU TIME+ %MEM VIRT RES SHR S COMMAND
7009 apache 16 0 1 0:06.95 0.8 1702m 11m 2172 S /home/local/apache/bin/httpd
跑了几天以后VIRT已经到了2G多,RES也接近1G,终于今天挂掉了,httpd进程被linux给kill掉了。
于是尝试修改apache的httd.conf配置,发现MaxClients为1536时,启动VIRT可以达到1024m,如果设定为1024时 VIRT为776m,所以对于2G内存的机器不要超过1536为好。
参考文档:
摘自:
015 mm/oom_kill.c
2006-5-26
mm/oom_kill.c
*
* 忙,并且忙了很久,占有的少,和权利大的靠边,并毫不谦让,直接出手的有更
* 多生还机会
*
超级纯粹的一个模块,实现out of memory killer.当内存严重不足的时候选择
一个"弱者",同过强制信号kill掉,释放出内存。
提供了两个借口:
void oom_kill(void)
int out_of_memory(void)
并且只有一个地方使用,vmscan.c:
int kswapd(void *unused)
{
........
else if (out_of_memory()) {
oom_kill();
}
}
分析filemap.c的时候,了解过kswapd的作用,罗列一下:
*****kswapd (mm/vmscan.c)
+---->do_try_to_free_pages (如果内存已经不够用)
+-->page_launder
| +-->扫描
| +-->对dirty页启动回写(包括mapping和buffer cache)
+-->refill_inactive
+-->refill_inactive_scan
+-->扫描,选择合适页面移入
+-->swap_out,对进程启动页面换出
+-->try_to_swap_out将选中页面放入
+----->refill_inactive_scan
先来看看什么叫做内存严重不足:
int out_of_memory(void)
{
struct sysinfo swp_info;
/* Enough free memory? Not OOM. */
if (nr_free_pages() > freepages.min)//每个node,每个zone的buddy系统页面余量不足
return 0;
if (nr_free_pages() + nr_inactive_clean_pages() > freepages.low)//即使算上可以立即
return 0; //回收的页面,也少的可怜
/* Enough swap space left? Not OOM. */
si_swapinfo(&swp_info);
if (swp_info.freeswap > 0)//连磁盘上的空间都不够用了
return 0;
/* Else... */
return 1; //请找个替死鬼吧
}
然后就是选择哪个进程的问题了:
void oom_kill(void)
{
//选择一个进程
struct task_struct *p = select_bad_process();
...
//授予选中的进程很高的优先级,让他尽快结束运行
p->counter = 5 * HZ;
p->flags |= PF_MEMALLOC;
/* This process has hardware access, be more careful. */
//强制发送信号,结束其运行
if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO)) {
force_sig(SIGTERM, p);
} else {
force_sig(SIGKILL, p);
}
/*
* Make kswapd go out of the way, so "p" has a good chance of
* killing itself before someone else gets the chance to ask
* for more memory.
*/
//只有kswapd调用此函数,所以,这就是让kswapd让出cpu
//重新调度的时候,被选中的进程可可能尽快结束运行,释放资源
current->policy |= SCHED_YIELD;
schedule();
return;
}
kswapd(oom_kill)选中要kill的进程,发送一个信号给这个进程,然后kswapd让
出cpu,是选中的进程可以得到调度.当选中的进程开始运行的时候,因为有一个信号
处于peding状态,所以系统建立一个运行signal的环境,并开始处理信号.最后被选
中的进程退出运行,释放资源.至于更详细的过程等到分析信号的时候再议.
select_bad_process选择badness值最高的进程,我们来看看什么样的进程最有
可能为系统牺牲:
static int badness(struct task_struct *p)
{
int points, cpu_time, run_time;
if (!p->mm)
return 0;
/*
* The memory size of the process is the basis for the badness.
*/
points = p->mm->total_vm; //占有内存越多,越可能被干掉
/*
* CPU time is in seconds and run time is in minutes. There is no
* particular reason for this other than that it turned out to work
* very well in practice. This is not safe against jiffie wraps
* but we don't care _that_ much...
*/
cpu_time = (p->times.tms_utime + p->times.tms_stime) >> (SHIFT_HZ + 3);
run_time = (jiffies - p->start_time) >> (SHIFT_HZ + 10);
points /= int_sqrt(cpu_time);//占有cpu越多,即越忙,就越有可能生存
points /= int_sqrt(int_sqrt(run_time));//运行时间越久越有生存机会
/*
* Niced processes are most likely less important, so double
* their badness points.
*/
if (p->nice > 0)
points *= 2;//越谦让就死的越快(不要说不公平啊)
/*
* Superuser processes are usually more important, so we make it
* less likely that we kill those.
*/
//管理员的程序需要尽力保留下来
if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_ADMIN) ||
p->uid == 0 || p->euid == 0)
points /= 4;
/*
* We don't want to kill a process with direct hardware access.
* Not only could that mess up the hardware, but usually users
* tend to only have this flag set on applications they think
* of as important.
*/
//那些直接操作硬件的程序可以获取更多生存机会
if (cap_t(p->cap_effective) & CAP_TO_MASK(CAP_SYS_RAWIO))
points /= 4;
#ifdef DEBUG
printk(KERN_DEBUG "OOMkill: task %d (%s) got %d points\n",
p->pid, p->comm, points);
#endif
return points;
}