内核oom过程简单分析

以内核3.10.79为例。这里分析一下内核对于cgroup.memory进程组oom的过程,以及混部环境下需要什么样的oom策略。

触发时机

内核对于每个memory cgroup维护一个计数器,统计当前cgroup内已经使用了的内存。每当cgroup内进程创建页面时,页面大小所占用的内存就会通过res_counter_charge_locked()函数计入计数器里。而当内存占用超过memory.limit_in_bytes所设置的阈值时,charge失败,返回ENOMEN错误。

int res_counter_charge_locked(
struct res_counter *counter, unsigned long val, bool force)
{
        int ret = 0;
        if (counter->usage + val > counter->limit) {
                counter->failcnt++;
                ret = -ENOMEM;
                if (!force)
                        return ret;
        }
        counter->usage += val;
        if (counter->usage > counter->max_usage)
                counter->max_usage = counter->usage;
        return ret;
}

另外有一个问题需要注意的是,内存这个子系统是拓扑型控制的,不是平级控制的。下一级子系统的所有limit_in_bytes之和不能超过父亲的limit_in_bytes值,否则会设置失败。

所以内存计数的时候:

  1. 进程新创建的页面,会被反向递归计入到所有的父cgroup下面。
  2. memory子系统的根的计数一定是当前内核所有进程的内存使用之和。(注意,由于cgroup.memory对内存的统计和proc文件系统的统计方法不一致,所以这两个系统对于内存使用的值并不是完全对等的)

子cgroup也许内存配额有冗余,但父cgroup不一定会有冗余,所以在反向递归计数的时候,谁内存超过阈值了,就oom谁(选择这个cgroup下的某个进程kill掉,所以这里是有可能某个cgroup明明内存没有被超限但也会被莫名的干掉了)。


libprocess 并发编程

libprocess是mesos中非常重要的一个基础库,提供一些很方便的helper函数以及并发编程所需要的基本原语,例如下面我将重点讲的future/promise。

为了更好的解释future/promise是什么,我抽取了一段mesos中的代码作为例子:

Future<Socket> PollSocketImpl::accept()
{
  return io::poll(get(), io::READ)
    .then(lambda::bind(&internal::accept, get()));
}

这个函数的基本作用是:使用io::poll()注册io::READ事件,并且当事件ready的时候,调用internal::accept()。

显然,这是一个异步的accept()方法。