memory cgroup起作用主要是限制各個進程使用內存大小,當其值超越限制值時會發生oom,講當前進程清理掉。
memcg oom的主要運行流程如下:
首先進程在申請內存時會進行try_charge操作,此時會進行oom檢測,如果是oom,則會把當前的memcg賦值給進程的memcg_in_oom成員,然後在缺頁中斷中會對當前進程是否存在oom進行判斷,如果是,則發送oom events事件,然後清理進程。調用流程如下:
malloc->try_charge()->mem_cgroup_oom()------------------------>進程oom
handle_mm_fault()->mem_cgroup_oom_synchronize()->mem_cgroup_oom_notify()---------------->發送oom events
->mem_cgroup_out_of_memory()----------------->執行殺進程操作
在memory cgroup中有幾個關鍵節點:
memory.memsw.limit_in_bytes//帶上swap空間的最大值
memory.memsw.usage_in_bytes//當前帶上swap的內存佔用
memory.memsw.max_usage_in_bytes//歷史使用的最大內存佔用,包含swap空間佔用
memory.soft_limit_in_bytes//當前內存使用的軟限制
memory.limit_in_bytes//當前內存使用的硬限制
memory.max_usage_in_bytes//當前歷史的最大內存佔用
memory.usage_in_bytes//當前的內存佔用
各個cgroup的內存用量通過page_counter結構來統計,內存申請成功和釋放通過charge和uncharge來完成統計值的更新,在charge過程中會根據page的類型更新percpu類型結構體mem_cgroup_stat_cpu中的count值,如果是anon page,則爲rss類型,如果是file page,則是cache類型,還會更新page in/out個數:
static void mem_cgroup_charge_statistics(struct mem_cgroup *memcg,
struct page *page,
bool compound, int nr_pages)
{
if (PageAnon(page))
__this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_RSS],
nr_pages);
else
__this_cpu_add(memcg->stat->count[MEM_CGROUP_STAT_CACHE],
nr_pages);
………………
if (nr_pages > 0)
__this_cpu_inc(memcg->stat->events[MEM_CGROUP_EVENTS_PGPGIN]);
else {
__this_cpu_inc(memcg->stat->events[MEM_CGROUP_EVENTS_PGPGOUT]);
nr_pages = -nr_pages; /* for event */
}
}
讀取cgroup內存佔用時分爲兩部分,一部分爲root目錄以及其他,root目錄通過統計CPUstat下的rss和cache類page數之和來實現,另外的通過讀取cgroup的page_counter結構體的count值獲取:
static unsigned long mem_cgroup_usage(struct mem_cgroup *memcg, bool swap)
{
unsigned long val = 0;
if (mem_cgroup_is_root(memcg)) {-----------根目錄
struct mem_cgroup *iter;
for_each_mem_cgroup_tree(iter, memcg) {
val += mem_cgroup_read_stat(iter,
MEM_CGROUP_STAT_CACHE);---------------(加上cache值)
val += mem_cgroup_read_stat(iter,
MEM_CGROUP_STAT_RSS);--------------(加上anon頁值)
if (swap)
val += mem_cgroup_read_stat(iter,
MEM_CGROUP_STAT_SWAP);------------(如果需要,加上swap值)
}
} else {---------------非根目錄
if (!swap)
val = page_counter_read(&memcg->memory);
else
val = page_counter_read(&memcg->memsw);
}
return val;
}
當usage超過soft_limit時,failcnt會加1,後面如果因爲cgroup內存佔用超過硬限制觸發oom會根據failcnt值來確定需要清理的進程。
bool page_counter_try_charge(struct page_counter *counter,
unsigned long nr_pages,
struct page_counter **fail)
{
struct page_counter *c;
for (c = counter; c; c = c->parent) {
long new;
new = atomic_long_add_return(nr_pages, &c->count);
if (new > c->limit) {
atomic_long_sub(nr_pages, &c->count);
/*
* This is racy, but we can live with some
* inaccuracy in the failcnt.
*/
c->failcnt++;
*fail = c;
goto failed;
}
/*
* Just like with failcnt, we can live with some
* inaccuracy in the watermark.
*/
if (new > c->watermark)
c->watermark = new;
}
return true;
failed:
for (c = counter; c != *fail; c = c->parent)
page_counter_cancel(c, nr_pages);
return false;
}
注:通過查看Android kernel代碼可以看到當前Android P memory cgroup的內存限制功能並沒有完全enable,只是在lmkd工作過程中計算內存壓力值時獲取了root目錄下面的總的內存佔用和內存+swap的佔用值,當前的memory cgroup不存在父子關係,各層cgroup之間的use_hierarchy的值均爲0。即各個進程的內存佔用總值並沒有通過逐級累加的方式統計到root目錄下。