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

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