推荐一首歌: 飞得高的鸟不落在跑不快的牛的背上 小河主唱
想到挪威算个好地方,于是翻看这段code。
代码执行背景: 当进程在不同group之间移动时,更新完进程与新的cgroup之间一些逻辑关系外,要调用不同subsystem的attach,来处理进程的资源属性改变后要做的事情。对于cpuset来说,这个函数是cpuset_attach(). 举个例子,在包含cpuset subsystem 的cgroup root fs文件系统目录内,task A在cgroup-0内.cgroup-0对应的cpuset资源是cpu0~4,现在用户能过vfs要把task A移到cgroup-1,其cpuset的cpumask 是cpu5~8. 这个过程可以分为两 部分:1) 进程在cgroup之间移动时的逻辑变化,比如把进程重新加入/申请一个 cgroup subsystem state数组集合等。 2) cgroup 的资源变化对进程的发生影响。这篇小文说的是cpuset这种属性变化时,进程相应的处理。
(打算把这篇文章写成发散式的,文中涉及并引出其它方面的知识的标注Qx的地方,会后续补充)
这个函数的代码:
file: kernel/cpuset.c
Note:注释直接在代码上,可能看起来不太方便。
static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cont,
struct cgroup *oldcont, struct task_struct *tsk,
bool threadgroup)
1. 参数:分别是 当前cgroup subsystem, 新的cgroup,旧的cgroup,要移动的进程
{
nodemask_t from, to; //cpuset除了进程所在的cpu集,还有memory node,这对于numa体系来讲的。 下面会看到除了进程与cpu的一些绑定,还有memory node的申请以及把task在当前node上的memory牵移到新的node上。
struct mm_struct *mm;
struct cpuset *cs = cgroup_cs(cont); //如何通过cgroup找到对应的cpuset.首先,cgroup是一种资源集合,不同类型的资源在此集合上的属性叫cgruop subsystem state,这个东西是由不同的subsystem create 的。 把这些不同资源的state放在cgroup的资源数组subsys[MAX_SUSSYSTEM_COUNT]里. 而此state正是cpuset这个结构的内嵌变量。 即通过下面的链条,找到了cpuset这种类型的资源在cgroup这个资源集合里相应的与之匹配属性: cgroup-->sussys[cpuset-id]-->cpuset. 也可以想到,通过cgroup里的等级也有cpuset相应的等级关系映射。
struct cpuset *oldcs = cgroup_cs(oldcont); //旧的cpuset
if (cs == &top_cpuset) { //祝贺你,来到top cpuset俱乐部。如果从某cgroup移动到所在mount系统的根目录,表示进程的cpuset资源指向了top cpuset,即享受任何cpu和node.
cpumask_copy(cpus_attach, cpu_possible_mask); //possible cpu 掩码。这个是根据CONFIG_NR_CPUS宏转换而来的。
to = node_possible_map; //将允许的node map
} else { //否则要guarantee online的cpu/node,这个比较有意思,会遍历cpuset直到顶层,避免空的cpu出现。
guarantee_online_cpus(cs, cpus_attach);
guarantee_online_mems(cs, &to);
}
/* do per-task migration stuff possibly for each in the threadgroup */
cpuset_attach_task(tsk, &to, cs); //参考 下面 2步对此函数解析
if (threadgroup) {// 如果是线程组的迁移,要挨个调用上面的attach操作,暂时cgroup里的操作此值是false.
struct task_struct *c;
rcu_read_lock();
list_for_each_entry_rcu(c, &tsk->thread_group, thread_group) { //fork进程时 CLONE-THREAD flag指定时,将fork的线程挂在group-leader的thread-group链上.
cpuset_attach_task(c, &to, cs);
}
rcu_read_unlock();
}
//上面是对cpuset里的cpu改变导致进程的迁移,除了有可以把进程塞进新的cpuset里的cpu 运行队列外,进程再去申请内存的时候要从allowed node去申请。 在numa体系里,当进程在新的cpu上运行时,其旧的mm如果仍然隶属旧的node,访问的代价很大,下面的即是将此进程的旧的mm也迁移到新的memory node上。
/* change mm; only needs to be done once even if threadgroup */
from = oldcs->mems_allowed;
to = cs->mems_allowed;
mm = get_task_mm(tsk);
if (mm) {
mpol_rebind_mm(mm, &to); // 重新绑定mm的vma到新的node -->Q0.
if (is_memory_migrate(cs)) //如果cpuset设置了CS-MEMORY-MIGRATE flag,要进行实际的 // memory pages migration.
cpuset_migrate_mm(mm, &from, &to); //Migrate memory region from one set of nodes to another.
---->Q1
mmput(mm); // decrease the use count and release all resources for such mm--> Q2
}
}
2. cpuset_attach_task()完成task的cpu/node资源配置工作。有两 个函数需要关注.
set_cpus_allowed_ptr(tsk, cpus_attach) 和 cpuset_change_task_nodemask(tsk, to).
set_cpus_allowed_ptr(tsk, cpus_attach)唤醒migration thread将tasks放到目的cpu的运行队列里。 --->Q3.
贴一段migration works的注释;
/*
* This is how migration works:
*
* 1) we queue a struct migration_req structure in the source CPU's
* runqueue and wake up that CPU's migration thread.
* 2) we down() the locked semaphore => thread blocks.
* 3) migration thread wakes up (implicitly it forces the migrated
* thread off the CPU)
* 4) it gets the migration request and checks whether the migrated
* task is still in the wrong runqueue.
* 5) if it's in the wrong runqueue then the migration thread removes
* it and puts it into the right queue.
* 6) migration thread up()s the semaphore.
* 7) we wake up and the migration is done.
*/
cpuset_change_task_nodemask重新绑定task的memory policy.推荐看两 篇memory policy的文章:
mm: memory policy for page cache allocation
mbind(2) - Linux man page
----> Q4
上面只是简单分析了这段代码的逻辑,其中牵涉到一些锁操作的逻辑,对整个cgroup的操作是有影响的。比如cpuset_migrate_mm 持有的cgroup_mutex,保证此期间current cpuset 不会被更改.再比如对task一些lock操作,也会影响其逻辑。
q0 ~ q4的文章,后面再补上。
欢迎拍砖头,越大越喜欢。。。
上文提到的cpuset的一些flags,比如CS_SPREAD_PAGE/SLAB,可以在cgroup里设置,这些属性会影响进程后面申请page。今天偶然看到一段示例 code.
函数 代码:
#ifdef CONFIG_NUMA
struct page *__page_cache_alloc(gfp_t gfp)
{
if (cpuset_do_page_mem_spread()) {
int n = cpuset_mem_spread_node();
return alloc_pages_exact_node(n, gfp, 0);
}
return alloc_pages(gfp, 0);
}
EXPORT_SYMBOL(__page_cache_alloc);
#endif
memory spread 在cpuset.txt文档中的解释:
1.6 What is memory spread ?
---------------------------
There are two boolean flag files per cpuset that control where the
kernel allocates pages for the file system buffers and related in
kernel data structures. They are called 'memory_spread_page' and
'memory_spread_slab'.
If the per-cpuset boolean flag file 'memory_spread_page' is set, then
the kernel will spread the file system buffers (page cache) evenly
over all the nodes that the faulting task is allowed to use, instead
of preferring to put those pages on the node where the task is running.
注:
1. cpuset_do_page_mem_spread()会检测当前进程是否其cpuset资源设置了page-spread,如果是,其申请page可以spread到其它的node.
2. 否则alloc_pages()根据current的memory policy在相就node上申请,不会有spread.
这段code的执行背景是map block buffer时,find_or_create_page()未在address_space的radix 树里检索到相应的page,于是__page_cache_alloc()去申请一个page,并将其加入到lru中。在得到请求的page后,检测是否此 page 有buffers,再决定把buffer插入相应位置或者初始化buffer。。。这部分有点儿复杂,不知道hyl当年怎么看的呀,哎,,佩服~
阅读(1469) | 评论(0) | 转发(0) |