Linux | 内核 | Memory OverCommit

Memory OverCommit介绍

首先,区分一下内存的申请和分配两个概念,申请内存只是内核给应用程序承诺的内存空间,并没有实际分配内存,实际内存页分配发生在内存使用的瞬间,而不是申请的瞬间。比如,调用malloc函数仅仅是向内核申请了固定大小的空间,而只有应用程序实际使用该内存块时,比如调用memset函数做初始化,内核才会真正分配物理内存给应用程序。

很多应用程序(比如数据库)习惯于一次性申请大量的内存空间,而有些内存块可能在程序的整个生命周期都没有被用到,如果最多只允许申请实际可用的空间大小,就会导致内存浪费,明明系统有未使用的内存,而其他程序却无法申请得到。Memory OverCommit功能允许内存申请过度提交,也就是允许应用程序申请超过实际可用大小的内存空间,注意这里的commit也是针对的是内存申请,所以通过malloc函数可以申请远超过实际物理内存的空间。Linux系统是允许Memory OverCommit的。

如果应用程序实际使用(需要分配)的内存空间超过了实际空间大小怎么办呢?Linux设计了一个OOM killer机制(OOM = out-of-memory)来处理这种情况:选择一些进程强行终止来释放一部分内存空间,也可通过设置内核参数 vm.panic_on_oom 使得发生OOM时自动重启系统。

Linux OverCommit

Linux对于是否启用OverCommit有一些相关的内核参数设置:

 vm.overcommit_memory,这个参数如下有3种取值:

0 --Heuristic overcommit handling. 这是缺省值,它允许overcommit。内核会对应用程序的内存申请做检查,当申请的内存超过剩余可分配空间时,应用程序申请内存的操作将会失败,比如调用malloc函数返回空指针,而应用程序将会处理这种错误。如果应用程序只是持续申请内存空间,即使始终不使用(没有实际分配),当申请的空间到达一个很大的值时,程序进程会被OOM Killer强行终止。

1 --允许OverCommit。内核不检查应用程序的内存申请,也就意味着malloc函数的调用不会因为空间不足而失败。但是,malloc即使申请成功并不代表这块内存就真的可用,在实际分配内存时可能会遇到空间不足,此时内核检测到内存被用完,OOM Killer将会选择一些进程强行终止来释放一部分内存空间。

2 --禁用OverCommit。内核根据CommitLimit限制实际可以申请的内存空间。涉及到的配置包括:

vm.overcommit_memory可以直接用echo命令修改:

echo 0 > /proc/sys/vm/overcommit_memory

CommitLimit的计算公式:

CommitLimit = Swap space + Memory space * (Ratio / 100)

Ratio保存在/proc/sys/vm/overcommit_ratio中,1 <= Ratio <= 100,可以直接用echo命令修改:

echo 50 > /proc/sys/vm/overcommit_ratio

比如系统:4GB Mem,4GB Swap;OverCommit = 2; Ratio = 50;则限制申请的内存大小为:

4GB * (50 / 100) + 4GB = 6GB

这个值也就是CommitLimit的大小,可以在/proc/meminfo中查看,它随着Ratio的改变而实时改变。注意:只有当vm.overcommit_memory设置为2时,CommitLimit才会被用到。

Notes:

1. 有些专用系统,比如数据库服务器,为了提升性能不想用到swap,则Ratio的的设置规则为:

Ratio = 100 * ((Memory Space - Swap Space) / Memory Space)

2. 当vm.overcommit_memory设置为2时,如果扩展了物理内存,一定要记得重新计算并修改Ratio的值,否则内存空间可能无法得到充分利用。在References来自pivotal的文章中对此有详细的描述。

Demo

我们用一些小程序来对Linux OverCommit做一些测试:

测试环境

$ cat /etc/redhat-release 
Red Hat Enterprise Linux Server release 7.6 (Maipo)
$ uname -r
3.10.0-957.12.2.el7.x86_64
$ free
              total        used        free      shared  buff/cache   available
Mem:       12040064     2442408     8843480        7876      754176     5951044
Swap:      15359996     4916760    10443236

在我的测试系统中:

  1. 物理内存总共11757MB,Swap空间总共有14999MB。
  2. 物理内存和Swap总共的剩余空间在18GB左右。

在后面的demo中,我不打算做准确的定量计算,只是做定性分析。

Demo1--申请内存后初始化

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main (void) {
    int n = 0;
    char *p;

    while (1) {
        if ((p = malloc(1<<27)) == NULL) {
                printf("malloc failure after %d MB\n", n);
                return 0;
        }
        memset (p, 0, (1<<27));
        n += 128;
        printf ("got %d MB\n", n);
    }
}

在这个程序中,我选择了每次申请128MB内存,并在申请成功后立即初始化。

vm.overcommit_memory = 0

程序终止于:

malloc failure after 16512 MB

注意,这里程序是因为malloc失败而正常退出,说明对内存申请OverCommit的检查生效了。

vm.overcommit_memory = 1

程序终止于:

$ ./test_CommiLimit_2  &
[1] 331755
..
got 17536 MB

[1]+  Killed                  ./test_CommiLimit_2

这里程序退出是因为内存不足,被OOM-killer强制kill,在/var/log/messages中可以看到OOM-killer的工作过程:

Jul 15 19:16:50 n8-h29 kernel: test_CommiLimit invoked oom-killer: gfp_mask=0x280da, order=0, oom_score_adj=0
Jul 15 19:16:51 n8-h29 kernel: test_CommiLimit cpuset=/ mems_allowed=0
Jul 15 19:16:51 n8-h29 kernel: CPU: 5 PID: 331755 Comm: test_CommiLimit Kdump: loaded Tainted: P           OE  ------------   3.10.0-957.12.2.el7.x86_64 #1
Jul 15 19:16:51 n8-h29 kernel: Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 12/12/2018
Jul 15 19:16:51 n8-h29 kernel: Call Trace:
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93763041>] dump_stack+0x19/0x1b
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9375da6a>] dump_header+0x90/0x229
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93101212>] ? ktime_get_ts64+0x52/0xf0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ba7b4>] oom_kill_process+0x254/0x3d0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ba25d>] ? oom_unkillable_task+0xcd/0x120
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ba306>] ? find_lock_task_mm+0x56/0xc0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931baff6>] out_of_memory+0x4b6/0x4f0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9375e56e>] __alloc_pages_slowpath+0x5d6/0x724
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931c13d4>] __alloc_pages_nodemask+0x404/0x420
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93211715>] alloc_pages_vma+0xb5/0x200
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931e9c57>] handle_pte_fault+0x887/0xd10
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff931ec1fd>] handle_mm_fault+0x39d/0x9b0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93770603>] __do_page_fault+0x203/0x4f0
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff93770925>] do_page_fault+0x35/0x90
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9376cab6>] ? error_swapgs+0xa7/0xbd
Jul 15 19:16:51 n8-h29 kernel: [<ffffffff9376c768>] page_fault+0x28/0x30
Jul 15 19:16:51 n8-h29 kernel: Mem-Info:
Jul 15 19:16:51 n8-h29 kernel: active_anon:1756967 inactive_anon:319617 isolated_anon:0#012 active_file:1864 inactive_file:6745 isolated_file:32#012 unevictable:21668 dirty:0 writeback:131 unstable:0#012 slab_reclaimable:11340 slab_unreclaimable:34853#012 mapped:2233 shmem:488 pagetables:18847 bounce:0#012 free:536526 free_pcp:781 free_cma:0
...
Jul 15 19:16:55 n8-h29 kernel: Out of memory: Kill process 331755 (test_CommiLimit) score 637 or sacrifice child
Jul 15 19:16:55 n8-h29 kernel: Killed process 331755 (test_CommiLimit) total-vm:18092700kB, anon-rss:8004720kB, file-rss:392kB, shmem-rss:4kB

vm.overcommit_memory = 2

vm.overcommit_ratio = 50

$ cat /proc/meminfo | grep Commit
CommitLimit:    21380028 kB
Committed_AS:   17967096 kB

CommitLimit = 12040064 * (50 / 100) + 15359996 = 21380028 KB = 20879 MB

Committed_AS代表已经申请的内存空间,大概有17546 MB,所以留给测试程序申请的最大空间为20897 MB - 17546 MB = 3333 MB。这并不代表我们的程序还可以申请这么多空间,实际申请到的往往比3333 MB小,排除测试系统剩余内存空间并不稳定的因素,测试程序本身包括其引用的库也会占用一部分内存。

$ ./test_CommiLimit_2 &
[1] 338176
...
malloc failure after 3200 MB

[1]+  Done                    ./test_CommiLimit_2

Demo2--只申请不使用内存

#include <stdio.h>
#include <stdlib.h>

int main (void) {
    int n = 0;

    while (1) {
        if (malloc(1<<24) == NULL) {
                printf("malloc failure after %d MB\n", n);
                return 0;
        }
        n +=16;
        printf ("got %d MB\n", n);
    }
}

在这个程序中,我们只是循环调用malloc申请内存,每次申请16MB,但是不使用申请的内存。

vm.overcommit_memory = 0

程序终止于:

got 32237728 MB

var/log/messages

Jul 15 19:55:04 n8-h29 kernel: Out of memory: Kill process 343274 (test_CommiLimit) score 576 or sacrifice child
Jul 15 19:55:04 n8-h29 kernel: Killed process 343274 (test_CommiLimit) total-vm:33335085348kB, anon-rss:0kB, file-rss:400kB, shmem-rss:0kB

vm.overcommit_memory = 1

程序终止于:

got 32540832 MB

总之,即便是只申请内存不使用,在申请的空间达到一个很大的值时,也会被OOM-killer杀掉。

References

http://linuxperf.com/?p=102

http://engineering.pivotal.io/post/virtual_memory_settings_in_linux_-_the_problem_with_overcommit/

https://stackoverflow.com/questions/911860/does-malloc-lazily-create-the-backing-pages-for-an-allocation-on-linux-and-othe

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值