vm内核参数之虚拟内存申请overcommit

注:本文分析基于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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章