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