<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