<kernel v5.0>
Control Group의 초기화는 두 단계에 걸쳐 수행된다.
- 부트업 초반에 cgroup_init_early()를 통해 초기화
- 부트업 후반에 cgroup_init()을 통해 초기화
다음 그림은 cgroup_init_early() 이하 함수와의 호출 관계를 보여준다.
cgroup_init_early()
kernel/cgroup/cgroup.c
/** * cgroup_init_early - cgroup initialization at system boot * * Initialize cgroups at system boot, and initialize any * subsystems that request early init. */
int __init cgroup_init_early(void)
{
static struct cgroup_sb_opts __initdata opts;
struct cgroup_subsys *ss;
int i;
init_cgroup_root(&cgrp_dfl_root, &opts);
cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;
RCU_INIT_POINTER(init_task.cgroups, &init_css_set);
for_each_subsys(ss, i) {
WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,
"invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p id:name=%d:%s\n",
i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,
ss->id, ss->name);
WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,
"cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);
ss->id = i;
ss->name = cgroup_subsys_name[i];
if (!ss->legacy_name)
ss->legacy_name = cgroup_subsys_name[i];
if (ss->early_init)
cgroup_init_subsys(ss, true);
}
return 0;
}
부트업 초반에 cgroup을 early 초기화하고 earlyinit 설정이 된 서브시스템들을 초기화한다. (cgroup의 정규 초기화함수는 커널 부트업 마지막 부분에서 호출되는 cgroup_init()이다.)
- 코드 라인 3에서 __initdata는 __section(.init.data)을 의미한다.
- 코드 라인 7에서 cgroup 루트를 초기화한다.
- 코드 라인 9에서 static하게 초기 설정된 init_css_set RCU 변수의 주소를 init_task.cgroups로 저장한다.
- RCU : Read Copy Update
- 커널 락을 없애려는 시도로 도입된 동기화 기법으로 read 요청이 많은 lock 구현에서 사용된다.
- RCU : Read Copy Update
- 코드 라인 11~26에서 모든 cgroup 서브시스템들을 대상으로 earlyinit이 설정된 서브시스템을 초기화한다.
다음 그림은 3개의 cgroup 서브시스템이 early 초기화된 후의 모습을 보여준다.
init_cgroup_root()
kernel/cgroup/cgroup.c
void init_cgroup_root(struct cgroup_root *root, struct cgroup_sb_opts *opts)
{
struct cgroup *cgrp = &root->cgrp;
INIT_LIST_HEAD(&root->root_list);
atomic_set(&root->nr_cgrps, 1);
cgrp->root = root;
init_cgroup_housekeeping(cgrp);
idr_init(&root->cgroup_idr);
root->flags = opts->flags;
if (opts->release_agent)
strscpy(root->release_agent_path, opts->release_agent, PATH_MAX);
if (opts->name)
strscpy(root->name, opts->name, MAX_CGROUP_ROOT_NAMELEN);
if (opts->cpuset_clone_children)
set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->cgrp.flags);
}
cgroup 루트를 초기화한다.
- 코드 라인 5~7에서 루트 리스트를 초기화하고 cgroup 수를 1로 한다.
- 코드 라인 cgrp과 모든 서브시스템에 있는 e_csets[] 리스트를 깨끗하게 초기화한다.
- 코드 라인 cgroup에 생성 시마다 id를 발급하기 위해 idr 초기화를 한다.
- IDR은 radix tree의 일종으로 정수 ID와 특정한 포인터 값을 연결시키는 역할을 해 준다.
- 원래는 POSIX timer 관련 시스템 콜 구현을 위해 작성된 것으로 특정한 timer 객체를 다룰 수 있는 ID를 생성해 주는 역할을 하였으나 현재는 각종 장치 드라이버나 VFS 레이어에서도 널리 사용된다.
다음 그림은 디폴트 루트 그룹의 초기화된 모습을 간략히 보여준다.
init_cgroup_housekeeping()
kernel/cgroup/cgroup.c
static void init_cgroup_housekeeping(struct cgroup *cgrp)
{
struct cgroup_subsys *ss;
int ssid;
INIT_LIST_HEAD(&cgrp->self.sibling);
INIT_LIST_HEAD(&cgrp->self.children);
INIT_LIST_HEAD(&cgrp->cset_links);
INIT_LIST_HEAD(&cgrp->pidlists);
mutex_init(&cgrp->pidlist_mutex);
cgrp->self.cgroup = cgrp;
cgrp->self.flags |= CSS_ONLINE;
cgrp->dom_cgrp = cgrp;
cgrp->max_descendants = INT_MAX;
cgrp->max_depth = INT_MAX;
INIT_LIST_HEAD(&cgrp->rstat_css_list);
prev_cputime_init(&cgrp->prev_cputime);
for_each_subsys(ss, ssid)
INIT_LIST_HEAD(&cgrp->e_csets[ssid]);
init_waitqueue_head(&cgrp->offline_waitq);
INIT_WORK(&cgrp->release_agent_work, cgroup1_release_agent);
}
요청한 cgroup과 모든 서브시스템에 있는 e_csets[] 리스트를 깨끗하게 초기화한다.
__init cgroup_init_subsys()
kernel/cgroup/cgroup.c
static void __init cgroup_init_subsys(struct cgroup_subsys *ss, bool early)
{
struct cgroup_subsys_state *css;
pr_debug("Initializing cgroup subsys %s\n", ss->name);
mutex_lock(&cgroup_mutex);
idr_init(&ss->css_idr);
INIT_LIST_HEAD(&ss->cfts);
/* Create the root cgroup state for this subsystem */
ss->root = &cgrp_dfl_root;
css = ss->css_alloc(cgroup_css(&cgrp_dfl_root.cgrp, ss));
/* We don't handle early failures gracefully */
BUG_ON(IS_ERR(css));
init_and_link_css(css, ss, &cgrp_dfl_root.cgrp);
/*
* Root csses are never destroyed and we can't initialize
* percpu_ref during early init. Disable refcnting.
*/
css->flags |= CSS_NO_REF;
if (early) {
/* allocation can't be done safely during early init */
css->id = 1;
} else {
css->id = cgroup_idr_alloc(&ss->css_idr, css, 1, 2, GFP_KERNEL);
BUG_ON(css->id < 0);
}
/* Update the init_css_set to contain a subsys
* pointer to this state - since the subsystem is
* newly registered, all tasks and hence the
* init_css_set is in the subsystem's root cgroup. */
init_css_set.subsys[ss->id] = css;
have_fork_callback |= (bool)ss->fork << ss->id;
have_exit_callback |= (bool)ss->exit << ss->id;
have_free_callback |= (bool)ss->free << ss->id;
have_canfork_callback |= (bool)ss->can_fork << ss->id;
/* At system boot, before all subsystems have been
* registered, no tasks have been forked, so we don't
* need to invoke fork callbacks here. */
BUG_ON(!list_empty(&init_task.tasks));
BUG_ON(online_css(css));
mutex_unlock(&cgroup_mutex);
}
요청한 서브시스템을 초기화한다.
- 코드 라인 13~17에서 cgroup 서브시스템의 루트를 생성하고, 초기화한 후 연결을 구성한다.
- 코드 라인 23~31에서 cgroup 서브시스템들의 루트는 절대 소멸하지 않는다. 따라서 참조 카운터 관리가 필요하지 않는다. 가장 처음 부트업 타임에 early하게 생성된 루트들은 id 값으로 1을 할당받고, 그렇지 않은 경우는 cgroup 각 서브시스템의 idr을 통해 id를 할당받는다.
- 코드 라인 37에서 init_css_set에 subsys[] 배열 중 요청한 서브시스템에 해당하는 배열 인덱스 항목에 루트 css가 등록된다.
- css
- struct cgroup_subsys_state
- 서브시스템별/cgroup 마다 생성된다.
- css
- 코드 라인 39~42에서 cgroup에 태스크가 fork/exit/free/canfork 된 후 서브시스템의 관련 후크 함수를 호출할 지 여부를 결정하는 비트마스크이다. 이 곳에 요청한 서브시스템에 해당하는 플래그를 설정해둔다.
- 코드 라인 47에서 cgroup이 early 초기화되는 이 시점에서 init_task 아래에 하위 태스크가 존재하지 않아야 한다.
예) 리눅스 부트와 함께 early 초기화되는 서브시스템들
[ 0.000000] Booting Linux on physical CPU 0x0 [ 0.000000] Initializing cgroup subsys cpuset [ 0.000000] Initializing cgroup subsys cpu [ 0.000000] Initializing cgroup subsys cpuacct
참고
- Control Groups | 문c
- RCU(Read Copy Update) | 문c
- Atomic Operation | 문c
- Mutex for linux kernel space | 문c
- IDR(integer ID 관리) | 문c


