注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4
1、关于虚拟内存的overcommit
我们都知道,进程使用malloc等函数申请内存时只是申请到一个虚拟地址,并没有分配实际的物理地址。只有当进程真的去访问获取到的虚拟地址时,产生page_fault,才会分配实际物理地址。从实际情况出发,很多时候系统申请一段内存后,并不会完全使用完,因此为了能供更多的进程运行,系统就可以overcommit,也就是说所有进程分配的内存之和大于实际物理内存。比如,A进程分配了100M内存,但是只使用了50M,那剩下的50M系统就可以先不分配,从而给其他进程腾出空间。但是肯定不能无限制overcommit,否则总有东窗事发的那一刻。因此就需要有一定的规则来限制vm的overcommit。
目前内核支持三种overcommit规则,
#define OVERCOMMIT_GUESS 0 //以一定的规则限制overcommit
#define OVERCOMMIT_ALWAYS 1 //不限制,随意overcommit
#define OVERCOMMIT_NEVER 2 //不允许overcommit
2、涉及参数
/proc/sys/vm/下
- admin_reserve_kbytes:root用户保留的内存数目,用于系统紧急恢复,默认值min(3% free mem, 8M)
- user_reserve_kbytes:普通用户保留的内存数目,用于用户自身紧急恢复,默认值min(3% free mem, 128M),该值仅在不允许overcommit时才有效
- overcommit_memory:控制overcommit策略,默认0
- overcommit_kbytes:当不允许overcommit时,设置vm允许申请值的上限,
- overcommit_ratio:当不允许overcommit时,设置vm允许申请的百分比,默认50%
3、具体作用
-
首先是admin_reserve_kbytes,内核里涉及该参数的地方有三个函数,一个是__vm_enough_memory(),在内存分配路径上,这是我们今天关注的;另一个是reserve_mem_notifier(),在拔插内存路径上;还有一个是init_admin_reserve(),也就是参数初始化,后面这两种我们暂不分析。
-
user_reserve_kbytes和admin_reserve_kbytes一样,在__vm_enough_memory()和reserve_mem_notifier()函数中,以及初始化函数init_user_reserve()。
-
overcommit_memory参数涉及newseg()函数,在分配新共享内存块路径上,以及do_mmap_pgoff函数,在mmap映射路径上,同样也涉及__vm_enough_memory()函数。
-
overcommit_kbytes参数涉及overcommit_ratio_handler()参数设置函数,以及__vm_enough_memory() -> vm_commit_limit()路径
-
overcommit_ratio参数涉及overcommit_kbytes_handler()参数设置函数,以及 __vm_enough_memory() -> vm_commit_limit()路径。
从代码出发,因此我们着重分析__vm_enough_memory函数。
#define OVERCOMMIT_GUESS 0
#define OVERCOMMIT_ALWAYS 1
#define OVERCOMMIT_NEVER 2
/*
* Check that a process has enough memory to allocate a new virtual
* mapping. 0 means there is enough memory for the allocation to
* succeed and -ENOMEM implies there is not.
*
* cap_sys_admin is 1 if the process has admin privileges, 0 otherwise.
*/
int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin)
{
long free, allowed, reserve;
//增加vm_committed_as计数,这个全局变量统计系统当前vm申请量
//这个值也就是/proc/meminfo里Committed_AS的值
//因为最开始就增加了,因此本次申请数量也包含了
vm_acct_memory(pages);
//完全不限制虚拟内存的分配,随意overcommit,因此总是能成功
if (sysctl_overcommit_memory == OVERCOMMIT_ALWAYS)
return 0;
//根据一定规则限制vm的overcommit,这也是系统默认行为
//这时就要计算下当前系统free的内存了
if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) {
//1、NR_FREE_PAGES是系统完全free的内存,也就是free命令查到的free项
free = global_page_state(NR_FREE_PAGES);
//2、NR_FILE_PAGES是page cache使用的页面,这些页面是可以释放的,
//因此也要计入free中,但是要扣除共享内存
free += global_page_state(NR_FILE_PAGES);
//3、NR_SHMEM是共享内存,这些不能计入free中
free -= global_page_state(NR_SHMEM);
//4、获取swap的free页数
free += get_nr_swap_pages();
//5、slab里可回收的肯定是要记入free中啦
free += global_page_state(NR_SLAB_RECLAIMABLE);
//6、考虑系统运行的基本需求,也要占用一部分内存,因此free肯定不能小于该值
if (free <= totalreserve_pages)
goto error;
else
free -= totalreserve_pages;
//7、根据admin_reserve_kbytes的设置
//留一部分内存给root用户保证紧急情况下能登录系统,并恢复系统
//比如需要启动sshd/login, bash, and top/kill
if (!cap_sys_admin)
free -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10);
//到最后了,free大于要分配的内存,这就是真能分配了
if (free > pages)
return 0;
goto error;
}
//这里就是完全不允许overcommit的情况了
//allowed用于统计系统vm上限,这个是就是/proc/meminfo里CommitLimit的值
//计算公式:CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap
allowed = vm_commit_limit();
//同上,留一部分内存给root用户
if (!cap_sys_admin)
allowed -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10);
//保证单进程不要使用完所有vm空间,至少保证自己能恢复
//和admin_reserve_kbytes类似,也要给自己留点退路,不然只能让root用户来恢复系统了
if (mm) {
//给普通用户保留的空间为min(当前进程vm的32分之一,将近3%,user_reserve_kbytes)
reserve = sysctl_user_reserve_kbytes >> (PAGE_SHIFT - 10);
allowed -= min_t(long, mm->total_vm / 32, reserve);
}
//vm_committed_as保存当前系统中已申请(包含本次)的vm数量
//如果已分配数量小于系统允许分配上限,那就是此次内存申请ok
if (percpu_counter_read_positive(&vm_committed_as) < allowed)
return 0;
error:
vm_unacct_memory(pages);
//内存不足
return -ENOMEM;
}
/*
* Committed memory limit enforced when OVERCOMMIT_NEVER policy is used
*/
unsigned long vm_commit_limit(void)
{
unsigned long allowed;
//如果设置了overcommit_kbytes参数,那么commit就不能超过该值
if (sysctl_overcommit_kbytes)
allowed = sysctl_overcommit_kbytes >> (PAGE_SHIFT - 10);
else
//如果没设置overcommit_kbytes参数,将读取overcommit_ratio参数的值
//既然是百分比,那么就需要有基数(总内存页面减去大页使用的内存)
allowed = ((totalram_pages - hugetlb_total_pages())
* sysctl_overcommit_ratio / 100);
//同样别忘了还有swap页面数量
allowed += total_swap_pages;
return allowed;
}
因此,这几个参数主要控制的是vm的申请,不同的申请策略,上限也不一样,总的来说
- overcommit_memory为0时,受系统内存以及admin_reserve_kbytes限制
- overcommit_memory为1时,无任何限制
- overcommit_memory为2时,受系统内存、admin_reserve_kbytes、overcommit_kbytes、overcommit_ratio以及user_reserve_kbytes限制
参考链接:
1、https://lkml.org/lkml/2013/3/18/812