Linux OOM killer机制介绍

1. 概念描述

Linux内核内存管理使用OOM killer(Out-Of-Memory killer)机制,在系统内存不足时,选择性杀死一些进程以释放内存,以使系统继续运行。

2. OOM killer产生的原因

2.1 malloc 内存分配

By default, Linux follows an optimistic memory allocation strategy.
This means that when malloc() returns non-NULL there is no guarantee that the memory really is available. This is a really bad bug. In case it turns out that the system is out of memory, one or more processes will be killed by the infamous OOM killer.

如上为malloc的manpage节选,说明malloc返回非空指针并不代表指向内存就是可用的,而当系统没有内存时,会通过杀死进程来腾出内存。

2.2 Memory Overcommit

此机制是指操作系统承诺给进程的内存大小超过实际可用物理内存。按照常规理解,内存分配有多少就分配多少,再申请就返回失败。但实际上这种策略是有些浪费内存,因为进程实际使用到的内存往往比申请的内存少。
按照Linux的算法,物理内存页的分配发生在使用瞬间,而不是在申请瞬间。overcommit针对 的也是内存申请,而不是内存分配。
Linux是允许memory overcommit的,即当申请的内存超过分配物理内存一定程度内,仍然可以申请成功。当实际使用时超过可分配物理内存时,利用OOM机制挑选一个进程出来杀死,以释放部分内存,如若不够则继续杀死进程,也可以配置内核参数panic_on_oom进行自动重启系统。

3. 虚拟内存分配

3.1 影响参数

3.1.1 overcommit_memory

用以控制内核的overcommit策略开关。

可选值含义
OVERCOMMIT_GUESS(0)视情况允许overcommit。系统在为应用进程分配虚拟地址空间时,会判断当前申请的虚拟地址空间大小是否超过剩余内存大小,如果超过,则虚拟地址空间分配失败。
OVERCOMMIT_ALWAYS(1)总是允许overcommit。系统在为应用进程分配虚拟地址空间时,完全不进行限制,避免了fork可能产生的失败,但由于malloc是先分配虚拟地址空间,而后通过异常陷入内核分配真正的物理内存,在内存不足的情况下,完全屏蔽了应用进程对系统内存状态的感知,即malloc总是能成功,但内存不足时会引起系统OOM杀进程。
OVERCOMMIT_NEVER(2)禁止overcommit,根据系统内存状态确定虚拟地址空间上限。然而很多情况下,进程的虚拟地址空间占用远大于其实际占用的物理内存,这样一旦内存使用量上去以后,对于一些动态产生的进程(需要复制父进程地址空间)则很容易创建失败,如果业务过程没有过多的这种动态申请内存或者创建子进程,则影响不大,否则会产生比较大的影响

3.1.2 overcommit_ratio

用以确定系统可申请内存大小,仅在禁止overcommit时生效。
可申请内存 = (总的物理内存 – huge页内存)* overcommit_ratio/100 + 交换分区大小。

3.1.3 admin_reserve_kbytes

系统为拥有cap_sys_admin权限用户预留的空闲内存大小。默认为3%的free pages与8MB中较小的值,以便管理员登录和杀死进程。

3.1.4 user_reserve_kbytes

系统为普通用户预留的空闲内存大小,仅在禁止overcommit时生效,默认为3%的free pages与128MB中的较小值,以便用户登录和杀死进程。

3.1.5 CommitLimit 和 Commited_AS

/proc/meminfo信息,禁止禁止overcommit时生效。

参数名称含义
CommitLimit可申请内存 =(总的物理内存 – huge页内存)* overcommit_ratio /100 + 交换分区大小。
Commited_AS当前已申请内存。

3.2 内存充足判断

用户空间分配内存时,内核都会调用__vm_enough_memory ()(mm/mmap.c) 来验证虚拟内存是否足够进行分配。

  1. 总是允许overcommit,直接返回0,表示内存充足。
  2. 视情况判断overcommit
    空闲内存free page计算,如下。
+=global_page_state(NR_FREE_PAGES)
位于Buddy system的free list中,没有任何开销可以使用;

+=global_page_state(NR_FILE_PAGES)
page cache,为加快用户空间读写文件性能使用,可以直接操作磁盘,可视为free。

-=global_page_state(NR_SHMEM)
用于进程间share memory机制,不能清除free,因此要减去。

+= get_nr_swap_pages()
swap file或swap device上的空闲page frame,由于其可作为anonymous page做腾挪,即把使用中的page frame swap out到swap page上,也算作free,开销较大。

+= global_page_state(NR_SLAB_RECLAIMABLE)
slab已标记可回收的page frame。

-= totalreserve_pages
系统运行预留的page,若小于等于此直接返回内存不足,否则减去预留。

-= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10)
对于普通进程,保留一定的free page,以保证管理员可以登录并执行恢复操作。

free > pages,若空闲内存大于申请内存,返回内存充足,否则返回内存不足。
  1. 禁止overcommit
    可申请内存allowed阈值计算,如下。
+=(totalram_pages - hugetlb_total_pages()) * sysctl_overcommit_ratio / 100。

-= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10)
对于普通用户进程,保留一定的free page,以保证管理员可以登录并执行恢复操作。

-= min(mm->total_vm / 32, sysctl_user_reserve_kbytes >> (PAGE_SHIFT - 10)
用户态进程,需预留内存保证登录并执行恢复操作。

percpu_counter_read_positive(&vm_committed_as) < allowed
已申请内存小于允许内存,返回内存充足,否则返回内存不足。

4. 物理内存分配

4.1 影响参数

4.1.1 min_free_kbytes

  1. 代表系统所保留空闲内存的最低限。
    min_free_kbytes = sqrt(lowmem_kbytes * 16) = 4 * sqrt(lowmem_kbytes)
    最小为128K,最大为64M ,lowmem_kbytes可认为是系统内存大小。
  2. 用于计算内存回收水印(zone_watermark)。
  1. zone watermark计算方法(每个zone各有一套watermark参数)
watermark[min] = per_zone_min_free_pages (min_free_kbytes换算为page单位)
watermark[low] = watermark[min] * 5 / 4
watermark[high] = watermark[min] * 3 / 2
不同内存水印跨度:per_zone_min_free_pages * 1/4,即跟内存大小成开方关系。
  1. zone_watermark作用
    在系统空闲内存低于low时,开始启动内核线程kswapd进行内存回收,直到该zone的空闲内存数量达到high后停止回收。
    如果上层申请内存的速度太快,导致空闲内存降至min后,内核就会进行direct reclaim(直接回收),即直接在应用程序的进程上下文中进行回收,再用回收上来的空闲页满足内存申请,因此实际会阻塞应用程序,带来一定的响应延迟,而且可能会触发系统OOM。
    min以下的内存属于系统自留内存,用以满足特殊使用,不会给用户态的普通申请来用。
  1. min_free_kbytes大小的影响
    min_free_kbytes越大,watermark越高,同时三个线之间的buffer量也会相应增加。这意味着会较早的启动kswapd进行回收,且会回收上来较多的内存(直至watermark[high]才会停止),导致系统预留过多空闲内存,一定程度上降低了应用程序可使用的内存。极端情况下设置为接近内存大小时,留给应用程序的内存就会太少而可能频繁导致OOM的发生。
    min_free_kbytes过小,则会导致系统预留内存过小。kswapd回收的过程中也会有少量的内存分配行为(会设上PF_MEMALLOC)标志,这个标志会允许kswapd使用预留内存;另外一种情况是被OOM选中杀死的进程在退出过程中,如果需要申请内存也可以使用预留部分。这两种情况下使用预留内存可以避免系统进入deadlock状态。

4.1.2 lowmem_reserve_ratio

  1. 各个zone之间进行的内存预留
    防止高端zone在没内存的情况下过度使用低端zone的内存资源。低端内存比较小,且有一定的特殊作用比如发生DMA时只能分配DMA zone的低端内存,因此需要在尽量可以使用高端内存时 而不使用低端内存,同时防止高端内存分配不足的时候抢占稀有的低端内存。此参数可以调整内核对于lower zone的保护力度。
  2. 计算及使用方法
    lowmem_reserve_ratio是一个数组,可以通过以下命令查看。
cat /proc/sys/vm/lowmem_reserve_ratio
256     32

数组的长度=内存zone数量 - 1,其中每个数并不是绝对值,而是一个比例,代表1/256或1/32。zone[i] 的 protection[j] 计算规则如下。

(i < j):
zone[i]->protection[j] = (total sums of present_pages from zone[i+1] to zone[j] on the node)/ lowmem_reserve_ratio[i];
(i = j):
  (should not be protected. = 0;
(i > j):
  (not necessary, but looks 0)

计算结果可通过以下命令查看。

cat /proc/zoneinfo
Node 0, zone      DMA
  pages free     3718
        min      335
        low      418
        high     502
……
        protection: (0, 3399, 3399)
……
Node 0, zone   Normal
  pages free     13719
        min      2224
        low      2780
        high     3336
……
        protection: (0, 0, 0)
……

在进行内存分配时,这些预留页数和watermark相加来一起决定现在是满足分配请求,若是认为空闲内存量过低需要启动回收。例如,如果一个normal区的页申请来试图分配DMA区的内存,且现在使用的判断标准是watermark[low]时,内核计算出 page_free = 3718,而watermark + protection[1] = 418 + 3399 = 3817 > page_free,则认为空闲内存太少而不予以分配。如果分配请求本就来自DMA zone,则 protection[0] = 0会被使用,而满足分配申请。

4.2 内存充足判断

在执行伙伴系统算法分配页框以前,需要调用zone_watermark_ok()(mm/page_alloc.c)判断当前内存区中的页框数目是否满足水印要求。若不满足要求,则会分配失败,最终导致OOM。

min = mark; 根据水印设置最小值
if (alloc_flags & ALLOC_HIGH) min -= min / 2; 若需求比较迫切,则放宽限制
if (alloc_flags & ALLOC_HARDER) min -= min / 4; 若需求很迫切,则进一步放宽限制

free_pages = zone_page_state(z, NR_FREE_PAGES); 计算空闲页面数量
free_pages -= (1 << order) - 1;
判断的是分配出去2^order个page之后的free pages是否满足水印要求

free_pages <= min + z->lowmem_reserve[classzone_idx]; 
若free pages已经不大于保留内存和min之和,说明不满足watermark要求

遍历buddy中比请求order小的所有order,检查free pages是否满足watermark要求。
for (o = 0; o < order; o++) {
  每个循环当中,先减去当前order的free pages
  free_pages -= z->free_area[o].nr_free << o;
  
  将min,即判断标准作相应的缩小
  min >>= 1;
  
  比较处理后的free pages和min,看是否满足watermark要求
  if (free_pages <= min)
	return false;
}

简单来看分配完后只要剩余的页数大于水位,就可以安然返回。但是伙伴系统不仅考虑剩余也总数,还要考虑内存碎片情况,上述检查循环其实是为了保证位于高阶和低阶的页大体均衡。尤其是free_pages比较少,正好位于水印附近的时候。

4.3 OOM 发生的影响因素

根据上述说明,可以总结影响OOM发生的因素,请求分配的order大小,请求分配发生的zone,zone的水印大小,内存碎片化程度。

5. OOM killer 调试参数

5.1 panic_on_oom

用以控制内核OOM时,是否触发panic的开关。

可选值含义
0不触发panic。
1如果是因为mempolicy、cpuset、memcg等限制未分配到内存,实际还有内存那么不触发panic,否则触发panic。
2直接触发panic。

5.2 oom_dump_tasks

用以控制内核OOM时,是否调用dump_tasks来打印所有task的内存状况。

可选值含义
0不会调用打印(进程数量很多时,逐一打印可能导致性能问题)。
1在如下情况会执行打印:a.由于OOM导致kernel panic,b.没有找到合适的bad进程,c.找到合适进程杀死时。

5.3 oom_kill_allocating_task

用以控制内核OOM时,是否优先杀死触发OOM的进程。

可选值含义
0选择最“坏”进程杀死。
1若触发OOM进程是用户空间进程、不是unkillable task(如init进程),未设定oom_score_adj阻止kill该进程,则直接杀死此进程。

5.4 oom_score_adj

进程得分相关参数,每个进程独有。

参数名称含义
oom_score进程oom_badness计算得分,为只读参数。
oom_score_adj计算进程得分时的调整值。取值范围是-1000~1000,0表示不调整得分,负值表示在实际打分值上减去一个折扣,正值表示要惩罚该进程增加得分。
oom_adj旧的计算进程得分时的调整值,实际使用时会转换为oom_score_adj。

6. OOM killer 选择算法

在这里插入图片描述

  • 4
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值