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目录下。