리눅스 커널의 cgroup v2 설계, 인터페이스 및 규칙 전반을 다루는 공식 문서로, 코어 기능과 각 컨트롤러별 동작을 상세히 설명한다.
URL: https://docs.kernel.org/admin-guide/cgroup-v2.html
Title: Control Group v2 — The Linux Kernel documentation
Date: October, 2015
Author: Tejun Heo tj@kernel.org
이 문서는 cgroup v2의 설계, 인터페이스 및 규칙에 관한 권위 있는 문서이다. cgroup의 코어와 각 컨트롤러별 동작을 포함하여 사용자 공간에서 볼 수 있는 모든 측면을 설명한다. 향후의 모든 변경 사항은 반드시 이 문서에 반영되어야 한다. v1에 대한 문서는 Documentation/admin-guide/cgroup-v1/index.rst에서 볼 수 있다.
“cgroup”은 “control group”의 약자로, 항상 소문자로 표기한다. 단수형은 전체 기능(서브시스템)을 가리킬 때와 “cgroup controllers”와 같은 수식어로 사용할 때 쓰인다. 여러 개의 개별 컨트롤 그룹을 명시적으로 가리킬 때는 복수형 “cgroups”를 사용한다.
cgroup은 프로세스를 계층적으로 조직하고, 계층 구조를 따라 시스템 자원을 통제 가능하고 설정 가능한 방식으로 분배하기 위한 메커니즘이다.
cgroup은 크게 두 부분, 코어와 컨트롤러로 구성된다. cgroup 코어는 주로 프로세스를 계층적으로 조직하는 역할을 한다. cgroup 컨트롤러는 보통 특정 종류의 시스템 자원을 계층 구조를 따라 분배하는 역할을 하지만, 자원 분배 이외의 목적을 가진 유틸리티 컨트롤러도 존재한다.
cgroup들은 트리 구조를 이루며, 시스템의 모든 프로세스는 정확히 하나의 cgroup에 속한다. 하나의 프로세스를 구성하는 모든 스레드는 동일한 cgroup에 속한다. 생성 시, 모든 프로세스는 그 시점의 부모 프로세스가 속한 cgroup에 배치된다. 프로세스는 다른 cgroup으로 마이그레이션될 수 있다. 프로세스의 마이그레이션은 이미 존재하는 하위(자손) 프로세스에는 영향을 미치지 않는다.
특정한 구조적 제약을 따르는 한에서, 컨트롤러는 cgroup 단위로 선택적으로 활성화되거나 비활성화될 수 있다. 모든 컨트롤러의 동작은 계층적이다. 즉, 어떤 cgroup에서 컨트롤러가 활성화되면, 그 cgroup을 포함하는 포괄적 서브 계층(inclusive sub-hierarchy)에 속한 cgroup들의 모든 프로세스에 영향을 미친다. 중첩된 cgroup에서 컨트롤러를 활성화하면 항상 자원 분배를 더 제한하는 방향으로만 동작한다. 계층 구조에서 루트에 더 가까운 곳에서 설정된 제한은 더 멀리 떨어진 곳에서 이를 무시하거나 덮어쓸 수 없다.
v1과 달리, cgroup v2는 단일 계층(hierarchy)만 가진다. cgroup v2 계층은 다음과 같은 mount 명령으로 마운트할 수 있다.
bash# mount -t cgroup2 none $MOUNT_POINT
cgroup2 파일시스템의 매직 넘버는 0x63677270(“cgrp”)이다. v2를 지원하고 v1 계층에 바인딩되어 있지 않은 모든 컨트롤러는 자동으로 v2 계층에 바인딩되어 루트에 나타난다. v2 계층에서 활성 사용 중이 아닌 컨트롤러는 다른 계층에 바인딩할 수 있다. 이를 통해 v2 계층과 기존 v1의 다중 계층을 완전한 하위 호환 방식으로 혼용할 수 있다.
컨트롤러를 다른 계층으로 이동할 수 있는 시점은, 현재 계층에서 더 이상 해당 컨트롤러가 참조되지 않을 때뿐이다. cgroup 단위의 컨트롤러 상태는 비동기적으로 파기되며 컨트롤러에 잔존 참조가 남아 있을 수 있으므로, 이전 계층의 마지막 umount 이후에도 컨트롤러가 v2 계층에 즉시 나타나지 않을 수 있다. 마찬가지로, 통합(unified) 계층에서 컨트롤러를 다른 계층으로 옮기려면 완전히 비활성화되어야 하며, 비활성화된 컨트롤러가 다른 계층에서 사용 가능해지기까지 시간이 걸릴 수 있다. 또한, 컨트롤러 간 의존성 때문에 다른 컨트롤러들도 함께 비활성화해야 할 수도 있다.
개발 및 수동 설정에서는 유용하지만, v2와 다른 계층 사이에 컨트롤러를 동적으로 이동시키는 것은 운영 환경에서 강하게 권장되지 않는다. 시스템 부팅 후 컨트롤러를 사용하기 시작하기 전에, 사용할 계층과 컨트롤러의 연결 관계를 미리 결정해 두는 것이 좋다.
v2로 전환되는 동안 시스템 관리 소프트웨어가 여전히 v1 cgroup 파일시스템을 자동으로 마운트하여, 수동 개입이 가능해지기 전 부팅 시점에 모든 컨트롤러를 가로챌 수 있다. 테스트와 실험을 쉽게 하기 위해, 커널 파라미터 cgroup_no_v1= 을 사용하면 v1에서 특정 컨트롤러를 비활성화하여 해당 컨트롤러가 항상 v2에서만 사용되게 할 수 있다.
cgroup v2는 현재 다음과 같은 마운트 옵션들을 지원한다.
nsdelegate cgroup 네임스페이스를 위임 경계(delegation boundary)로 간주한다. 이 옵션은 시스템 전체에 적용되며, init 네임스페이스에서 마운트 또는 remount를 통해서만 설정 혹은 변경할 수 있다. 비 init 네임스페이스에서의 마운트에는 이 옵션이 무시된다. 자세한 내용은 Delegation 섹션을 참고하라.
favordynmods 작업 마이그레이션이나 컨트롤러 on/off 같은 동적인 cgroup 변경의 지연(latency)을 줄이는 대신, fork와 exit 같은 핫 패스 동작을 더 비싸게 만든다. cgroup을 생성하고, 컨트롤러를 활성화한 뒤, CLONE_INTO_CGROUP으로 프로세스를 배치하는 정적 사용 패턴은 이 옵션의 영향을 받지 않는다.
memory_localevents memory.events 파일에 현재 cgroup만의 이벤트 데이터만 채우고, 서브트리의 이벤트는 포함하지 않는다. 이는 레거시 동작이며, 이 옵션 없이의 기본 동작은 서브트리 카운트를 포함하는 것이다. 이 옵션은 시스템 전체에 적용되며 init 네임스페이스에서 마운트 또는 remount 시에만 설정/변경 가능하다. 비 init 네임스페이스에서의 마운트에는 이 옵션이 무시된다.
memory_recursiveprot memory.min과 memory.low 보호를 리프 cgroup으로의 하향 전파를 명시적으로 설정할 필요 없이, 전체 서브트리에 재귀적으로 적용한다. 이를 통해 서브트리들 상호 간에는 보호를 적용하면서, 각 서브트리 내부에서는 자유 경쟁을 유지할 수 있다. 원래는 이 동작이 기본이어야 했지만, 기존 의미론(예: 상위 트리 레벨에서 비정상적으로 큰 ‘우회(bypass)’ 보호값을 지정하는 설정)에 의존하는 환경을 깨뜨리지 않기 위해 마운트 옵션으로 제공된다.
memory_hugetlb_accounting HugeTLB 메모리 사용량을 메모리 컨트롤러의 전체 메모리 사용량(통계 보고 및 메모리 보호 목적)에 포함시킨다. 이는 기존 설정에 영향을 줄 수 있는 새로운 동작이므로, 이 마운트 옵션으로 명시적으로 opt-in 해야 한다.
몇 가지 주의해야 할 점은 다음과 같다.
메모리 컨트롤러는 HugeTLB 풀 관리 기능을 제공하지 않는다. 사전 할당된 풀은 누구에게도 속하지 않는다. 특히, 새로운 HugeTLB folio가 풀에 할당될 때, 이는 메모리 컨트롤러 관점에서 계정(account)되지 않는다. 해당 folio가 실제로 사용될 때(예: 페이지 폴트 시점)에만 cgroup에 청구(charge)된다. 호스트 메모리 오버커밋 관리 시에는 하드 리밋 설정에서 이를 고려해야 한다. 일반적으로 HugeTLB 풀 관리는 HugeTLB 컨트롤러 등 다른 메커니즘을 통해 수행해야 한다.
HugeTLB folio를 메모리 컨트롤러에 청구하지 못하면 SIGBUS가 발생한다. 이는 HugeTLB 풀에 여전히 페이지가 남아 있더라도(cgroup 리밋에 도달했고 재클레임이 실패한 경우) 일어날 수 있다.
HugeTLB 메모리를 메모리 컨트롤러 사용량에 포함시키면, 메모리 보호 및 리클레임 동작에 영향을 준다. low, min 리밋 등의 사용자 공간 튜닝은 이를 고려해야 한다.
이 옵션을 사용하지 않는 동안 사용된 HugeTLB 페이지는, 이후 cgroup v2를 remount하더라도 메모리 컨트롤러에서 추적되지 않는다.
pids_localevents 이 옵션은 pids.events:max의 동작을 v1과 유사하게 되돌린다. 즉, cgroup 내부(해당 cgroup 자체)에서 발생한 fork 실패만 카운트한다. 이 옵션 없이는 pids.events.max가 cgroup 서브트리 전체에서의 pids.max 강제(enforcement)를 나타낸다.
초기에는 루트 cgroup만 존재하며, 모든 프로세스는 이 루트 cgroup에 속한다. 하위 cgroup은 하위 디렉터리를 생성하여 만들 수 있다.
bash# mkdir $CGROUP_NAME
하나의 cgroup은 여러 자식 cgroup을 가질 수 있으며, 이를 통해 트리 구조를 형성한다. 각 cgroup에는 읽기/쓰기 가능한 인터페이스 파일 “cgroup.procs”가 있다. 이 파일을 읽으면 해당 cgroup에 속한 모든 프로세스의 PID를 한 줄에 하나씩 나열한다. PID들은 정렬되어 있지 않으며, 프로세스가 다른 cgroup으로 이동했다가 다시 돌아온 경우나, 읽는 동안 PID가 재사용된 경우 동일 PID가 여러 번 나타날 수도 있다.
프로세스를 다른 cgroup으로 마이그레이션하려면, 해당 프로세스의 PID를 대상 cgroup의 “cgroup.procs” 파일에 기록하면 된다. 한 번의 write(2) 호출로는 하나의 프로세스만 이동시킬 수 있다. 프로세스가 여러 스레드로 구성되어 있는 경우, 어떤 스레드의 PID를 쓰더라도 해당 프로세스의 모든 스레드가 함께 이동한다.
프로세스가 자식 프로세스를 fork하면, 새 프로세스는 fork 시점에 부모 프로세스가 속한 cgroup에 생성된다. 프로세스가 종료한 뒤에도, reap될 때까지는 종료 시점에 속해 있던 cgroup과 연관된 상태로 남는다. 하지만 좀비 프로세스는 “cgroup.procs”에는 나타나지 않으며, 다른 cgroup으로 이동시킬 수도 없다.
자식도 없고 살아 있는 프로세스도 없는 cgroup은 디렉터리를 제거하여 파괴할 수 있다. 좀비 프로세스와만 연관된 cgroup은 비어 있는 것으로 간주되며, 제거 가능하다.
bash# rmdir $CGROUP_NAME
“/proc/$PID/cgroup”은 프로세스가 속한 cgroup을 보여준다. 시스템에서 레거시 cgroup(v1)을 사용하는 경우, 이 파일은 계층마다 한 줄씩 여러 줄을 포함할 수 있다. cgroup v2의 항목은 항상 “0::$PATH” 형식을 가진다.
bash# cat /proc/842/cgroup ... 0::/test-cgroup/test-cgroup-nested
프로세스가 좀비가 되고, 이후 해당 프로세스가 속해 있던 cgroup이 제거된 경우에는 경로 끝에 “ (deleted)”가 붙는다.
bash# cat /proc/842/cgroup ... 0::/test-cgroup/test-cgroup-nested (deleted)
cgroup v2는 일부 컨트롤러에 대해 스레드 단위(thread granularity)를 지원하여, 여러 프로세스 스레드에 걸친 계층적 자원 분배가 필요한 사용 사례를 지원한다. 기본적으로, 한 프로세스의 모든 스레드는 동일한 cgroup에 속하며, 이는 프로세스/스레드에 특정되지 않은 자원 소비를 보유하는 리소스 도메인 역할도 한다. 스레드 모드(thread mode)를 사용하면, 스레드를 서브트리 전체에 흩어 배치하면서도 이들을 위한 공통 리소스 도메인을 유지할 수 있다.
스레드 모드를 지원하는 컨트롤러를 threaded 컨트롤러라고 하고, 그렇지 않은 컨트롤러를 domain 컨트롤러라고 부른다.
cgroup를 threaded로 표시하면, 해당 cgroup은 부모의 리소스 도메인에 threaded cgroup으로 합류한다. 부모 역시 상위에 또 다른 리소스 도메인을 가진 threaded cgroup일 수 있다. threaded 서브트리의 루트, 즉 threaded가 아닌 가장 가까운 조상은 threaded domain 혹은 thread root라고 부르며, 전체 서브트리의 리소스 도메인 역할을 한다.
threaded 서브트리 내부에서는, 하나의 프로세스 스레드들을 서로 다른 cgroup에 배치할 수 있으며, no internal process constraint(내부 프로세스 금지 제약)의 영향을 받지 않는다. 즉, threaded 컨트롤러는 해당 cgroup이 스레드를 보유하든 말든 리프가 아닌 cgroup에서도 활성화될 수 있다.
threaded domain cgroup은 서브트리 전체의 도메인 자원 소비를 호스팅하므로, 그 안에 프로세스가 있든 없든 항상 내부 자원 소비가 있다고 간주되며, threaded가 아닌 채로 채워진(populated) 자식 cgroup을 가질 수 없다. 루트 cgroup은 no internal process constraint의 적용을 받지 않으므로, threaded domain이자 domain cgroup들의 부모 역할을 동시에 수행할 수 있다.
cgroup의 현재 동작 모드 혹은 타입은 “cgroup.type” 파일에 나타난다. 이 파일은 해당 cgroup이 일반 도메인(domain)인지, threaded 서브트리의 도메인 역할을 하는 threaded domain인지, threaded cgroup인지 등을 표시한다.
cgroup은 생성 시 항상 domain cgroup이며, “cgroup.type” 파일에 “threaded”를 기록하여 threaded로 설정할 수 있다. 이 동작은 단방향이다.
bash# echo threaded > cgroup.type
일단 threaded로 전환된 cgroup은 다시 domain으로 되돌릴 수 없다. 스레드 모드를 활성화하기 위해서는 다음 조건을 만족해야 한다.
해당 cgroup은 부모의 리소스 도메인에 합류하므로, 부모는 유효한 (threaded) 도메인 또는 threaded cgroup이어야 한다.
부모가 threaded가 아닌 도메인일 경우, 어떤 domain 컨트롤러도 활성화되어 있지 않고, 채워진(populated) domain 자식도 없어야 한다. 루트는 이 요구 사항에서 제외된다.
토폴로지 관점에서, cgroup은 무효(invalid) 상태가 될 수 있다. 예를 들어 다음과 같은 구조를 보자.
A (threaded domain) - B (threaded) - C (domain, 막 생성됨)
C는 도메인으로 생성되었지만, 자식 도메인을 수용할 수 있는 부모에 연결되어 있지 않다. 이 경우 C는 threaded cgroup으로 전환되기 전까지 사용할 수 없다. 이런 경우 “cgroup.type” 파일은 “domain (invalid)”를 보고한다. 잘못된 토폴로지 때문에 실패하는 연산은 errno로 EOPNOTSUPP를 사용한다.
하위 cgroup 중 하나가 threaded가 되거나, cgroup에 프로세스가 있는 상태에서 “cgroup.subtree_control”에 threaded 컨트롤러를 활성화하면, domain cgroup은 threaded domain으로 전환된다. 해당 조건이 사라지면 threaded domain은 다시 일반 domain으로 돌아간다.
“cgroup.threads”를 읽으면 cgroup에 속한 모든 스레드의 TID 리스트가 표시된다. 연산이 프로세스 단위가 아닌 스레드 단위라는 점을 제외하면, “cgroup.threads”의 형식과 동작은 “cgroup.procs”와 동일하다. “cgroup.threads”는 어떤 cgroup에서든 쓸 수 있지만, 같은 threaded 도메인 안에서만 스레드 이동이 가능하므로, 그 동작은 각 threaded 서브트리 내부로 제한된다.
threaded domain cgroup은 전체 서브트리의 리소스 도메인 역할을 하며, 스레드들이 서브트리 전체에 흩어져 있을 수 있지만 모든 프로세스는 threaded domain cgroup에 속한 것으로 간주된다. threaded domain cgroup의 “cgroup.procs”는 서브트리 안의 모든 프로세스의 PID를 포함하며, 서브트리 내부에서는 읽을 수 없다. 하지만, 서브트리 내 어떤 위치에서든 “cgroup.procs”에 쓰기를 통해 해당 프로세스의 모든 스레드를 그 cgroup으로 마이그레이션할 수 있다.
threaded 서브트리에서는 threaded 컨트롤러만 활성화될 수 있다. threaded 서브트리 내에서 threaded 컨트롤러를 활성화하면, 해당 cgroup과 그 자손들에 속한 스레드에 연관된 자원 소비만을 계정 및 제어한다. 특정 스레드에 귀속되지 않은 모든 소비는 threaded domain cgroup에 속한다.
threaded 서브트리는 no internal process constraint의 적용을 받지 않으므로, threaded 컨트롤러는 리프가 아닌 cgroup의 스레드와 그 자식 cgroup 간 경쟁을 처리할 수 있어야 한다. 각 threaded 컨트롤러는 이러한 경쟁을 어떻게 처리할지 자체적으로 정의한다.
현재 threaded 컨트롤러로 구현되어 threaded cgroup에서 활성화할 수 있는 컨트롤러는 다음과 같다.
각 비루트(non-root) cgroup에는 “cgroup.events” 파일이 있으며, 이 파일에는 해당 cgroup 서브트리에 살아 있는 프로세스가 있는지를 나타내는 “populated” 필드가 있다. cgroup과 그 자손에 살아 있는 프로세스가 하나도 없으면 값은 0, 그렇지 않으면 1이다. 값이 변경될 때 poll과 [id]notify 이벤트가 발생한다. 예를 들어, 특정 서브트리에 속한 모든 프로세스가 종료된 후 정리(clean-up) 작업을 시작하는 데 사용할 수 있다. populated 상태의 갱신과 알림은 재귀적으로 전달된다. 다음과 같은 서브트리를 생각해 보자. 괄호 안 숫자는 각 cgroup에 속한 프로세스 개수이다.
A(4) - B(0) - C(1) \ D(0)
이 경우 A, B, C의 “populated” 필드는 1이고, D는 0이다. C에 있던 단 하나의 프로세스가 종료되면, B와 C의 “populated” 필드는 0으로 바뀌고, 두 cgroup의 “cgroup.events” 파일에 파일 수정 이벤트가 발생한다.
컨트롤러가 커널에서 지원(컴파일되어 포함, 비활성화되지 않음, v1 계층에 붙어 있지 않음)되고, “cgroup.controllers” 파일에 나열되어 있으면 해당 cgroup에서 컨트롤러를 사용할 수 있다. 사용 가능하다는 것은 해당 cgroup 디렉터리에 컨트롤러 인터페이스 파일이 노출되어 있어, 대상 자원의 분배를 관찰하거나 제어할 수 있음을 의미한다.
각 cgroup에는, 해당 cgroup에서 활성화할 수 있는 모든 컨트롤러를 나열하는 “cgroup.controllers” 파일이 있다.
bash# cat cgroup.controllers cpu io memory
기본적으로 어떤 컨트롤러도 활성화되어 있지 않다. 컨트롤러는 “cgroup.subtree_control” 파일에 쓰기를 통해 활성화/비활성화할 수 있다.
bash# echo "+cpu +memory -io" > cgroup.subtree_control
“cgroup.controllers”에 나열된 컨트롤러만 활성화할 수 있다. 위와 같이 여러 동작을 동시에 지정할 경우, 모두 성공하거나 모두 실패한다. 동일 컨트롤러에 대해 여러 동작을 지정한 경우 마지막 동작만 유효하다.
어떤 cgroup에서 컨트롤러를 활성화한다는 것은, 해당 cgroup의 직계 자식들 사이에서 대상 자원의 분배를 제어하겠다는 의미이다. 다음과 같은 서브트리를 보자. 괄호 안에는 활성화된 컨트롤러가 나열되어 있다.
A(cpu,memory) - B(memory) - C() \ D()
A는 “cpu”와 “memory”를 활성화했으므로, 자식(이 경우 B)에게 CPU 사이클과 메모리를 어떻게 분배할지 제어한다. B는 “memory”는 활성화했으나 “cpu”는 활성화하지 않았다. 따라서 C와 D는 CPU 사이클에 대해서는 자유 경쟁하지만, B에 허용된 메모리의 분배는 제어된다.
컨트롤러는 대상 자원을 cgroup의 자식에게 분배하는 역할을 하므로, cgroup에서 컨트롤러를 활성화하면 자식 cgroup 내에 해당 컨트롤러의 인터페이스 파일이 생성된다. 위 예에서 B에서 “cpu”를 활성화하면 C와 D에 “cpu.” 접두사가 붙은 컨트롤러 인터페이스 파일들이 생성된다. 마찬가지로 B에서 “memory”를 비활성화하면 C와 D에서 “memory.” 접두사가 붙은 인터페이스 파일들이 제거된다. 즉, “cgroup.”으로 시작하지 않는 컨트롤러 인터페이스 파일은 cgroup 자신이 아니라 부모에 의해 소유된다.
자원은 상위에서 하위로 분배되며, cgroup은 상위로부터 자원을 분배받은 경우에만 그 자원을 자식에게 다시 분배할 수 있다. 즉, 비루트 cgroup의 “cgroup.subtree_control” 파일에는 부모의 “cgroup.subtree_control” 파일에서 이미 활성화된 컨트롤러만 포함될 수 있다. 부모가 컨트롤러를 활성화한 경우에만 자식에서 컨트롤러를 활성화할 수 있고, 한 명 이상의 자식이 해당 컨트롤러를 활성화하고 있는 동안에는 부모에서 해당 컨트롤러를 비활성화할 수 없다.
비루트 cgroup은 해당 cgroup에 자체 프로세스가 없을 때에만 도메인 자원을 자식에게 분배할 수 있다. 다시 말해, 자체적으로 프로세스를 포함하지 않는 도메인 cgroup만이 “cgroup.subtree_control” 파일에서 도메인 컨트롤러를 활성화할 수 있다.
이 제약은, 도메인 컨트롤러가 자신이 활성화되어 있는 계층 부분을 볼 때 언제나 프로세스가 리프에만 존재하도록 보장한다. 이를 통해 자식 cgroup이 부모의 내부 프로세스와 경쟁하는 상황을 배제한다.
루트 cgroup은 이 제약에서 예외이다. 루트는 다른 어떤 cgroup에도 연결할 수 없는 프로세스와 익명 자원 소비를 포함하며, 대부분의 컨트롤러에서 별도의 처리가 필요하다. 루트 cgroup에서 자원 소비를 어떻게 관리할지는 각 컨트롤러에 맡겨져 있다(자세한 내용은 컨트롤러 장의 비규범적 정보(Non-normative information) 섹션 참조).
이 제약은 해당 cgroup의 “cgroup.subtree_control”에 활성화된 컨트롤러가 하나도 없는 경우에는 적용되지 않는다. 그렇지 않으면, 프로세스로 채워진 cgroup의 자식을 생성할 수 없게 되기 때문이다. cgroup에서 자원 분배를 제어하려면, 먼저 자식 cgroup을 생성하고 모든 프로세스를 자식으로 옮긴 뒤에, “cgroup.subtree_control” 파일에서 컨트롤러를 활성화해야 한다.
cgroup은 두 가지 방식으로 위임될 수 있다. 첫째, 디렉터리와 그 안의 “cgroup.procs”, “cgroup.threads”, “cgroup.subtree_control” 파일에 쓰기 권한을 부여하여, 권한이 더 낮은 사용자에게 위임할 수 있다. 둘째, “nsdelegate” 마운트 옵션이 설정된 경우, 네임스페이스 생성 시 cgroup 네임스페이스에 자동 위임된다.
특정 디렉터리에 있는 자원 제어 인터페이스 파일들은 부모의 자원 분배를 제어하므로, 위임을 받은 대상(delegatee)은 이들 파일에 쓰기 권한을 가지면 안 된다. 첫 번째 방법(사용자 위임)에서는 단순히 이 파일들에 대한 권한을 주지 않음으로써 이를 구현한다. 두 번째 방법(네임스페이스 위임)에서는, 최소한 마운트 네임스페이스 등의 수단으로 네임스페이스 밖의 파일들이 위임 받은 대상에게 보이지 않도록 해야 하며, 커널은 cgroup 네임스페이스 내부에서 네임스페이스 루트에 위치한 파일에 대해 “/sys/kernel/cgroup/delegate”에 나열된 파일들(“cgroup.procs”, “cgroup.threads”, “cgroup.subtree_control” 등)을 제외하고는 모든 파일 쓰기를 거부한다.
두 위임 방식의 최종 결과는 동일하다. 일단 위임되면, 사용자는 해당 디렉터리 아래에 서브트리를 구축하고, 그 안에서 프로세스를 원하는 대로 조직할 수 있으며, 부모로부터 받은 자원을 그 안에서 다시 분배할 수 있다. 모든 자원 컨트롤러의 리밋 및 기타 설정은 계층적이므로, 위임된 서브트리 내부에서 어떤 일이 일어나더라도 부모가 부과한 자원 제한에서 벗어날 수 없다.
현재 cgroup은 위임된 서브트리 내에서 cgroup 개수나 중첩 깊이에 어떤 제한도 강제하지 않는다. 다만 향후 명시적으로 제한을 둘 수 있다.
위임된 서브트리는, 위임 받은 대상(delegatee)이 해당 서브트리 밖의 프로세스를 안으로 옮기거나, 안의 프로세스를 밖으로 내보낼 수 없다는 의미에서 폐쇄적이다.
권한이 낮은 사용자에게 위임하는 경우, 비루트 euid를 가진 프로세스가 “cgroup.procs” 파일에 PID를 쓰는 방식으로 대상을 다른 cgroup으로 마이그레이션하려면 다음 조건들을 모두 만족해야 한다.
쓰는 주체(writer)는 대상 cgroup의 “cgroup.procs” 파일에 쓰기 권한을 가져야 한다.
쓰는 주체는 소스 cgroup과 대상 cgroup의 공통 조상(common ancestor)에 있는 “cgroup.procs” 파일에도 쓰기 권한을 가져야 한다.
위 두 조건은, 위임을 받은 대상이 위임된 서브트리 내부에서는 자유롭게 프로세스를 이동할 수 있지만, 서브트리 밖에서 끌어오거나 서브트리 밖으로 내보내지 못하도록 보장한다.
예를 들어, cgroup C0와 C1이 사용자 U0에게 위임되었고, U0가 C0 아래에 C00과 C01을, C1 아래에 C10을 다음과 같이 생성했다고 가정하자. 또한 C0와 C1 아래의 모든 프로세스가 U0 소유라고 하자.
~ cgroup ~ \ C01
~ hierarchy ~
~~~~~~~~~~~~~ - C1 - C10
또한, 현재 C10에 있는 프로세스의 PID를 “C00/cgroup.procs”에 쓰고자 한다고 하자. U0는 이 파일에 대한 쓰기 권한을 가지고 있다. 하지만 소스 cgroup인 C10과 대상 cgroup인 C00의 공통 조상은 위임 지점보다 위에 있으며, U0는 그 공통 조상의 “cgroup.procs”에 대한 쓰기 권한이 없으므로, 이 쓰기는 -EACCES로 거부된다.
네임스페이스에 대한 위임에서는, 소스와 대상 cgroup 모두가 마이그레이션을 시도하는 프로세스가 속한 네임스페이스에서 접근 가능한 경우에만 마이그레이션을 허용한다. 둘 중 하나라도 접근 불가능하면 마이그레이션은 -ENOENT로 거부된다.
### 지침(Guidelines)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#guidelines "Permalink to this heading")
#### 한 번 조직하고 제어(Organize Once and Control)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#organize-once-and-control "Permalink to this heading")
프로세스를 cgroup 간에 마이그레이션하는 작업은 상대적으로 비용이 큰 연산이며, 메모리와 같이 상태를 가지는 자원은 프로세스와 함께 이동하지 않는다. 이는, 동기화 비용 측면에서 마이그레이션과 다양한 핫 패스 사이에 내재한 트레이드오프가 존재하기 때문에 의도된 설계 결정이다.
따라서 서로 다른 자원 제한을 적용하기 위해 프로세스를 자주 cgroup 간에 이동시키는 방식은 지양해야 한다. 워크로드는 시스템의 논리 및 자원 구조에 따라, 시작 시점에 한 번만 적절한 cgroup에 할당되어야 한다. 자원 분배에 대한 동적 조정은 컨트롤러의 설정 파일을 변경하는 방식으로 수행할 수 있다.
#### 이름 충돌 피하기(Avoid Name Collisions)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#avoid-name-collisions "Permalink to this heading")
하나의 디렉터리 아래에 cgroup 인터페이스 파일과 자식 cgroup 디렉터리가 함께 위치하므로, 자식 cgroup 이름이 인터페이스 파일 이름과 충돌할 수 있다.
모든 cgroup 코어 인터페이스 파일 이름은 “cgroup.” 접두사를 갖고, 각 컨트롤러의 인터페이스 파일 이름은 컨트롤러 이름과 점(.)을 접두사로 갖는다. 컨트롤러 이름은 소문자 알파벳과 ‘_’로 구성되며, ‘_’로 시작하지 않으므로, 충돌 회피를 위해 이를 접두 문자로 활용할 수 있다. 또한, 인터페이스 파일 이름은 job, service, slice, unit, workload와 같이 워크로드 분류에 자주 쓰이는 용어로 시작하거나 끝나지 않는다.
cgroup 자체는 이름 충돌을 방지하기 위한 동작을 제공하지 않으며, 이를 피하는 것은 사용자 책임이다.
자원 분배 모델[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#resource-distribution-models "Permalink to this heading")
--------------------------------------------------------------------------------------------------------------------------------------------
cgroup 컨트롤러는 자원 유형과 예상 사용 사례에 따라 여러 가지 자원 분배 방식을 구현한다. 이 절에서는 널리 사용되는 주요 방식과 그 예상 동작을 설명한다.
### 가중치(Weights)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#weights "Permalink to this heading")
부모의 자원은 활성 자식들의 가중치 합을 기준으로 분배된다. 각 자식은, 자신의 가중치를 전체 가중치 합에 대한 비율만큼의 자원을 받는다. 해당 시점에 실제로 자원을 사용할 수 있는 자식만 분배에 참여하므로, 이 모델은 작업 보존적(work-conserving)이다. 동적인 특성 때문에, 이 모델은 보통 상태가 없는(stateless) 자원에 사용된다.
모든 가중치는 [1, 10000] 범위에 있으며, 기본값은 100이다. 이 범위는 직관적인 범위를 유지하면서도, 위아래 방향으로 대칭적이고 충분히 세밀한 곱셈 비율을 제공한다.
가중치가 범위 내에 있는 한, 모든 설정 조합은 유효하며 설정 변경이나 프로세스 마이그레이션을 거부할 이유가 없다.
“cpu.weight”는 활성 자식들 간에 CPU 사이클을 비례적으로 분배하는 예이다.
### 리밋(Limits)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#limits "Permalink to this heading")
자식은 설정된 양만큼의 자원만을 소비할 수 있다. 리밋은 오버커밋될 수 있다. 즉, 자식들의 리밋 합이 부모에게 허용된 자원보다 클 수 있다.
리밋 값은 [0, max] 범위이며 기본값은 “max”(사실상 비활성)이다.
리밋은 오버커밋 가능하므로, 모든 설정 조합이 유효하며 설정 변경이나 프로세스 마이그레이션을 거부할 이유가 없다.
“io.max”는 특정 IO 디바이스에서 cgroup이 소비할 수 있는 최대 BPS 및/또는 IOPS를 제한하는 예이다.
### 보호(Protections)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#protections "Permalink to this heading")
cgroup은, 모든 조상 cgroup의 사용량이 각자의 보호 레벨 이하인 동안에는, 설정된 양만큼의 자원을 보호받는다. 보호는 강한 보장(hard guarantee)일 수 있고, 최선 노력(soft) 경계일 수도 있다. 보호도 오버커밋될 수 있으며, 이 경우 자식들 간에 보호받는 양의 총합은 부모에게 허용된 양으로 제한된다.
보호 값은 [0, max] 범위이며 기본값은 0(비활성)이다.
보호가 오버커밋 가능하므로, 모든 설정 조합이 유효하며 설정 변경이나 프로세스 마이그레이션을 거부할 이유가 없다.
“memory.low”는 최선 노력 기반의 메모리 보호를 구현하는 예이다.
### 할당(Allocations)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#allocations "Permalink to this heading")
cgroup은 유한한 자원을 독점적으로 일정량 할당받는다. 할당은 오버커밋될 수 없으며, 자식들의 할당 합이 부모에게 허용된 자원보다 클 수 없다.
할당 값은 [0, max] 범위이며, 기본값은 0(자원이 없음)이다.
할당은 오버커밋될 수 없으므로, 일부 설정 조합은 유효하지 않으며 거부되어야 한다. 또한, 해당 자원이 프로세스 실행에 필수인 경우, 프로세스 마이그레이션이 거부될 수도 있다.
“cpu.rt.max”는 실시간(realtime) 슬라이스를 하드 할당하는 예이다.
인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#interface-files "Permalink to this heading")
------------------------------------------------------------------------------------------------------------------
### 형식(Format)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#format "Permalink to this heading")
가능한 경우, 모든 인터페이스 파일은 다음 형식 중 하나를 사용해야 한다.
새 줄로 구분된 값
(한 번에 하나의 값만 쓸 수 있는 경우)
```text
VAL0\n
VAL1\n
...
```
공백으로 구분된 값
(읽기 전용이거나, 한 번에 여러 값을 쓸 수 있는 경우)
```text
VAL0 VAL1 ...\n
```
플랫 키-값(Flat keyed)
```text
KEY0 VAL0\n
KEY1 VAL1\n
...
```
중첩 키-값(Nested keyed)
```text
KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
...
```
쓰기 가능한 파일에서는 일반적으로 쓰기 형식이 읽기 형식을 따른다. 다만 컨트롤러에 따라, 뒤쪽 필드를 생략하거나, 자주 쓰이는 용례에 대한 제한된 축약 표기를 지원할 수도 있다.
플랫 키-값 및 중첩 키-값 파일에서는, 한 번에 하나의 키에 대한 값만 쓸 수 있다. 중첩 키-값 파일에서, 서브 키-값 쌍(sub key pairs)은 임의의 순서로 지정할 수 있으며, 모든 쌍을 지정할 필요는 없다.
### 규칙(Conventions)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#conventions "Permalink to this heading")
* 단일 기능(feature)에 대한 설정은 하나의 파일에 담아야 한다.
* 루트 cgroup은 자원 제어에서 면제되어야 하며, 따라서 자원 제어 인터페이스 파일을 가져서는 안 된다.
* 기본 시간 단위는 마이크로초(microseconds)이다. 다른 단위를 사용할 경우, 명시적인 단위 접미사를 포함해야 한다.
* 퍼센트(ppm, %) 단위 양은 소수점 이하 두 자리 이상을 가진 퍼센트 표현(예: 13.40)으로 표현해야 한다.
* 컨트롤러가 가중치 기반 자원 분배를 구현한다면, 해당 인터페이스 파일 이름은 “weight”여야 하며, [1, 10000] 범위와 기본값 100을 가져야 한다. 이 값들은 충분한 가중치 편향을 제공하면서도 직관적(기본값 100% 등)인 범위를 유지하도록 선택되었다.
* 컨트롤러가 절대적(하드) 자원 보장 또는 리밋을 구현한다면, 인터페이스 파일 이름은 각각 “min”과 “max”여야 한다. 최선 노력(soft) 보장 또는 리밋을 구현한다면, 각각 “low”와 “high”여야 한다.
위 네 개의 제어 파일에서는, 위쪽 무한대(upper infinity)를 나타내기 위해 “max”라는 특수 토큰을 읽기와 쓰기 모두에서 사용해야 한다.
* 어떤 설정이 기본값과 키 기반 특정 값(오버라이드)을 모두 가지고 있다면, 기본 항목은 “default” 키를 사용하고 파일의 첫 항목으로 나타나야 한다.
기본값은 “default $VAL” 또는 “$VAL”을 쓰는 방식으로 갱신할 수 있다.
특정 오버라이드를 갱신할 때 값으로 “default”를 쓰면 오버라이드를 제거한다는 의미가 된다. 값을 “default”로 갖는 오버라이드 항목은 읽을 때 파일에 나타나서는 안 된다.
예를 들어, major:minor 디바이스 번호를 키로 하고 정수 값을 가지는 설정은 다음과 같이 표시될 수 있다.
```bash
# cat cgroup-example-interface-file
default 150
8:0 300
```
기본값은 다음과 같이 갱신할 수 있다.
```bash
# echo 125 > cgroup-example-interface-file
```
또는
```bash
# echo "default 125" > cgroup-example-interface-file
```
오버라이드는 다음과 같이 설정할 수 있다.
```bash
# echo "8:16 170" > cgroup-example-interface-file
```
그리고 다음과 같이 제거할 수 있다.
```bash
# echo "8:0 default" > cgroup-example-interface-file
# cat cgroup-example-interface-file
default 125
8:16 170
```
* 빈번하지 않은 이벤트에 대해서는 “events” 인터페이스 파일을 만들어, 이벤트 키-값 쌍을 나열해야 한다. 알림 대상 이벤트가 발생할 때마다, 해당 파일에 파일 수정 이벤트를 발생시켜야 한다.
### 코어 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#core-interface-files "Permalink to this heading")
모든 cgroup 코어 파일 이름은 “cgroup.” 접두사를 가진다.
> cgroup.type
> 비루트 cgroup에 존재하는 읽기/쓰기 단일 값 파일이다.
>
>
> 읽을 때, 해당 cgroup의 현재 타입을 나타내며 다음 값 중 하나가 될 수 있다.
>
>
> * “domain” : 일반적인 유효 도메인 cgroup.
>
> * “domain threaded” : threaded 서브트리의 루트 역할을 하는 threaded domain cgroup.
>
> * “domain invalid” : 무효 상태의 cgroup. 프로세스를 둘 수 없고, 컨트롤러도 활성화할 수 없다. threaded cgroup이 되도록 허용될 수는 있다.
>
> * “threaded” : threaded 서브트리의 구성원인 threaded cgroup.
>
>
>
> cgroup에 “threaded”를 이 파일에 쓰면 threaded cgroup으로 전환할 수 있다.
>
> cgroup.procs
> 모든 cgroup에 존재하는, 읽기/쓰기 가능한 새 줄 구분 값 파일이다.
>
>
> 읽으면, 해당 cgroup에 속한 모든 프로세스의 PID를 한 줄에 하나씩 나열한다. PID는 정렬되어 있지 않고, 프로세스가 다른 cgroup으로 이동했다가 다시 돌아온 경우나, 읽는 동안 PID가 재사용된 경우 같은 PID가 여러 번 나타날 수 있다.
>
>
> PID를 쓰면 해당 PID에 연결된 프로세스를 그 cgroup으로 마이그레이션한다. 쓰는 주체는 다음 조건들을 모두 만족해야 한다.
>
>
> * 대상 “cgroup.procs” 파일에 쓰기 권한이 있어야 한다.
>
> * 소스와 대상 cgroup의 공통 조상에 있는 “cgroup.procs” 파일에도 쓰기 권한이 있어야 한다.
>
>
>
> 서브트리를 위임할 때는 이 파일과 디렉터리 자체에 대한 쓰기 권한을 함께 부여해야 한다.
>
>
> threaded cgroup에서는, 모든 프로세스가 thread root에 속하므로, 이 파일을 읽으면 EOPNOTSUPP로 실패한다. 쓰기는 가능하며, 해당 프로세스의 모든 스레드를 그 cgroup으로 이동시킨다.
>
> cgroup.threads
> 모든 cgroup에 존재하는, 읽기/쓰기 가능한 새 줄 구분 값 파일이다.
>
>
> 읽으면, 해당 cgroup에 속한 모든 스레드의 TID를 한 줄에 하나씩 나열한다. TID는 정렬되어 있지 않고, 스레드가 다른 cgroup으로 이동했다가 다시 돌아온 경우나, 읽는 동안 TID가 재사용된 경우 같은 TID가 여러 번 나타날 수 있다.
>
>
> TID를 쓰면 해당 TID에 연결된 스레드를 그 cgroup으로 마이그레이션한다. 쓰는 주체는 다음 조건들을 모두 만족해야 한다.
>
>
> * 대상 “cgroup.threads” 파일에 쓰기 권한이 있어야 한다.
>
> * 해당 스레드가 현재 속한 cgroup과, 대상 cgroup이 같은 리소스 도메인 안에 있어야 한다.
>
> * 소스와 대상 cgroup의 공통 조상에 있는 “cgroup.procs” 파일에도 쓰기 권한이 있어야 한다.
>
>
>
> 서브트리를 위임할 때는 이 파일과 디렉터리 자체에 대한 쓰기 권한을 함께 부여해야 한다.
>
> cgroup.controllers
> 모든 cgroup에 존재하는 읽기 전용 공백-구분 값 파일이다.
>
>
> 해당 cgroup에서 활성화할 수 있는 모든 컨트롤러의 리스트를 공백으로 구분하여 표시한다. 컨트롤러 순서는 정해져 있지 않다.
>
> cgroup.subtree_control
> 모든 cgroup에 존재하는 읽기/쓰기 가능한 공백-구분 값 파일이다. 초기에는 비어 있다.
>
>
> 읽으면, 해당 cgroup이 자식에게 자원을 분배하기 위해 활성화한 컨트롤러 리스트를 공백으로 구분하여 보여준다.
>
>
> ‘+’ 또는 ‘-’가 붙은 컨트롤러 이름의 공백-구분 리스트를 써서 컨트롤러를 활성화 또는 비활성화할 수 있다. 이름 앞의 ‘+’는 활성화, ‘-’는 비활성화를 의미한다. 어떤 컨트롤러 이름이 리스트에 여러 번 등장하면 마지막 것이 유효하다. 여러 활성화/비활성화 연산을 동시에 지정할 경우, 모두 성공하거나 모두 실패한다.
>
> cgroup.events
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다. 다음 항목들이 정의되어 있다. 별도로 명시하지 않는 한, 값이 변경되면 파일 수정 이벤트가 발생한다.
>
>
> > populated
> > 해당 cgroup 또는 그 자손에 하나라도 살아 있는 프로세스가 있으면 1, 없으면 0.
> >
> > frozen
> > 해당 cgroup이 동결(freeze) 상태면 1, 아니면 0.
>
> cgroup.max.descendants
> 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> 허용되는 최대 자손 cgroup 수를 지정한다. 실제 자손 수가 이 값 이상인 경우, 계층에 새로운 cgroup을 생성하려는 시도는 실패한다.
>
> cgroup.max.depth
> 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> 현재 cgroup 아래에서 허용되는 최대 자손 깊이(depth)를 지정한다. 실제 자손 깊이가 이 값 이상이면, 새로운 자식 cgroup을 생성하려는 시도는 실패한다.
>
> cgroup.stat
> 읽기 전용 플랫 키-값 파일로, 다음 항목이 정의되어 있다.
>
>
> > nr_descendants
> > 보이는(visible) 모든 자손 cgroup의 총 개수.
> >
> > nr_dying_descendants
> > dying 상태인 자손 cgroup의 총 개수. cgroup은 사용자가 삭제 요청을 한 뒤 dying 상태가 된다. cgroup은 시스템 부하 등에 따라 정의되지 않은 일정 시간 동안 dying 상태를 유지한 뒤 완전히 파괴된다.
> >
> >
> > 프로세스는 어떤 경우에도 dying cgroup에 들어갈 수 없으며, dying cgroup은 다시 살아날 수 없다.
> >
> >
> > dying cgroup은 삭제 시점에 유효했던 리밋을 넘지 않는 선에서 시스템 자원을 계속 소비할 수 있다.
> >
> > nr_subsys_<cgroup_subsys>
> > 현재 cgroup 및 그 아래에 있는 live cgroup 서브시스템(예: memory cgroup)의 총 개수.
> >
> > nr_dying_subsys_<cgroup_subsys>
> > 현재 cgroup 및 그 아래에 있는 dying 상태의 cgroup 서브시스템(예: memory cgroup)의 총 개수.
>
> cgroup.stat.local
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다. 다음 항목이 정의되어 있다.
>
>
> > frozen_usec
> > 이 cgroup이 자체 설정이나 조상 cgroup의 설정에 의해 동결 상태에서 해제 상태로 전환되기까지 누적된 시간. “frozen” 상태에 도달했는지 여부 자체는 이 값에 포함되지 않는다.
> >
> >
> > 아래 ASCII 그림에서 freezer 상태 전이를 다음과 같이 표현한다고 하자.
> >
> >
> >
> > 1 _____
> > frozen 0 __/ \__
> > ab cd
> >
> >
> > 측정되는 시간 구간은 a에서 c까지의 구간이다.
>
> cgroup.freeze
> 비루트 cgroup에 존재하는 읽기/쓰기 단일 값 파일이다. 허용 값은 “0”과 “1”이며, 기본값은 “0”이다.
>
>
> “1”을 쓰면 해당 cgroup과 그 모든 자손 cgroup이 동결된다. 이는 해당 cgroup 트리에 속한 모든 프로세스가 정지되어, 명시적으로 해제(thaw)될 때까지 실행되지 않음을 의미한다. cgroup 동결은 어느 정도 시간이 걸릴 수 있다. 동결이 완료되면, “cgroup.events” 제어 파일의 “frozen” 값이 “1”로 갱신되고, 이에 대한 알림이 발생한다.
>
>
> 하나의 cgroup은 자체 설정 또는 조상 cgroup의 설정에 의해 동결될 수 있다. 조상 cgroup 중 하나라도 동결 상태라면, 해당 cgroup은 계속 동결 상태를 유지한다.
>
>
> 동결된 cgroup 안의 프로세스는 치명적 시그널로 종료될 수 있다. 또한, 사용자에 의한 명시적 이동이나 fork()와 동결의 경합(race)에 의해, 동결된 cgroup으로 들어가거나 나올 수도 있다. 동결된 cgroup으로 프로세스가 이동되면 정지 상태가 되며, 동결된 cgroup에서 밖으로 이동되면 다시 실행된다.
>
>
> cgroup의 동결 상태는 cgroup 트리 연산에 영향을 주지 않는다. 즉, 비어 있는 동결된 cgroup을 삭제할 수 있고, 새로운 하위 cgroup을 생성하는 것도 가능하다.
>
> cgroup.kill
> 비루트 cgroup에 존재하는 쓰기 전용 단일 값 파일이다. 허용 값은 “1”뿐이다.
>
>
> “1”을 쓰면 해당 cgroup과 그 모든 자손 cgroup이 kill된다. 이는 해당 cgroup 트리에 위치한 모든 프로세스가 SIGKILL을 통해 종료됨을 의미한다.
>
>
> cgroup 트리를 kill하는 연산은 동시에 발생하는 fork를 적절히 처리하며, 마이그레이션으로부터 보호된다.
>
>
> threaded cgroup에서는, cgroup kill이 프로세스 전체(쓰레드 그룹 전체)에 대한 연산이기 때문에, 이 파일에 쓰면 EOPNOTSUPP로 실패한다.
>
> cgroup.pressure
> 읽기/쓰기 가능한 단일 값 파일이다. 허용 값은 “0”과 “1”이며 기본값은 “1”이다.
>
>
> “0”을 쓰면 해당 cgroup에 대한 PSI(Pressure Stall Information) 계정을 비활성화한다. “1”을 쓰면 PSI 계정을 다시 활성화한다.
>
>
> 이 제어 속성은 계층적이지 않다. 즉, 어떤 cgroup에서 PSI 계정을 비활성화하거나 활성화해도 자손 cgroup의 PSI 계정에는 영향을 주지 않고, 루트에서부터의 계층적 활성화를 필요로 하지 않는다.
>
>
> 이 제어 속성이 존재하는 이유는, PSI가 각 cgroup에 대해 별도로 stall을 계정하고, 계층의 각 레벨에서 이를 집계하기 때문이다. 이는 계층 깊이가 깊은 상황에서 일부 워크로드에 비무시할 수 없는 오버헤드를 야기할 수 있으며, 이런 경우 비리프 cgroup에서 PSI 계정을 비활성화하기 위해 이 속성을 사용할 수 있다.
>
> irq.pressure
> 읽기/쓰기 가능한 중첩 키-값 파일이다.
>
>
> IRQ/SOFTIRQ에 대한 pressure stall 정보를 보여준다. 자세한 내용은 [Documentation/accounting/psi.rst](https://docs.kernel.org/accounting/psi.html#psi)를 참고하라.
컨트롤러[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#controllers "Permalink to this heading")
----------------------------------------------------------------------------------------------------------
### CPU[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu "Permalink to this heading")
“cpu” 컨트롤러는 CPU 사이클 분배를 제어한다. 이 컨트롤러는 일반 스케줄링 정책을 위해 가중치 기반 및 절대 대역폭 리밋 모델을 구현하며, 실시간 스케줄링 정책을 위해 절대 대역폭 할당 모델을 구현한다.
위 모델들은 모두 시간 기반(temporal base)에 한해 사이클 분배를 정의하며, 작업이 실행되는 CPU 주파수는 고려하지 않는다. (선택적인) utilization clamping 지원을 사용하면, schedutil cpufreq 거버너에 대해 CPU가 항상 제공해야 하는 최소 원하는 주파수와, 넘지 않아야 할 최대 원하는 주파수를 힌트로 제공할 수 있다.
경고: cgroup2 cpu 컨트롤러는 아직(대역폭) 실시간 프로세스 제어를 지원하지 않는다. 실시간 프로세스 그룹 스케줄링을 위한 CONFIG_RT_GROUP_SCHED 옵션을 사용해 빌드된 커널에서는, 모든 RT 프로세스가 루트 cgroup에 있을 때에만 cpu 컨트롤러를 활성화할 수 있다. 시스템 관리 소프트웨어가 부팅 시점에 이미 RT 프로세스를 루트가 아닌 cgroup에 배치했을 수 있으며, 이러한 RT 프로세스는 CONFIG_RT_GROUP_SCHED가 활성화된 커널에서 cpu 컨트롤러를 활성화하기 전에 루트 cgroup으로 옮겨야 할 수도 있다.
CONFIG_RT_GROUP_SCHED가 비활성화된 경우에는 이 제한이 적용되지 않으며, 일부 인터페이스 파일은 실시간 프로세스에 영향을 주거나 이를 계정한다. 구체적인 내용은 아래 섹션을 참조하라. CONFIG_RT_GROUP_SCHED의 영향을 받는 것은 cpu 컨트롤러뿐이며, 다른 컨트롤러는 실시간 프로세스의 자원 제어에 대해 CONFIG_RT_GROUP_SCHED 여부와 무관하게 사용할 수 있다.
#### CPU 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files "Permalink to this heading")
cpu 컨트롤러와의 상호 작용은 프로세스의 스케줄링 정책 및 하부 스케줄러에 따라 달라진다. cpu 컨트롤러 관점에서, 프로세스는 다음과 같이 분류할 수 있다.
* 공정(fair) 클래스 스케줄러에 속한 프로세스
* `cgroup_set_weight` 콜백을 가진 BPF 스케줄러에 속한 프로세스
* 그 외의 모든 프로세스: `SCHED_{FIFO,RR,DEADLINE}` 및 `cgroup_set_weight` 콜백이 없는 BPF 스케줄러에 속한 프로세스
프로세스가 언제 공정 클래스 스케줄러 또는 BPF 스케줄러에 속하는지에 대해서는 [Documentation/scheduler/sched-ext.rst](https://docs.kernel.org/scheduler/sched-ext.html#sched-ext)를 참고하라.
아래 각 인터페이스 파일 설명에서, 위의 범주들을 참조한다. 모든 시간 단위는 마이크로초이다.
> cpu.stat
> 읽기 전용 플랫 키-값 파일이다. 이 파일은 컨트롤러가 활성화되지 않았더라도 항상 존재한다.
>
>
> 항상 다음 세 가지 통계를 보고하며, 이는 cgroup 내 모든 프로세스를 대상으로 한다.
>
>
> * usage_usec
>
> * user_usec
>
> * system_usec
>
>
>
> 컨트롤러가 활성화된 경우에는, 공정 클래스 스케줄러에 속한 프로세스만을 대상으로 하는 다음 다섯 가지 통계도 보고한다.
>
>
> * nr_periods
>
> * nr_throttled
>
> * throttled_usec
>
> * nr_bursts
>
> * burst_usec
>
>
> cpu.weight
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “100”이다.
>
>
> 비 idle 그룹(cpu.idle = 0)의 경우, weight는 [1, 10000] 범위이다.
>
>
> 만약 해당 cgroup이 SCHED_IDLE(cpu.idle = 1)로 설정된 경우, weight 값은 0으로 표시된다.
>
>
> 이 파일은 공정 클래스 스케줄러에 속한 프로세스와, `cgroup_set_weight` 콜백을 가진 BPF 스케줄러에 속한 프로세스(콜백의 구현 내용에 따라)에만 영향을 미친다.
>
> cpu.weight.nice
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “0”이다.
>
>
> nice 값의 범위는 [-20, 19]이다.
>
>
> 이 파일은 “cpu.weight”의 대체 인터페이스로, nice(2)에서 사용하는 값과 동일한 값으로 weight를 읽고 설정할 수 있게 한다. nice 값의 범위가 더 작고, 분해능이 더 거칠기 때문에, 읽기 시 반환되는 값은 현재 weight에 대한 가장 근접한 근사값이다.
>
>
> 이 파일 역시 공정 클래스 스케줄러에 속한 프로세스와, `cgroup_set_weight` 콜백을 가진 BPF 스케줄러에 속한 프로세스(콜백의 구현 내용에 따라)에만 영향을 미친다.
>
> cpu.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 두 값 파일이며, 기본값은 “max 100000”이다.
>
>
> 최대 대역폭 리밋을 의미하며, 형식은 다음과 같다.
>
>
>
> $MAX $PERIOD
>
>
> 여기서, 그룹은 각 $PERIOD 기간 동안 최대 $MAX만큼 사용할 수 있음을 의미한다. $MAX에 “max”를 지정하면 리밋이 없음을 뜻한다. 한 개의 숫자만 쓰는 경우, $MAX만 갱신된다.
>
>
> 이 파일은 공정 클래스 스케줄러에 속한 프로세스에만 영향을 미친다.
>
> cpu.max.burst
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “0”이다.
>
>
> burst 값은 [0, $MAX] 범위이다.
>
>
> 이 파일은 공정 클래스 스케줄러에 속한 프로세스에만 영향을 미친다.
>
> cpu.pressure
> 읽기/쓰기 가능한 중첩 키-값 파일이다.
>
>
> CPU에 대한 pressure stall 정보를 제공한다. 자세한 내용은 [Documentation/accounting/psi.rst](https://docs.kernel.org/accounting/psi.html#psi)를 참고하라.
>
>
> 이 파일은 cgroup 내의 모든 프로세스를 대상으로 계정한다.
>
> cpu.uclamp.min
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “0”이며, 이는 utilization boosting이 없음을 의미한다.
>
>
> 최소 요청(utilization 보호)값을 퍼센트 유리수 형식(예: 12.34%)으로 지정한다.
>
>
> 이 인터페이스는 sched_setattr(2)와 유사한 방식으로 최소 utilization clamp 값을 읽고 설정할 수 있게 한다. 이 최소 utilization 값은 실시간 프로세스를 포함한 모든 태스크의 개별 최소 utilization clamp를 제한하는 데 사용된다.
>
>
> 요청한 최소 utilization(보호)값은 항상 현재 최대 utilization(제한)값인 cpu.uclamp.max에 의해 상한 제한을 받는다.
>
>
> 이 파일은 cgroup 내의 모든 프로세스에 영향을 미친다.
>
> cpu.uclamp.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이며, 이는 utilization 상한이 없음을 의미한다.
>
>
> 최대 요청(utilization 상한)을 퍼센트 유리수 형식(예: 98.76%)으로 지정한다.
>
>
> 이 인터페이스는 sched_setattr(2)와 유사한 방식으로 최대 utilization clamp 값을 읽고 설정할 수 있게 한다. 이 최대 utilization 값은 실시간 프로세스를 포함한 모든 태스크의 개별 최대 utilization clamp를 제한하는 데 사용된다.
>
>
> 이 파일 역시 cgroup 내의 모든 프로세스에 영향을 미친다.
>
> cpu.idle
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 0이다.
>
>
> 이는 per-task SCHED_IDLE 스케줄 정책의 cgroup 버전에 해당한다. 이 값을 1로 설정하면 해당 cgroup의 스케줄 정책이 SCHED_IDLE이 된다. cgroup 안의 스레드들은 서로 간의 상대 우선순위는 유지하지만, 같은 계층 내의 다른 cgroup에 비해 매우 낮은 우선순위로 취급된다.
>
>
> 이 파일은 공정 클래스 스케줄러에 속한 프로세스에만 영향을 미친다.
### 메모리[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#memory "Permalink to this heading")
“memory” 컨트롤러는 메모리 분배를 제어한다. 메모리는 상태를 가지는 자원이므로, 리밋 및 보호 모델을 모두 구현한다. 메모리 사용량, 리클레임 압력, 그리고 상태성(stateful) 특성 간의 긴밀한 상호 작용 때문에, 메모리 분배 모델은 비교적 복잡하다.
완전히 누수가 없지는 않지만, 특정 cgroup에 의해 발생하는 주요 메모리 사용량은 대부분 추적되어, 전체 메모리 소비를 합리적인 수준에서 계정하고 제어할 수 있다. 현재 다음과 같은 종류의 메모리 사용량이 추적된다.
* 사용자 공간 메모리 – 페이지 캐시 및 익명(anonymous) 메모리
* dentry, inode 등 커널 데이터 구조
* TCP 소켓 버퍼
위 목록은 커버리지를 개선하기 위해 향후 확장될 수 있다.
#### 메모리 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-interface-files "Permalink to this heading")
모든 메모리 양은 바이트 단위이다. PAGE_SIZE에 정렬되지 않은 값을 쓰면, 나중에 읽을 때 가장 가까운 PAGE_SIZE 배수로 반올림되어 보일 수 있다.
> memory.current
> 비루트 cgroup에 존재하는 읽기 전용 단일 값 파일이다.
>
>
> 해당 cgroup과 그 자손에서 현재 사용 중인 메모리의 총량을 의미한다.
>
> memory.min
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “0”이다.
>
>
> 하드 메모리 보호이다. cgroup의 메모리 사용량이 해당 cgroup의 유효(effective) min 경계 이내인 경우, 어떤 상황에서도 그 cgroup의 메모리는 리클레임되지 않는다. 보호되지 않은(reclaimable) 메모리가 더 이상 없다면, OOM killer가 호출된다. 유효 min 경계(또는 그것보다 높은 유효 low 경계)를 넘는 부분에 대해서는, 초과분 비율에 비례하여 페이지가 리클레임되며, 초과가 작을수록 리클레임 압력이 줄어든다.
>
>
> 유효 min 경계는 모든 조상 cgroup의 memory.min 값에 의해 제한된다. 만약 memory.min이 오버커밋된 상황(자식 cgroup들이 부모가 허용할 수 있는 것보다 많은 보호 메모리를 요구하는 경우)에서는, 각 자식 cgroup은 자신의 memory.min 이하 실제 사용량 비율에 비례하여 부모 보호량의 일부를 받는다.
>
>
> 전체 시스템에서 사용 가능한 메모리보다 많은 양을 이 보호에 두는 것은 권장되지 않으며, 지속적인 OOM을 야기할 수 있다.
>
>
> 프로세스로 채워져 있지 않은(non-populated) memory cgroup의 memory.min은 무시된다.
>
> memory.low
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “0”이다.
>
>
> 최선 노력(best-effort) 메모리 보호이다. cgroup의 메모리 사용량이 유효 low 경계 이내라면, 보호되지 않은 cgroup에 리클레임할 메모리가 없을 때를 제외하고는, 해당 cgroup의 메모리는 리클레임되지 않는다. 유효 low 경계(또는 그것보다 높은 유효 min 경계)를 넘는 부분에 대해서는, 초과분 비율에 비례하여 페이지가 리클레임되며, 초과가 작을수록 리클레임 압력이 줄어든다.
>
>
> 유효 low 경계는 모든 조상 cgroup의 memory.low 값에 의해 제한된다. memory.low가 오버커밋된 경우(자식 cgroup들이 부모가 허용할 수 있는 것보다 많은 보호 메모리를 요구하는 경우)에는, 각 자식 cgroup은 자신의 memory.low 이하 실제 사용량 비율에 비례하여 부모 보호량의 일부를 받는다.
>
>
> 전체 시스템에서 사용 가능한 메모리보다 많은 양을 이 보호에 두는 것은 권장되지 않는다.
>
> memory.high
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> 메모리 사용량 스로틀(throttle) 리밋이다. cgroup의 사용량이 high 경계를 넘으면, 해당 cgroup의 프로세스들은 스로틀되고 강한 리클레임 압력을 받는다.
>
>
> high 리밋을 초과했다고 해서 OOM killer가 호출되지는 않으며, 극단적 상황에서는 이 리밋이 깨질 수도 있다. high 리밋은 외부 프로세스가 제한된 cgroup을 모니터링하며, 심한 리클레임 압력을 완화하는 시나리오에서 사용해야 한다.
>
>
> memory.high를 O_NONBLOCK으로 열면 동기 리클레임이 우회된다. 이는, 자신의 CPU 자원을 메모리 리클레임에 쓰지 않고도, 동적으로 잡(job)의 메모리 리밋을 조정해야 하는 관리용 프로세스에게 유용하다. 잡은 다음 charge 요청에서 리클레임을 트리거하거나 스로틀을 당하게 된다.
>
>
> O_NONBLOCK을 사용할 경우, 대상 memory cgroup이 사용량을 리밋 이하로 줄이는 데 상당 시간(지연된 charge 요청이나, 리클레임을 느리게 하는 메모리 반복 접근 등) 소요될 수 있음을 유의해야 한다.
>
> memory.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> 메모리 사용량 하드 리밋이다. 이는 cgroup의 메모리 사용량을 제한하는 주된 메커니즘이다. cgroup의 메모리 사용량이 이 리밋에 도달하고도 줄어들지 않으면, 해당 cgroup에서 OOM killer가 호출된다. 특정 상황에서는 사용량이 일시적으로 리밋을 초과할 수도 있다.
>
>
> 기본 설정에서, 일반 0-오더(0-order) 할당은 OOM killer가 현재 태스크를 희생자로 선택하지 않는 한 항상 성공한다.
>
>
> 일부 종류의 할당은 OOM killer를 호출하지 않는다. 이 경우 호출자는 다른 방식으로 재시도하거나, -ENOMEM으로 사용자 공간에 반환하거나, 디스크 리드어헤드처럼 조용히 무시할 수도 있다.
>
>
> memory.max를 O_NONBLOCK으로 열면 동기 리클레임과 OOM-kill이 우회된다. 이는, 자신의 CPU 자원을 메모리 리클레임에 쓰지 않고도, 동적으로 잡의 메모리 리밋을 조정해야 하는 관리용 프로세스에게 유용하다. 잡은 다음 charge 요청에서 리클레임 및/또는 OOM-kill을 트리거하게 된다.
>
>
> O_NONBLOCK을 사용할 경우, 대상 memory cgroup이 사용량을 리밋 이하로 줄이는 데 상당 시간 소요될 수 있음을 유의해야 한다(지연된 charge 요청, 리클레임을 늦추는 메모리 반복 접근 등).
>
> memory.reclaim
> 모든 cgroup에 존재하는 쓰기 전용 중첩 키-값 파일이다.
>
>
> 이는 대상 cgroup에서 메모리 리클레임을 트리거하기 위한 단순한 인터페이스이다.
>
>
> 예:
>
>
>
> echo "1G" > memory.reclaim
>
>
> 커널은 지정된 양보다 더 많이 또는 더 적게 리클레임할 수 있다. 지정한 양보다 적은 바이트를 리클레임한 경우, -EAGAIN이 반환된다.
>
>
> 또한, 이 인터페이스로 트리거된 사전(proactive) 리클레임은, 해당 memory cgroup에 메모리 압력이 있음을 의미하는 용도가 아니다. 그러므로, 일반적으로 memory 리클레임으로 인해 수행되는 소켓 메모리 밸런싱은 이 경우에는 동작하지 않는다. 이는, network 계층이 memory.reclaim에 의해 유도된 리클레임에 기반해 적응(adapt)하지 않음을 의미한다.
정의된 중첩 키는 다음과 같다.
> > > swappiness 리클레임 시 사용할 swappiness 값
> >
> >
> > swappiness 값을 지정하면 리클레임 시 해당 swappiness로 동작하도록 커널에 지시한다. 이는 memcg 리클레임에 적용되는 vm.swappiness와 동일한 의미를 가지며, 기존 제한 사항 및 향후 확장과 같은 의미를 가진다.
> >
> >
> > swappiness의 유효 범위는 [0-200, max]이며, swappiness=max는 익명 메모리만 리클레임하도록 한다.
>
> memory.peak
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다.
>
>
> cgroup과 그 자손에 대해, cgroup 생성 시점 또는 이 파일 디스크립터에 대한 가장 최근 리셋 이후 기록된 최대 메모리 사용량이다.
>
>
> 비어 있지 않은 어떤 문자열이든 이 파일에 쓰면, 해당 파일 디스크립터를 통한 이후 읽기에 대해, 현재 메모리 사용량으로 피크 값이 리셋된다.
>
> memory.oom.group
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “0”이다.
>
>
> OOM killer가 이 cgroup을 분할 불가능한(atomic) 워크로드로 취급해야 할지 여부를 결정한다. 설정된 경우, 해당 memory cgroup이 리프가 아니면 그 자손을 포함하여, cgroup에 속한 모든 태스크가 함께 kill되거나 아예 kill되지 않는다. 이를 통해 워크로드 무결성을 보장하기 위해 부분적인 kill을 피할 수 있다.
>
>
> OOM 보호(oom_score_adj가 -1000으로 설정된 태스크)는 예외로 취급되며, 절대 kill되지 않는다.
>
>
> 어떤 cgroup에서 OOM killer가 호출된 경우, 해당 cgroup 밖의 태스크는, 조상 cgroup의 memory.oom.group 값에 관계없이 kill 대상이 되지 않는다.
>
> memory.events
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다. 다음 항목이 정의되어 있으며, 별도로 명시하지 않는 한 값이 변경되면 파일 수정 이벤트가 발생한다.
>
>
> 이 파일의 모든 필드는 계층적이다. 즉, 서브트리 아래에서 발생한 이벤트도 이 파일 수정 이벤트를 유발할 수 있다. cgroup 레벨에서의 로컬 이벤트에 대해서는 memory.events.local을 참고하라.
>
>
> > low
> > 해당 cgroup의 사용량이 low 경계 이내임에도 불구하고, 높은 메모리 압력으로 인해 리클레임된 횟수. 보통 low 경계가 오버커밋되었음을 의미한다.
> >
> > high
> > 메모리 사용량이 high 경계를 초과하여, 해당 cgroup 프로세스가 스로틀되고 직접 메모리 리클레임에 참여하도록 강제된 횟수. 글로벌 메모리 압력이 아니라 high 리밋에 의해 메모리가 제한된 cgroup의 경우, 이 이벤트가 자주 발생할 수 있다.
> >
> > max
> > cgroup의 메모리 사용량이 max 경계를 초과할 뻔한 횟수. 직접 리클레임이 사용량을 낮추는 데 실패하면, cgroup은 OOM 상태로 들어간다.
> >
> > oom
> > cgroup의 메모리 사용량이 리밋에 도달했고, 할당이 실패 직전이었던 횟수.
> >
> >
> > 이 이벤트는, OOM killer가 옵션으로 고려되지 않는 경우(예: 고차원(high-order) 할당 실패나, 호출자가 재시도를 원하지 않는다고 지정한 경우 등)에는 발생하지 않는다.
> >
> > oom_kill
> > 어떤 종류의 OOM killer에 의해 이 cgroup에 속한 프로세스가 kill된 횟수.
> >
> > oom_group_kill
> > 그룹 OOM이 발생한 횟수.
>
> memory.events.local
> memory.events와 유사하지만, 파일 내 필드는 cgroup에 국한된(비계층적) 로컬 값만을 나타낸다. 이 파일에 대해 발생하는 파일 수정 이벤트는 오직 로컬 이벤트만을 반영한다.
>
> memory.stat
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다.
>
>
> 이 파일은 cgroup의 메모리 footprint를 여러 유형의 메모리, 유형별 세부 항목, 메모리 관리 시스템의 상태 및 과거 이벤트에 관련된 기타 정보로 분해하여 보여준다.
>
>
> 모든 메모리 양은 바이트 단위이다.
>
>
> 항목들은 사람이 읽기 좋도록 정렬되어 있으며, 새 항목이 중간에 추가될 수 있다. 항목 위치가 고정되어 있다고 가정해서는 안 되며, 꼭 키 이름을 사용해 값을 조회해야 한다.
>
>
> per-node 카운터가 없거나(memeory.numa_stat에 표시되지 않는 항목), per-node 카운터를 사용하지 않는 항목에는 ‘npn’(non-per-node) 태그를 사용해 memory.numa_stat에 나타나지 않음을 표시한다.
>
>
> > anon
> > `brk()`, `sbrk()`, mmap(MAP_ANONYMOUS) 등 익명 매핑에 사용된 메모리 양. 일부 커널 설정에서는, 큰 할당(예: THP) 전체가 모두 매핑된 상태가 아니더라도, 그 중 일부만 매핑이 해제되었을 때 전체를 하나로 계정할 수 있다.
> >
> > file
> > tmpfs와 공유 메모리를 포함하여, 파일시스템 데이터를 캐시하는 데 사용된 메모리 양.
> >
> > kernel (npn)
> > (kernel_stack, pagetables, percpu, vmalloc, slab)을 포함한 모든 커널 메모리 양.
> >
> > kernel_stack
> > 커널 스택에 할당된 메모리 양.
> >
> > pagetables
> > 페이지 테이블에 할당된 메모리 양.
> >
> > sec_pagetables
> > 보조 페이지 테이블(secondary page tables)에 할당된 메모리 양. 현재는 x86과 arm64에서 KVM mmu 할당 및 IOMMU 페이지 테이블이 포함된다.
> >
> > percpu (npn)
> > per-CPU 커널 데이터 구조를 저장하는 데 사용된 메모리 양.
> >
> > sock (npn)
> > 네트워크 전송 버퍼에 사용된 메모리 양.
> >
> > vmalloc (npn)
> > vmap 기반 메모리에 사용된 메모리 양.
> >
> > shmem
> > tmpfs, shm 세그먼트, 공유 익명 mmap() 등과 같이, swap 백업된 파일시스템 데이터를 캐시하는 데 사용된 메모리 양.
> >
> > zswap
> > zswap 압축 backend가 소비하는 메모리 양.
> >
> > zswapped
> > zswap에 swap-out된 애플리케이션 메모리 양.
> >
> > file_mapped
> > mmap()으로 매핑된 파일시스템 캐시 데이터의 양. 일부 커널 설정에서는, 큰 할당(예: THP) 전체가 모두 매핑된 상태가 아니더라도, 그 중 일부만 매핑이 해제된 경우 해당 THP 전체를 하나로 계정할 수 있다.
> >
> > file_dirty
> > 수정되었지만 아직 디스크에 기록되지 않은 파일시스템 캐시 데이터 양.
> >
> > file_writeback
> > 수정되었으며 현재 디스크에 기록 중인 파일시스템 캐시 데이터 양.
> >
> > swapcached
> > 메모리에 캐시된 swap 양. swap 캐시는 메모리 사용량과 swap 사용량 모두에 대해 계정된다.
> >
> > anon_thp
> > 투명 hugepage(THP)에 의해 백업되는 익명 매핑에 사용된 메모리 양.
> >
> > file_thp
> > 투명 hugepage로 백업되는 파일시스템 캐시 데이터 양.
> >
> > shmem_thp
> > shm, tmpfs, 공유 익명 mmap() 등, 투명 hugepage로 백업되는 메모리 양.
> >
> > inactive_anon, active_anon, inactive_file, active_file, unevictable
> > 페이지 리클레임 알고리즘에서 사용하는 내부 메모리 관리 리스트에 올라 있는 swap 백업 및 파일시스템 백업 메모리 양.
> >
> >
> > 이러한 항목들은 내부 리스트 상태를 나타내므로(예: shmem 페이지는 anon 메모리 관리 리스트에 올라간다), inactive_foo + active_foo 합이 항상 foo 카운터 값과 같지는 않다. foo 카운터는 유형 기반인 반면, inactive/active 리스트는 리스트 상태 기반이기 때문이다.
> >
> > slab_reclaimable
> > dentries와 inodes처럼 리클레임 가능한 “slab” 메모리 양.
> >
> > slab_unreclaimable
> > 메모리 압력에서도 리클레임될 수 없는 “slab” 메모리 양.
> >
> > slab (npn)
> > 커널 내 데이터 구조를 저장하는 데 사용되는 메모리 양.
> >
> > workingset_refault_anon
> > 이전에 리클레임되었던 익명 페이지의 refault 횟수.
> >
> > workingset_refault_file
> > 이전에 리클레임되었던 파일 페이지의 refault 횟수.
> >
> > workingset_activate_anon
> > refault 후 즉시 활성화된 익명 페이지의 수.
> >
> > workingset_activate_file
> > refault 후 즉시 활성화된 파일 페이지의 수.
> >
> > workingset_restore_anon
> > 리클레임되기 전에 active working set으로 감지되었다가 리스토어된 익명 페이지 수.
> >
> > workingset_restore_file
> > 리클레임되기 전에 active working set으로 감지되었다가 리스토어된 파일 페이지 수.
> >
> > workingset_nodereclaim
> > shadow 노드가 리클레임된 횟수.
> >
> > pswpin (npn)
> > 메모리에 swap-in된 페이지 수.
> >
> > pswpout (npn)
> > 메모리에서 swap-out된 페이지 수.
> >
> > pgscan (npn)
> > 비활성 LRU 리스트에서 스캔한 페이지 수.
> >
> > pgsteal (npn)
> > 리클레임된 페이지 수.
> >
> > pgscan_kswapd (npn)
> > kswapd가 비활성 LRU 리스트에서 스캔한 페이지 수.
> >
> > pgscan_direct (npn)
> > 직접(direct) 비활성 LRU 리스트에서 스캔한 페이지 수.
> >
> > pgscan_khugepaged (npn)
> > khugepaged가 비활성 LRU 리스트에서 스캔한 페이지 수.
> >
> > pgscan_proactive (npn)
> > 사전(proactive) 비활성 LRU 리스트에서 스캔한 페이지 수.
> >
> > pgsteal_kswapd (npn)
> > kswapd에 의해 리클레임된 페이지 수.
> >
> > pgsteal_direct (npn)
> > 직접(direct) 리클레임된 페이지 수.
> >
> > pgsteal_khugepaged (npn)
> > khugepaged에 의해 리클레임된 페이지 수.
> >
> > pgsteal_proactive (npn)
> > 사전(proactive) 리클레임된 페이지 수.
> >
> > pgfault (npn)
> > 전체 페이지 폴트 발생 횟수.
> >
> > pgmajfault (npn)
> > major 페이지 폴트 발생 횟수.
> >
> > pgrefill (npn)
> > 활성 LRU 리스트에서 스캔된 페이지 수.
> >
> > pgactivate (npn)
> > 활성 LRU 리스트로 이동된 페이지 수.
> >
> > pgdeactivate (npn)
> > 비활성 LRU 리스트로 이동된 페이지 수.
> >
> > pglazyfree (npn)
> > 메모리 압력하에서 해제될(lazy free) 예정인 페이지 수.
> >
> > pglazyfreed (npn)
> > 리클레임된 lazyfree 페이지 수.
> >
> > swpin_zero
> > swap-out 시 내용이 0으로 판별되어 I/O가 최적화(생략)된 페이지가, swap-in 시 0으로 채워진 횟수.
> >
> > swpout_zero
> > 내용이 0으로 판별되어 I/O 없이 swap-out된 0-필드 페이지 수.
> >
> > zswpin
> > zswap에서 메모리로 이동된 페이지 수.
> >
> > zswpout
> > 메모리에서 zswap으로 이동된 페이지 수.
> >
> > zswpwb
> > zswap에서 swap 디스크로 기록(write-back)된 페이지 수.
> >
> > thp_fault_alloc (npn)
> > 페이지 폴트를 처리하기 위해 할당된 투명 hugepage 수. CONFIG_TRANSPARENT_HUGEPAGE가 설정되지 않은 경우 이 카운터는 나타나지 않는다.
> >
> > thp_collapse_alloc (npn)
> > 기존 페이지 범위를 collapse하기 위해 할당된 투명 hugepage 수. CONFIG_TRANSPARENT_HUGEPAGE가 설정되지 않은 경우 이 카운터는 나타나지 않는다.
> >
> > thp_swpout (npn)
> > 분할되지 않은 상태에서 한 번에 swap-out된 투명 hugepage 수.
> >
> > thp_swpout_fallback (npn)
> > swap-out 전에 분할된 투명 hugepage 수. 보통 hugepage 전체를 담을 연속된 swap 공간을 할당하지 못했을 때 발생한다.
> >
> > numa_pages_migrated (npn)
> > NUMA 밸런싱에 의해 마이그레이션된 페이지 수.
> >
> > numa_pte_updates (npn)
> > NUMA 힌팅 폴트를 유도하기 위해 NUMA 밸런싱에 의해 페이지 테이블 엔트리가 수정된 페이지 수.
> >
> > numa_hint_faults (npn)
> > NUMA 힌팅 폴트 횟수.
> >
> > pgdemote_kswapd
> > kswapd에 의해 demote된 페이지 수.
> >
> > pgdemote_direct
> > direct demote된 페이지 수.
> >
> > pgdemote_khugepaged
> > khugepaged에 의해 demote된 페이지 수.
> >
> > pgdemote_proactive
> > 사전(proactive) demote된 페이지 수.
> >
> > hugetlb
> > hugetlb 페이지에 사용된 메모리 양. 이 메트릭은, 메모리 컨트롤러에서 hugetlb 사용량을 memory.current에 계정하는 경우(즉, cgroup이 memory_hugetlb_accounting 옵션으로 마운트된 경우)에만 나타난다.
>
> memory.numa_stat
> 비루트 cgroup에 존재하는 읽기 전용 중첩 키-값 파일이다.
>
>
> cgroup의 메모리 footprint를 per-node 단위로 분해하여, 여러 메모리 유형, 유형별 세부 항목, 메모리 관리 시스템 상태 등에 대한 정보를 제공한다.
>
>
> 이는 memcg 내에서의 NUMA 로컬리티 정보를 제공하는 데 유용하다. memcg는 페이지가 어떤 물리 노드에서든 할당될 수 있으므로, 이 정보를 워크로드의 CPU 할당 정보와 결합하여, 애플리케이션 성능을 평가하는 등의 용도로 사용할 수 있다.
>
>
> 모든 메모리 양은 바이트 단위이다.
>
>
> memory.numa_stat의 출력 형식은 다음과 같다.
>
>
>
> type N0=<노드 0에서의 바이트 수> N1=<노드 1에서의 바이트 수> ...
>
>
> 항목들은 사람이 읽기 좋도록 정렬되어 있으며, 중간에 새 항목이 추가될 수 있다. 항목 위치가 고정되어 있다고 가정해서는 안 되며, 반드시 키 이름을 사용해 값을 조회해야 한다.
>
>
> 각 항목은 memory.stat 항목을 참조할 수 있다.
>
> memory.swap.current
> 비루트 cgroup에 존재하는 읽기 전용 단일 값 파일이다.
>
>
> 해당 cgroup과 그 자손에서 현재 사용 중인 swap의 총량이다.
>
> memory.swap.high
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> swap 사용량 스로틀 리밋이다. cgroup의 swap 사용량이 이 리밋을 초과하면, 이후 모든 할당이 스로틀되어 사용자 공간에서 커스텀 OOM 처리를 구현할 수 있도록 한다.
>
>
> 이 리밋은 해당 cgroup을 위한 ‘돌이킬 수 없는 지점(point of no return)’을 표시한다. 정상 동작 동안 워크로드가 수행하는 swap 양을 제어하려는 용도가 아니다. memory.swap.max와 비교해 보면, memory.swap.max는 특정 양 이상으로 swap하는 것을 금지하지만, 다른 메모리를 리클레임할 수 있는 한 cgroup의 동작을 방해하지 않는다.
>
>
> 정상적인 워크로드는 이 리밋에 도달하지 않는 것이 기대된다.
>
> memory.swap.peak
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다.
>
>
> cgroup과 그 자손에 대해, cgroup 생성 시점 또는 이 파일 디스크립터의 가장 최근 리셋 이후 기록된 최대 swap 사용량이다.
>
>
> 비어 있지 않은 어떤 문자열이든 이 파일에 쓰면, 해당 파일 디스크립터를 통한 이후 읽기에 대해, 현재 swap 사용량으로 피크 값이 리셋된다.
>
> memory.swap.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> swap 사용량 하드 리밋이다. cgroup의 swap 사용량이 이 리밋에 도달하면, 해당 cgroup의 익명 메모리는 더 이상 swap-out되지 않는다.
>
> memory.swap.events
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다. 다음 항목들이 정의되어 있으며, 별도로 명시하지 않는 한 값이 변경되면 파일 수정 이벤트가 발생한다.
>
>
> > high
> > cgroup의 swap 사용량이 high 경계를 초과한 횟수.
> >
> > max
> > cgroup의 swap 사용량이 max 경계를 초과할 뻔해 swap 할당이 실패한 횟수.
> >
> > fail
> > 시스템 전체 swap 부족 또는 max 리밋 때문에 swap 할당이 실패한 횟수.
>
>
> swap 사용량을 현재 사용량보다 낮게 줄이면, 기존 swap 엔트리는 점진적으로 리클레임되며, swap 사용량은 상당 시간 동안 리밋보다 높게 유지될 수 있다. 이는 워크로드와 메모리 관리에 미치는 영향을 줄이기 위함이다.
>
> memory.zswap.current
> 비루트 cgroup에 존재하는 읽기 전용 단일 값 파일이다.
>
>
> zswap 압축 backend가 소비하는 메모리 총량이다.
>
> memory.zswap.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> zswap 사용량 하드 리밋이다. cgroup의 zswap 풀 사용량이 이 리밋에 도달하면, 기존 엔트리가 다시 메모리로 읽혀오거나 디스크로 기록될 때까지 새로운 store를 수락하지 않는다.
>
> memory.zswap.writeback
> 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “1”이다. 이 설정은 계층적이다. 즉, 상위 계층에서 writeback을 비활성화하면 자식 cgroup에서도 암묵적으로 비활성화된다.
>
>
> 이 값을 0으로 설정하면, swap 디바이스로의 모든 swap 동작 시도가 비활성화된다. 여기에는 zswap writeback과, zswap store 실패로 인한 일반 swap-out이 모두 포함된다. zswap store 실패가 반복되는 경우(예: 페이지가 압축 불가능할 때), writeback을 비활성화하면 동일 페이지가 반복적으로 거부되면서 리클레임 효율이 떨어질 수 있다.
>
>
> 이 설정은 memory.swap.max를 0으로 설정하는 것과 미묘하게 다르다. memory.swap.max=0은 swap 디바이스로 쓰는 것을 완전히 막지만, 이 설정은 zswap 풀에 페이지를 쓰는 것은 허용한다. zswap이 비활성화된 경우에는 이 설정이 영향을 미치지 않으며, memory.swap.max가 0으로 설정되지 않는 한 일반 swap은 허용된다.
>
> memory.pressure
> 읽기 전용 중첩 키-값 파일이다.
>
>
> 메모리에 대한 pressure stall 정보를 제공한다. 자세한 내용은 [Documentation/accounting/psi.rst](https://docs.kernel.org/accounting/psi.html#psi)를 참고하라.
#### 사용 지침[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#usage-guidelines "Permalink to this heading")
“memory.high”는 메인 메커니즘으로 메모리 사용량을 제어한다. high 리밋의 합이 사용 가능한 메모리보다 커지도록 오버커밋하고, 글로벌 메모리 압력에 따라 메모리가 사용량에 비례하여 분배되도록 하는 전략도 유효하다.
high 리밋을 초과해도 OOM killer가 트리거되지 않고, offending cgroup이 스로틀되므로, 관리 에이전트는 이를 모니터링하고 적절한 조치(메모리 추가 할당, 워크로드 종료 등)를 취할 충분한 기회를 갖는다.
어떤 cgroup에 메모리가 충분한지 판단하는 것은 단순하지 않다. 메모리 사용량만으로는 워크로드가 더 많은 메모리로부터 이득을 얻을 수 있는지 여부를 알 수 없기 때문이다. 예를 들어, 네트워크에서 데이터를 받아 파일에 기록하는 워크로드는 사용 가능한 모든 메모리를 사용할 수 있지만, 소량의 메모리만으로도 동일 성능을 낼 수 있다. 워크로드가 메모리 부족으로 얼마나 영향을 받고 있는지를 나타내는 메모리 압력 지표가 있어야, 워크로드에 더 많은 메모리가 필요 여부를 판단할 수 있다. 하지만, 메모리 압력을 모니터링하는 메커니즘은 아직 구현되지 않았다.
#### 메모리 소유권[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#memory-ownership "Permalink to this heading")
메모리 영역은 이를 생성(instantiation)한 cgroup에 charge되며, 해당 영역이 해제될 때까지 해당 cgroup에 charge된 상태로 남는다. 프로세스를 다른 cgroup으로 마이그레이션하더라도, 이전 cgroup에 있을 때 해당 프로세스가 생성한 메모리 사용량은 새 cgroup으로 이동하지 않는다.
메모리 영역은 여러 cgroup에 속한 프로세스에 의해 사용될 수 있다. 어느 cgroup에 charge될지는 비결정적이지만, 시간이 지남에 따라, 해당 영역은 높은 리클레임 압력 없이 유지될 수 있을 만큼 충분한 메모리 허용량을 가진 cgroup에 속하게 될 가능성이 크다.
어떤 cgroup이 다른 cgroup에서 반복적으로 접근될 것이 기대되는 상당한 양의 메모리를 수용하고 있다면, 영향을 받는 파일에 대해 POSIX_FADV_DONTNEED를 사용해 해당 메모리 소유권을 반환하여 메모리 소유권을 더 정확하게 유지하는 것이 바람직할 수 있다.
### IO[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#io "Permalink to this heading")
“io” 컨트롤러는 IO 자원 분배를 제어한다. 이 컨트롤러는 가중치 기반 및 절대 대역폭/IOPS 리밋 분배를 모두 구현하지만, 가중치 기반 분배는 cfq-iosched 사용 시에만 가능하며, 어느 방식도 blk-mq 디바이스에는 사용되지 않는다.
#### IO 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#io-interface-files "Permalink to this heading")
> io.stat
> 읽기 전용 중첩 키-값 파일이다.
>
>
> 각 줄은 $MAJ:$MIN 디바이스 번호를 키로 하며, 순서는 보장되지 않는다. 다음 중첩 키들이 정의되어 있다.
>
>
> > rbytes 읽은 바이트 수
> > wbytes 쓴 바이트 수
> > rios 읽기 IO 개수
> > wios 쓰기 IO 개수
> > dbytes discard된 바이트 수
> > dios discard IO 개수
>
>
> 예시 출력은 다음과 같다.
>
>
>
> 8:16 rbytes=1459200 wbytes=314773504 rios=192 wios=353 dbytes=0 dios=0
> 8:0 rbytes=90430464 wbytes=299008000 rios=8950 wios=1252 dbytes=50331648 dios=3021
>
> io.cost.qos
> 루트 cgroup에만 존재하는 읽기/쓰기 가능한 중첩 키-값 파일이다.
>
>
> 이 파일은 IO cost 모델 기반 컨트롤러(CONFIG_BLK_CGROUP_IOCOST)의 QoS(Quality of Service)를 설정하며, 현재는 “io.weight” 비례 제어만을 구현한다. 각 줄은 $MAJ:$MIN 디바이스 번호를 키로 하며, 순서는 보장되지 않는다. 특정 디바이스에 대응하는 줄은 해당 디바이스에 대해 “io.cost.qos” 또는 “io.cost.model”에 첫 쓰기가 발생하는 시점에 생성된다. 다음 중첩 키들이 정의되어 있다.
>
>
> > enable 가중치 기반 제어 활성화 여부
> > ctrl “auto” 또는 “user”
> > rpct 읽기 지연 시간 퍼센타일 [0, 100]
> > rlat 읽기 지연 시간 임계값
> > wpct 쓰기 지연 시간 퍼센타일 [0, 100]
> > wlat 쓰기 지연 시간 임계값
> > min 최소 스케일 비율 [1, 10000]
> > max 최대 스케일 비율 [1, 10000]
>
>
> 컨트롤러는 기본적으로 비활성화되어 있으며, “enable”을 1로 설정하면 활성화된다. “rpct”와 “wpct”는 기본값 0이며, 이 경우 컨트롤러는 내부적인 디바이스 포화(saturation) 상태에 따라 전체 IO 속도를 “min”과 “max” 사이에서 조절한다.
>
>
> 더 나은 제어 품질이 필요하다면, 지연 시간 QoS 파라미터를 설정할 수 있다. 예를 들어,
>
>
>
> 8:16 enable=1 ctrl=auto rpct=95.00 rlat=75000 wpct=95.00 wlat=150000 min=50.00 max=150.0
>
>
> 위는 sdb 디바이스에 대해 컨트롤러가 활성화되어 있고, 읽기 완료 지연 시간의 95번째 퍼센타일이 75ms를 넘거나 쓰기 150ms를 넘으면 디바이스를 포화 상태로 간주하며, 전체 IO 발행 속도를 50%~150% 범위에서 조절함을 의미한다.
>
>
> 포화 지점을 낮출수록 지연 시간 QoS는 좋아지지만, 총 대역폭은 줄어든다. “min”과 “max” 사이의 허용 조절 범위가 좁을수록, cost 모델에 더 잘 부합(conform)하는 IO 동작을 보인다. IO 발행 기본 속도가 100% 기준에서 크게 벗어나 있을 수 있으므로, “min”과 “max”를 무턱대고 설정하면, 디바이스 용량이나 제어 품질이 크게 손실될 수 있다. “min”과 “max”는, 예를 들어, 한동안 라인 속도로 쓰기를 받다가 수 초 동안 완전히 멈추는 SSD처럼, 일시적인 동작 변화 폭이 큰 디바이스를 조절하는 데 유용하다.
>
>
> “ctrl”이 “auto”인 경우, 파라미터는 커널에 의해 제어되며 자동으로 변경될 수 있다. “ctrl”을 “user”로 설정하거나 퍼센타일/지연 시간 파라미터 중 하나라도 설정하면, “user” 모드가 되며 자동 변경이 비활성화된다. 다시 자동 모드로 돌아가려면 “ctrl”을 “auto”로 설정하면 된다.
>
> io.cost.model
> 루트 cgroup에만 존재하는 읽기/쓰기 가능한 중첩 키-값 파일이다.
>
>
> 이 파일은 IO cost 모델 기반 컨트롤러(CONFIG_BLK_CGROUP_IOCOST)의 cost 모델을 설정하며, 현재는 “io.weight” 비례 제어만을 구현한다. 각 줄은 $MAJ:$MIN 디바이스 번호를 키로 하며, 순서는 보장되지 않는다. 특정 디바이스에 대한 줄은 해당 디바이스에 대해 “io.cost.qos” 또는 “io.cost.model”에 첫 쓰기가 발생할 때 생성된다. 다음 중첩 키들이 정의되어 있다.
>
>
> > ctrl “auto” 또는 “user”
> > model 사용 중인 cost 모델 – “linear”
>
>
> “ctrl”이 “auto”인 경우, 커널은 모든 파라미터를 동적으로 변경할 수 있다. “ctrl”을 “user”로 설정하거나 다른 파라미터를 쓰면, “ctrl”은 “user”가 되고 자동 변경이 비활성화된다.
>
>
> “model”이 “linear”일 경우, 다음 모델 파라미터들이 정의된다.
>
>
> > [r|w]bps 최대 순차 IO 처리량
> > [r|w]seqiops 최대 4k 순차 IO/s
> > [r|w]randiops 최대 4k 랜덤 IO/s
>
>
> 위 정보로부터, 내장된 선형 모델은 순차 및 랜덤 IO의 기본 cost와 IO 크기를 위한 cost 계수(coefficient)를 결정한다. 단순하지만, 대부분의 일반적인 디바이스 클래스에서 수용 가능한 수준의 모델링을 제공한다.
>
>
> IO cost 모델이 절대적 의미에서 정확할 필요는 없으며, 실제 디바이스 동작에 맞춰 동적으로 스케일 조정된다.
>
>
> 필요하다면 tools/cgroup/iocost_coef_gen.py를 사용해 디바이스별 계수를 생성할 수 있다.
>
> io.weight
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 플랫 키-값 파일이며, 기본값은 “default 100”이다.
>
>
> 첫 줄은 특정 오버라이드가 없는 디바이스에 적용할 기본 weight이다. 나머지 줄은 $MAJ:$MIN 디바이스 번호를 키로 하는 오버라이드이며, 순서는 보장되지 않는다. weight는 [1, 10000] 범위에 있으며, cgroup이 형제들과 비교해 사용할 수 있는 IO 시간의 상대적 양을 지정한다.
>
>
> 기본 weight는 “default $WEIGHT” 또는 단순히 “$WEIGHT”를 쓰는 방식으로 갱신할 수 있다. “$MAJ:$MIN $WEIGHT”를 써서 특정 디바이스에 대한 오버라이드를 설정하고, “$MAJ:$MIN default”를 써서 해당 오버라이드를 해제할 수 있다.
>
>
> 예시 출력은 다음과 같다.
>
>
>
> default 100
> 8:16 200
> 8:0 50
>
> io.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 중첩 키-값 파일이다.
>
>
> BPS 및 IOPS 기반 IO 리밋을 정의한다. 각 줄은 $MAJ:$MIN 디바이스 번호를 키로 하며, 순서는 보장되지 않는다. 다음 중첩 키들이 정의되어 있다.
>
>
> > rbps 초당 읽기 바이트 수 최대값
> > wbps 초당 쓰기 바이트 수 최대값
> > riops 초당 읽기 IO 연산 최대 개수
> > wiops 초당 쓰기 IO 연산 최대 개수
>
>
> 쓰기 시, 임의 개수의 키-값 쌍을 임의 순서로 지정할 수 있다. 값으로 “max”를 지정하면 특정 리밋을 제거한다. 동일 키를 여러 번 지정하면 결과는 정의되지 않는다.
>
>
> BPS와 IOPS는 IO 방향별로 측정되며, 리밋에 도달하면 IO는 지연된다. 일시적인 버스트는 허용된다.
>
>
> 예를 들어, 8:16 디바이스에 대해 읽기 리밋을 2M BPS, 쓰기 리밋을 120 IOPS로 설정하려면:
>
>
>
> echo "8:16 rbps=2097152 wiops=120" > io.max
>
>
> 읽으면 다음과 같이 표시된다.
>
>
>
> 8:16 rbps=2097152 wbps=max riops=max wiops=120
>
>
> 이후 쓰기 IOPS 리밋을 제거하려면 다음과 같이 쓴다.
>
>
>
> echo "8:16 wiops=max" > io.max
>
>
> 다시 읽으면 다음과 같이 표시된다.
>
>
>
> 8:16 rbps=2097152 wbps=max riops=max wiops=max
>
> io.pressure
> 읽기 전용 중첩 키-값 파일이다.
>
>
> IO에 대한 pressure stall 정보를 제공한다. 자세한 내용은 [Documentation/accounting/psi.rst](https://docs.kernel.org/accounting/psi.html#psi)를 참고하라.
#### 쓰기백(Writeback)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#writeback "Permalink to this heading")
페이지 캐시는 버퍼링된 쓰기 및 공유 mmap을 통해 dirty 상태가 되고, 백엔드 파일시스템으로의 비동기 쓰기 작업은 writeback 메커니즘이 수행한다. writeback은 메모리와 IO 도메인 사이에 위치하며, dirty 메모리 비율을 조절하기 위해 dirtying 속도와 write IO를 균형 있게 관리한다.
io 컨트롤러는 메모리 컨트롤러와 함께 페이지 캐시 writeback IO를 제어한다. 메모리 컨트롤러는 dirty 메모리 비율을 계산 및 유지하는 메모리 도메인을 정의하고, io 컨트롤러는 해당 메모리 도메인의 dirty 페이지를 writeout할 IO 도메인을 정의한다. 시스템 전체와 cgroup별 dirty 메모리 상태가 모두 고려되며, 둘 중 더 엄격한 쪽이 적용된다.
cgroup writeback은 하부 파일시스템의 명시적 지원을 필요로 한다. 현재 ext2, ext4, btrfs, f2fs, xfs에서 cgroup writeback이 구현되어 있다. 다른 파일시스템에서는 모든 writeback IO가 루트 cgroup에 귀속된다.
메모리 관리와 writeback 관리 간에는 본질적인 차이가 있으며, 이는 cgroup 소유권 추적 방식에 영향을 미친다. 메모리는 페이지 단위로 추적되는 반면, writeback은 inode 단위로 추적된다. writeback 관점에서, 하나의 inode는 특정 cgroup에 할당되며, 해당 inode에서 dirty 페이지를 writeout하는 모든 IO 요청은 해당 cgroup에 귀속된다.
메모리 소유권은 페이지 단위로 추적되므로, inode와 연관된 cgroup과는 다른 cgroup에 속한 페이지가 존재할 수 있다. 이를 foreign 페이지라고 부른다. writeback은 foreign 페이지를 지속적으로 추적하며, 특정 foreign cgroup이 일정 기간 동안 majority(대부분)를 차지하면 해당 inode의 소유권을 그 cgroup으로 전환한다.
이 모델은, 특정 inode가 대부분 하나의 cgroup에 의해 dirty되며, 시간이 지나면서 주요 쓰기 주체가 바뀌는 경우에도 잘 동작한다. 하지만 여러 cgroup이 동시에 하나의 inode에 쓰는 사용 사례에는 적합하지 않다. 이런 상황에서는 상당 부분의 IO가 잘못된 cgroup에 귀속될 가능성이 크다. 메모리 컨트롤러가 페이지 소유권을 최초 사용 시점에만 설정하고 페이지가 해제될 때까지 갱신하지 않기 때문에, writeback이 페이지 소유권을 엄격하게 따른다 하더라도, 여러 cgroup이 겹치는 영역을 dirty하는 경우 기대와 다른 동작을 보이게 된다. 이러한 사용 패턴은 피하는 것이 권장된다.
writeback 동작에 영향을 주는 sysctl 튜닝은, cgroup writeback에 대해 다음과 같이 적용된다.
> vm.dirty_background_ratio, vm.dirty_ratio
> 이 비율들은, 메모리 컨트롤러 및 시스템 전체 clean 메모리가 부과한 리밋으로 가용 메모리 양이 제한된 상태에서, cgroup writeback에도 동일하게 적용된다.
>
> vm.dirty_background_bytes, vm.dirty_bytes
> cgroup writeback의 경우, 이 값은 전체 가용 메모리에 대한 비율로 환산되어, vm.dirty[_background]_ratio와 동일한 방식으로 적용된다.
#### IO 지연(IO Latency)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#io-latency "Permalink to this heading")
이 컨트롤러는 IO 워크로드 보호를 위한 cgroup v2 컨트롤러이다. 특정 그룹에 지연 시간 목표(latency target)를 지정하면, 측정된 평균 지연 시간이 해당 목표를 초과할 경우, 이 보호 대상 워크로드보다 더 낮은 지연 시간 목표를 가진 피어 그룹들에 대해 스로틀이 적용된다.
리밋은 계층 내 피어 수준에서만 적용된다. 즉, 아래 그림에서, A, B, C 그룹만 서로에게 영향을 미치며, D와 F 그룹은 서로에게만 영향을 미친다. G 그룹은 다른 그룹에 영향을 주지 않는다.
```text
[root]
/ | \
A B C
/ \ |
D F G
```
따라서 가장 좋은 설정 방법은 A, B, C 그룹에서 io.latency를 설정하는 것이다. 일반적으로 디바이스가 제공할 수 있는 지연 시간보다 낮은 값을 설정하지 않는 것이 좋다. 워크로드에 가장 적합한 값을 찾기 위해 실험을 수행해야 한다. 먼저 디바이스의 예상 지연 시간보다 높은 값으로 시작하고, 워크로드 그룹의 io.stat에 나타나는 avg_lat 값을 모니터링해 일반 동작에서의 지연 시간을 파악한다. 실제 설정 값은 io.stat의 avg_lat 값보다 10–15% 정도 높은 값으로 설정하는 것이 좋다.
#### IO 지연 스로틀 동작 방식[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#how-io-latency-throttling-works "Permalink to this heading")
io.latency는 작업 보존적(work-conserving)이다. 즉, 모든 그룹이 자신의 지연 시간 목표를 만족하는 동안에는 컨트롤러가 아무 작업도 하지 않는다. 어느 한 그룹이 목표를 달성하지 못하기 시작하면, 그 그룹보다 더 높은 지연 시간 목표를 가진 피어 그룹들에 대해 스로틀이 적용되기 시작한다. 스로틀은 두 가지 형태로 이루어진다.
* 큐 깊이 스로틀(queue depth throttling): 그룹이 가질 수 있는 미해결 outstanding IO 개수에 제한을 둔다. 이 제한은 비교적 빠르게 강화되며, 무제한 상태에서 시작해, 최대 한 번에 1개의 IO만 허용하는 수준까지 내려갈 수 있다.
* 인위적 지연 유도(artificial delay induction): 상위 우선순위 그룹에 부정적인 영향을 줄 수 있어 스로틀할 수 없는 유형의 IO도 있다. 여기에는 swapping과 메타데이터 IO가 포함된다. 이런 IO는 정상적으로 허용되지만, 그 비용은 원래 요청한 그룹에 ‘청구’된다. 해당 그룹이 스로틀 대상이면, io.stat의 use_delay와 delay 필드가 증가하는 것을 볼 수 있다. delay 값은 이 그룹에서 실행되는 프로세스에 추가되는 마이크로초 지연 시간이다. swapping이나 메타데이터 IO가 대량으로 발생하는 경우 이 숫자가 매우 커질 수 있으므로, 개별 지연 이벤트는 최대 1초로 제한된다.
피해를 입은 그룹(victimized group)이 다시 지연 시간 목표를 만족하기 시작하면, 이전에 스로틀되었던 피어 그룹들에 대한 스로틀이 점진적으로 완화된다. 피해 그룹이 IO를 완전히 중단해도, 전역 카운터는 적절한 시점에 스로틀을 해제한다.
#### IO 지연 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#io-latency-interface-files "Permalink to this heading")
> io.latency
> 다른 컨트롤러와 유사한 형식을 따른다.
>
>
> > “MAJOR:MINOR target=<마이크로초 단위 목표 지연 시간>”
>
> io.stat
> 컨트롤러가 활성화된 경우, 표준 항목 외에 io.stat에 다음과 같은 추가 통계가 나타난다.
>
>
> > depth
> > 현재 그룹의 큐 깊이(queue depth).
> >
> > avg_lat
> > 1/exp 감쇠율을 가진 지수 이동 평균(exponential moving average)이며, 샘플링 간격에 의해 상한이 정해진다. 감쇠율에 해당하는 인터벌은 io.stat의 win 값과, 그 값에 대응하는 샘플 개수를 곱한 값으로 계산할 수 있다.
> >
> > win
> > 밀리초 단위 샘플 윈도 크기이다. 이는 평가 이벤트 사이의 최소 시간 간격이다. 윈도는 IO 활동이 있을 때만 진행된다. 유휴 기간은 마지막 윈도를 연장한다.
#### IO 우선순위(IO Priority)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#io-priority "Permalink to this heading")
IO 우선순위 cgroup 정책은 단일 속성 io.prio.class에 의해 제어된다. 이 속성은 다음 값들을 허용한다.
> no-change
> IO 우선순위 클래스를 변경하지 않는다.
>
> promote-to-rt
> RT가 아닌 IO 우선순위 클래스를 가진 요청에 대해, 우선순위 클래스를 RT로 변경하고 우선순위 레벨을 4로 설정한다. 이미 RT 클래스를 가진 요청의 IO 우선순위는 변경하지 않는다.
>
> restrict-to-be
> IO 우선순위 클래스가 없거나 RT인 요청에 대해, 우선순위 클래스를 BE로 변경하고 우선순위 레벨을 0으로 설정한다. 이미 IDLE 클래스를 가진 요청의 IO 우선순위 클래스는 변경하지 않는다.
>
> idle
> 모든 요청의 IO 우선순위 클래스를 가장 낮은 IO 우선순위 클래스인 IDLE로 변경한다.
>
> none-to-rt
> 더 이상 사용되지 않는다. promote-to-rt의 별칭(alias)일 뿐이다.
IO 우선순위 정책에 대응하는 숫자 값은 다음과 같다.
no-change 0
promote-to-rt 1
restrict-to-be 2
idle 3
각 IO 우선순위 클래스에 대응하는 숫자 값은 다음과 같다.
IOPRIO_CLASS_NONE 0
IOPRIO_CLASS_RT (real-time) 1
IOPRIO_CLASS_BE (best effort) 2
IOPRIO_CLASS_IDLE 3
요청의 IO 우선순위 클래스를 설정하는 알고리즘은 다음과 같다.
* IO 우선순위 클래스 정책이 promote-to-rt인 경우, 요청의 IO 우선순위 클래스를 IOPRIO_CLASS_RT로 변경하고 우선순위 레벨을 4로 설정한다.
* IO 우선순위 클래스 정책이 promote-to-rt가 아닌 경우, IO 우선순위 클래스 정책을 숫자로 변환한 뒤, 요청의 IO 우선순위 클래스를 (정책의 숫자 값과 기존 IO 우선순위 클래스 숫자 값 중) 더 큰 값으로 변경한다.
### PID[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#pid "Permalink to this heading")
프로세스 개수 컨트롤러는, 설정된 리밋에 도달하면 해당 cgroup에서 새로운 태스크가 fork() 또는 clone()으로 생성되는 것을 막기 위해 사용된다.
cgroup 내 태스크 개수는, 다른 컨트롤러가 막을 수 없는 방식으로도 고갈될 수 있으므로, 이를 위한 별도의 컨트롤러가 필요하다. 예를 들어, fork bomb는 메모리 제한에 도달하기 전에 태스크 개수 리밋을 먼저 소진하는 경우가 흔하다.
이 컨트롤러에서 사용하는 PID는 TID(커널에서 사용하는 프로세스 ID)를 의미한다.
#### PID 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#pid-interface-files "Permalink to this heading")
> pids.max
> 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 기본값은 “max”이다.
>
>
> 허용되는 프로세스 개수에 대한 하드 리밋이다.
>
> pids.current
> 비루트 cgroup에 존재하는 읽기 전용 단일 값 파일이다.
>
>
> 해당 cgroup 및 그 자손에 현재 존재하는 프로세스 개수이다.
>
> pids.peak
> 비루트 cgroup에 존재하는 읽기 전용 단일 값 파일이다.
>
>
> 해당 cgroup 및 그 자손에 대해, 과거에 존재했던 프로세스 개수의 최대값이다.
>
> pids.events
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다. 별도로 명시하지 않는 한, 값이 변경되면 파일 수정 이벤트가 발생한다. 다음 항목이 정의되어 있다.
>
>
> > max
> > cgroup 전체 프로세스 개수가 pids.max 리밋에 도달한 횟수(pids_localevents와도 관련 있음).
>
> pids.events.local
> pids.events와 유사하지만, 파일 내 필드는 cgroup에 국한된(비계층적) 로컬 값만을 나타낸다. 이 파일에 대한 파일 수정 이벤트는 로컬 이벤트만 반영한다.
조직(organizational) 연산은 cgroup 정책에 의해 막히지 않으므로, pids.current > pids.max가 되는 상황도 가능하다. 예를 들어, pids.current 값보다 작은 리밋을 설정하거나, cgroup에 충분히 많은 프로세스를 attach하여 pids.current가 pids.max보다 커지게 할 수 있다. 하지만, fork()나 clone()을 통해 cgroup PID 정책을 위반할 수는 없다. 이 경우 새 프로세스를 생성하면 cgroup 정책을 위반하게 되므로, -EAGAIN이 반환된다.
### Cpuset[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#cpuset "Permalink to this heading")
“cpuset” 컨트롤러는 태스크의 CPU 및 메모리 노드 배치를 제약하여, 태스크가 속한 cgroup의 cpuset 인터페이스 파일에서 지정한 자원만 사용하도록 한다. 이는 대형 NUMA 시스템에서 특히 유용한데, 적절한 크기의 서브시스템에 작업을 배치하고, 프로세서 및 메모리 배치를 신중하게 선택하여 cross-node 메모리 접근과 경쟁을 줄이면 전체 시스템 성능을 향상시킬 수 있다.
“cpuset” 컨트롤러는 계층적이다. 즉, 컨트롤러는 부모에서 허용하지 않은 CPU나 메모리 노드를 사용할 수 없다.
#### Cpuset 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#cpuset-interface-files "Permalink to this heading")
> cpuset.cpus
> cpuset이 활성화된 비루트 cgroup에 존재하는 읽기/쓰기 가능한 다중 값 파일이다.
>
>
> 이 파일은 해당 cgroup 내 태스크가 사용할 수 있도록 요청하는 CPU 목록을 나열한다. 하지만 실제로 허용되는 CPU 목록은 상위 cgroup이 부과한 제약에 따라 달라질 수 있다.
>
>
> CPU 번호는 쉼표로 구분된 숫자 또는 범위로 지정된다. 예:
>
>
>
> # cat cpuset.cpus
> 0-4,6,8-10
>
>
> 값이 비어 있으면, 상위 계층에서 가장 가까운 조상 중 “cpuset.cpus”가 비어 있지 않은 cgroup의 설정을 그대로 사용하거나, 아예 그런 조상이 없다면 사용 가능한 모든 CPU를 사용한다는 의미이다.
>
>
> “cpuset.cpus” 값은 다음 업데이트까지 변하지 않으며, CPU hotplug 이벤트의 영향을 받지 않는다.
>
> cpuset.cpus.effective
> cpuset이 활성화된 모든 cgroup에 존재하는 읽기 전용 다중 값 파일이다.
>
>
> 상위 cgroup이 현재 cgroup에 실제로 허용한(onlined) CPU 목록을 나열한다. 이 CPU들은 현재 cgroup 내 태스크가 사용할 수 있는 CPU이다.
>
>
> “cpuset.cpus”가 비어 있는 경우, “cpuset.cpus.effective”는 상위 cgroup이 이 cgroup에 허용할 수 있는 모든 CPU를 보여준다. 그렇지 않으면, “cpuset.cpus.effective”는 “cpuset.cpus”의 서브셋이어야 한다. 단, “cpuset.cpus”에 나열된 CPU 중 상위에서 허용할 수 있는 것이 하나도 없다면, “cpuset.cpus”가 비어 있는 것과 동일하게 처리된다.
>
>
> “cpuset.cpus.effective” 값은 CPU hotplug 이벤트에 의해 변경될 수 있다.
>
> cpuset.mems
> cpuset이 활성화된 비루트 cgroup에 존재하는 읽기/쓰기 가능한 다중 값 파일이다.
>
>
> 이 파일은 해당 cgroup 내 태스크가 사용할 수 있도록 요청하는 메모리 노드 목록을 나열한다. 그러나 실제 허용되는 메모리 노드 목록은 상위 cgroup이 부과한 제약에 따라 달라질 수 있다.
>
>
> 메모리 노드 번호는 쉼표로 구분된 숫자 또는 범위로 지정된다. 예:
>
>
>
> # cat cpuset.mems
> 0-1,3
>
>
> 값이 비어 있으면, 상위 계층에서 가장 가까운 조상 중 “cpuset.mems”가 비어 있지 않은 cgroup의 설정을 그대로 사용하거나, 아예 그런 조상이 없다면 사용 가능한 모든 메모리 노드를 사용한다는 의미이다.
>
>
> “cpuset.mems” 값은 다음 업데이트까지 변하지 않으며, 메모리 노드 hotplug 이벤트에 의해 변경되지 않는다.
>
>
> “cpuset.mems”에 비어 있지 않은 값을 설정하면, 해당 cgroup 내 태스크가 지정된 노드 외의 메모리를 사용하고 있는 경우, 그 메모리를 지정된 노드로 마이그레이션한다.
>
>
> 메모리 마이그레이션에는 비용이 든다. 또한 마이그레이션이 완전히 끝나지 않아 일부 페이지가 남을 수 있다. 따라서 cpuset.mems는 cpuset에 새 태스크를 스폰하기 전에 적절히 설정하는 것이 좋다. 활성 태스크가 있는 상태에서 cpuset.mems를 변경해야 한다 하더라도, 이를 자주 변경하는 것은 피해야 한다.
>
> cpuset.mems.effective
> cpuset이 활성화된 모든 cgroup에 존재하는 읽기 전용 다중 값 파일이다.
>
>
> 상위 cgroup이 현재 cgroup에 실제로 허용한(onlined) 메모리 노드 목록을 나열한다. 이 노드들은 현재 cgroup 내 태스크가 사용할 수 있는 메모리 노드이다.
>
>
> “cpuset.mems”가 비어 있는 경우, 이 파일은 상위 cgroup이 이 cgroup에 허용할 수 있는 모든 메모리 노드를 보여준다. 그렇지 않으면, “cpuset.mems.effective”는 “cpuset.mems”의 서브셋이어야 한다. 단, “cpuset.mems”에 나열된 메모리 노드 중 상위에서 허용할 수 있는 것이 하나도 없다면, “cpuset.mems”가 비어 있는 것과 동일하게 처리된다.
>
>
> “cpuset.mems.effective” 값은 메모리 노드 hotplug 이벤트에 의해 변경될 수 있다.
>
> cpuset.cpus.exclusive
> cpuset이 활성화된 비루트 cgroup에 존재하는 읽기/쓰기 가능한 다중 값 파일이다.
>
>
> 이 파일은 새로운 cpuset 파티션을 생성하는 데 사용할 수 있는 exclusive CPU 목록을 나열한다. 해당 cgroup이 유효한 파티션 루트가 되지 않는 한, 이 값은 사용되지 않는다. cpuset 파티션이 무엇인지에 대한 설명은 아래 “cpuset.cpus.partition” 섹션을 참조한다.
>
>
> cgroup이 파티션 루트가 되면, 해당 파티션에 실제로 할당된 exclusive CPU 목록은 “cpuset.cpus.exclusive.effective”에 나열되며, 이는 “cpuset.cpus.exclusive”와 다를 수 있다. “cpuset.cpus.exclusive”가 사전에 설정되어 있는 경우, “cpuset.cpus.exclusive.effective”는 항상 그 서브셋이 된다.
>
>
> 사용자는 “cpuset.cpus.exclusive”를 “cpuset.cpus”와 다른 값으로 수동 설정할 수 있다. 다만, 이때 형제 cgroup의 “cpuset.cpus.exclusive”와 충돌하지 않아야 한다. 형제 cgroup의 “cpuset.cpus.exclusive”가 설정되어 있지 않은 경우, 그 형제의 “cpuset.cpus” 값(설정된 경우)이 현재 cgroup의 exclusive CPU 집합에 완전히 포함되어서는 안 된다. exclusive CPU를 할당할 때, 적어도 하나의 CPU는 남겨 두어야 하기 때문이다.
>
>
> 부모 cgroup 입장에서, 각 exclusive CPU는 최대 하나의 자식 cgroup에만 할당될 수 있다. 동일 exclusive CPU가 두 개 이상의 자식 cgroup에 나타나는 것은 허용되지 않는다(exclusivity 규칙). 이 규칙을 위반하는 값은 쓰기 오류로 거부된다.
>
>
> 루트 cgroup은 파티션 루트이며, 사용 가능한 모든 CPU가 exclusive CPU 집합에 속한다.
>
> cpuset.cpus.exclusive.effective
> cpuset이 활성화된 모든 비루트 cgroup에 존재하는 읽기 전용 다중 값 파일이다.
>
>
> 이 파일은 파티션 루트 생성에 사용할 수 있는 effective exclusive CPU 집합을 보여준다. 루트 cgroup이 아닌 경우, 이 파일의 내용은 항상 부모 cgroup의 “cpuset.cpus.exclusive.effective”의 서브셋이다. 또한, “cpuset.cpus.exclusive”가 설정되어 있으면 그 서브셋이 된다. “cpuset.cpus.exclusive”가 설정되어 있지 않으면, 로컬 파티션 형성 시 “cpuset.cpus”와 동일한 암묵적 값을 갖는 것으로 취급한다.
>
> cpuset.cpus.isolated
> 루트 cgroup에만 존재하는 읽기 전용 다중 값 파일이다.
>
>
> 이 파일은 기존 isolated 파티션에서 사용 중인 모든 isolated CPU 집합을 보여준다. isolated 파티션이 하나도 없으면 비어 있다.
>
> cpuset.cpus.partition
> cpuset이 활성화된 비루트 cgroup에 존재하는 읽기/쓰기 가능한 단일 값 파일이다. 이 플래그는 부모 cgroup이 소유하며, 위임(delegation)할 수 없다.
>
>
> 쓰기 시 허용되는 값은 다음뿐이다.
>
>
> > “member” 비루트 파티션 구성원
> > “root” 파티션 루트
> > “isolated” 로드 밸런싱이 없는 파티션 루트
>
>
> cpuset 파티션은, 계층 구조 상단에 파티션 루트가 있고, 그 자손 중 별도의 파티션 루트 및 그 자손을 제외한 모든 cpuset 활성화 cgroup 집합이다. 파티션은 할당된 exclusive CPU 집합에 대한 독점적인 접근 권한을 가진다. 파티션 밖의 다른 cgroup은 이 CPU들 중 어떤 것도 사용할 수 없다.
>
>
> 파티션에는 local 파티션과 remote 파티션의 두 가지 타입이 있다. local 파티션은 부모 cgroup도 유효한 파티션 루트인 경우를 말한다. remote 파티션은 부모 cgroup이 파티션 루트가 아닌 경우이다. local 파티션을 생성할 때는, “cpuset.cpus.exclusive”를 반드시 설정할 필요는 없으며, 설정하지 않으면 해당 값은 암묵적으로 “cpuset.cpus”와 동일하게 간주된다. remote 파티션을 생성하려면, 대상 파티션 루트까지의 계층 구조에서 “cpuset.cpus.exclusive” 값을 올바르게 설정해 두어야 한다.
>
>
> 현재로서는, local 파티션 아래에 remote 파티션을 생성할 수 없다. remote 파티션 루트를 제외한 모든 조상(루트 cgroup 제외)은 파티션 루트가 될 수 없다.
>
>
> 루트 cgroup은 항상 파티션 루트이며, 그 상태는 변경할 수 없다. 그 외 모든 비루트 cgroup은 초기 상태에서 “member”이다.
>
>
> 값을 “root”로 설정하면, 현재 cgroup이 새로운 파티션 또는 스케줄링 도메인의 루트가 된다. exclusive CPU 집합은 “cpuset.cpus.exclusive.effective” 값에 의해 결정된다.
>
>
> 값을 “isolated”로 설정하면, 해당 파티션의 CPU들은 스케줄러의 로드 밸런싱 대상에서 제외되고, unbound workqueue에서도 제외된다. 이러한 파티션에 여러 CPU가 포함되어 있는 경우, 최적의 성능을 위해 태스크를 개별 CPU에 신중하게 분산 및 바인딩해야 한다.
>
>
> 파티션 루트(“root” 또는 “isolated”)는 유효(valid) 또는 무효(invalid) 두 가지 상태 중 하나에 있을 수 있다. 무효 파티션 루트는 일부 상태 정보를 유지하지만, 동작은 “member”에 더 가깝게 동작하는 열화(degraded) 상태다.
>
>
> “member”, “root”, “isolated” 간의 모든 상태 전이는 허용된다.
>
>
> “cpuset.cpus.partition” 파일을 읽으면, 다음 값들 중 하나가 표시될 수 있다.
>
>
> > “member” 비루트 파티션 구성원
> > “root” 파티션 루트
> > “isolated” 로드 밸런싱이 없는 파티션 루트
> > “root invalid (<이유>)” 무효 파티션 루트
> > “isolated invalid (<이유>)” 무효 isolated 파티션 루트
>
>
> 무효 파티션 루트인 경우, 괄호 안에 파티션이 무효가 된 이유를 설명하는 문자열이 함께 표시된다.
>
>
> local 파티션 루트가 유효하기 위해서는 다음 조건들을 충족해야 한다.
>
>
> 1. 부모 cgroup이 유효한 파티션 루트여야 한다.
>
> 2. “cpuset.cpus.exclusive.effective” 파일이 비어 있으면 안 된다(다만 오프라인 CPU를 포함할 수는 있다).
>
> 3. “cpuset.cpus.effective”는, 해당 파티션에 태스크가 하나도 없는 경우를 제외하면, 비어 있으면 안 된다.
>
>
>
> remote 파티션 루트는, 위 조건 중 첫 번째를 제외한 나머지 조건들을 모두 만족해야 한다.
>
>
> CPU hotplug나 “cpuset.cpus”, “cpuset.cpus.exclusive” 변경 같은 외부 이벤트는, 유효한 파티션 루트를 무효 상태로 만들거나, 그 반대로 만들 수 있다. “cpuset.cpus.effective”가 비어 있는 cgroup으로는 태스크를 이동할 수 없다는 점에 유의해야 한다.
>
>
> 유효한 비루트 부모 파티션은, 해당 파티션에 태스크가 하나도 없는 경우, 모든 CPU를 자식 local 파티션들에 분배할 수 있다.
>
>
> 유효한 파티션 루트를 “member”로 변경할 때는 주의를 기울여야 한다. 그 아래에 있는 모든 자식 local 파티션이 무효 상태가 되어, 해당 자식 파티션에서 실행 중인 태스크에 중단을 초래하기 때문이다. 비활성화된 이들 파티션은, 부모를 적절한 “cpuset.cpus” 또는 “cpuset.cpus.exclusive” 값을 가진 파티션 루트로 다시 전환하면 복구될 수 있다.
>
>
> “cpuset.cpus.partition” 상태가 변경될 때마다 poll 및 inotify 이벤트가 발생한다. 여기에는 “cpuset.cpus.partition”에 대한 쓰기, CPU hotplug, 다른 변경으로 인한 파티션 유효성 상태 변화 등이 포함된다. 이는 사용자 공간 에이전트가 지속적인 폴링 없이도 “cpuset.cpus.partition”의 예상치 못한 변화를 모니터링할 수 있도록 하기 위함이다.
>
>
> 사용자는 “isolcpus” 커널 부트 커맨드라인 옵션을 통해, 부트 시에 특정 CPU를 로드 밸런싱이 비활성화된 isolated 상태로 미리 설정할 수 있다. 이러한 CPU를 파티션에 포함시키려면, 해당 CPU를 isolated 파티션에서 사용해야 한다.
### 디바이스 컨트롤러[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#device-controller "Permalink to this heading")
디바이스 컨트롤러는 디바이스 파일에 대한 접근을 관리한다. 이는 mknod를 이용한 새로운 디바이스 파일 생성과, 기존 디바이스 파일에 대한 접근을 모두 포함한다.
cgroup v2 디바이스 컨트롤러는 자체 인터페이스 파일이 없으며, cgroup BPF 위에 구현된다. 디바이스 파일 접근을 제어하려면, 사용자는 BPF_PROG_TYPE_CGROUP_DEVICE 타입의 BPF 프로그램을 작성하여, 이를 BPF_CGROUP_DEVICE 플래그와 함께 cgroup에 attach하면 된다. 디바이스 파일에 접근을 시도할 때마다, 해당 BPF 프로그램이 실행되며, 반환 값에 따라 접근이 성공하거나 -EPERM으로 실패한다.
BPF_PROG_TYPE_CGROUP_DEVICE 프로그램은 bpf_cgroup_dev_ctx 구조체 포인터를 인자로 받으며, 이는 디바이스 접근 시도를 기술한다. 여기에는 접근 타입(mknod/read/write)과 디바이스 타입, major/minor 번호가 포함된다. 프로그램이 0을 반환하면 접근은 -EPERM으로 실패하고, 0 이외의 값을 반환하면 접근이 허용된다.
BPF_PROG_TYPE_CGROUP_DEVICE 프로그램의 예시는 커널 소스 트리의 tools/testing/selftests/bpf/progs/dev_cgroup.c에서 찾을 수 있다.
### RDMA[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#rdma "Permalink to this heading")
“rdma” 컨트롤러는 RDMA 자원의 분배와 계정을 제어한다.
#### RDMA 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#rdma-interface-files "Permalink to this heading")
> rdma.max
> 루트를 제외한 모든 cgroup에 존재하는 읽기/쓰기 가능한 중첩 키-값 파일로, RDMA/IB 디바이스별 현재 설정된 자원 리밋을 기술한다.
>
>
> 각 줄은 디바이스 이름을 키로 하며, 순서는 보장되지 않는다. 각 줄에는 공백으로 구분된 자원 이름과 해당 자원에 대해 분배 가능한 리밋이 나타난다.
>
>
> 정의된 중첩 키는 다음과 같다.
>
>
> > hca_handle 허용되는 HCA 핸들 최대 개수
> > hca_object 허용되는 HCA 오브젝트 최대 개수
>
>
> mlx4 및 ocrdma 디바이스에 대한 예시는 다음과 같다.
>
>
>
> mlx4_0 hca_handle=2 hca_object=2000
> ocrdma1 hca_handle=3 hca_object=max
>
> rdma.current
> 루트를 제외한 모든 cgroup에 존재하는 읽기 전용 파일로, 현재 RDMA 자원 사용량을 기술한다.
>
>
> mlx4 및 ocrdma 디바이스에 대한 예시는 다음과 같다.
>
>
>
> mlx4_0 hca_handle=1 hca_object=20
> ocrdma1 hca_handle=1 hca_object=23
### DMEM[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#dmem "Permalink to this heading")
“dmem” 컨트롤러는 디바이스 메모리 영역(device memory regions)의 분배 및 계정을 제어한다. 각 메모리 영역은 시스템 페이지 크기와 다를 수 있는 고유 페이지 크기를 가질 수 있으므로, 단위는 항상 바이트를 사용한다.
#### DMEM 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#dmem-interface-files "Permalink to this heading")
> dmem.max, dmem.min, dmem.low
> 루트를 제외한 모든 cgroup에 존재하는 읽기/쓰기 가능한 중첩 키-값 파일로, 각 메모리 영역에 대한 현재 설정된 자원 리밋을 기술한다.
>
>
> xe 디바이스에 대한 예시는 다음과 같다.
>
>
>
> drm/0000:03:00.0/vram0 1073741824
> drm/0000:03:00.0/stolen max
>
>
> 의미론은 메모리 cgroup 컨트롤러와 동일하며, 동일한 방식으로 계산된다.
>
> dmem.capacity
> 루트 cgroup에만 존재하는 읽기 전용 파일로, 각 메모리 영역의 최대 용량을 기술한다. 모든 메모리가 cgroup에 할당 가능한 것은 아니며, 일부는 커널이 내부적으로 사용하기 위해 예약한다.
>
>
> xe 디바이스에 대한 예시는 다음과 같다.
>
>
>
> drm/0000:03:00.0/vram0 8514437120
> drm/0000:03:00.0/stolen 67108864
>
> dmem.current
> 루트를 제외한 모든 cgroup에 존재하는 읽기 전용 파일로, 현재 자원 사용량을 기술한다.
>
>
> xe 디바이스에 대한 예시는 다음과 같다.
>
>
>
> drm/0000:03:00.0/vram0 12550144
> drm/0000:03:00.0/stolen 8650752
### HugeTLB[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#hugetlb "Permalink to this heading")
HugeTLB 컨트롤러는 cgroup별 HugeTLB 사용량을 제한하고, 페이지 폴트 시 컨트롤러 리밋을 강제한다.
#### HugeTLB 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#hugetlb-interface-files "Permalink to this heading")
> hugetlb.<hugepagesize>.current
> 루트를 제외한 모든 cgroup에 존재하며, 해당 “hugepagesize”에 대한 현재 HugeTLB 사용량을 보여준다.
>
> hugetlb.<hugepagesize>.max
> 루트를 제외한 모든 cgroup에 존재하며, “hugepagesize”에 대한 HugeTLB 사용량 하드 리밋을 설정/표시한다. 기본값은 “max”이다.
>
> hugetlb.<hugepagesize>.events
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다.
>
>
> > max
> > HugeTLB 리밋에 의해 할당이 실패한 횟수
>
> hugetlb.<hugepagesize>.events.local
> hugetlb.<hugepagesize>.events와 유사하지만, 파일 내 필드는 cgroup에 국한된(비계층적) 로컬 값만을 나타낸다. 이 파일에 대한 파일 수정 이벤트는 로컬 이벤트만을 반영한다.
>
> hugetlb.<hugepagesize>.numa_stat
> memory.numa_stat와 유사하게, 해당 cgroup 내 <hugepagesize> HugeTLB 페이지의 NUMA 정보를 보여준다. 현재 active 상태로 사용 중인 HugeTLB 페이지만 포함한다. per-node 값은 바이트 단위이다.
### Misc[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#misc "Permalink to this heading")
Miscellaneous cgroup은, 다른 cgroup 자원처럼 추상화하기 어려운 스칼라(scalar) 자원에 대해 자원 제한 및 추적 메커니즘을 제공한다. 이 컨트롤러는 CONFIG_CGROUP_MISC 설정 옵션으로 활성화된다.
새로운 자원은 include/linux/misc_cgroup.h의 `enum misc_res_type`에 추가하고, kernel/cgroup/misc.c의 misc_res_name[] 배열에 이름을 추가함으로써 컨트롤러에 등록할 수 있다. 자원 공급자는 `misc_cg_set_capacity()`를 호출하여, 사용에 앞서 해당 자원의 용량(capacity)을 설정해야 한다.
용량이 설정되면, charge 및 uncharge API를 이용해 자원 사용량을 갱신할 수 있다. misc 컨트롤러와 상호 작용하기 위한 모든 API는 include/linux/misc_cgroup.h에 정의되어 있다.
#### Misc 인터페이스 파일[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#misc-interface-files "Permalink to this heading")
Misc 컨트롤러는 3개의 인터페이스 파일을 제공한다. 두 개의 misc 자원(res_a, res_b)이 등록된 경우를 예로 들면 다음과 같다.
> misc.capacity
> 루트 cgroup에만 표시되는 읽기 전용 플랫 키-값 파일이다. 플랫폼에서 사용 가능한 Misc 스칼라 자원과 각 자원의 총량을 보여준다.
>
>
>
> $ cat misc.capacity
> res_a 50
> res_b 10
>
> misc.current
> 모든 cgroup에 표시되는 읽기 전용 플랫 키-값 파일이다. 해당 cgroup과 그 자손에서 현재 사용 중인 자원 양을 보여준다.
>
>
>
> $ cat misc.current
> res_a 3
> res_b 0
>
> misc.peak
> 모든 cgroup에 표시되는 읽기 전용 플랫 키-값 파일이다. 해당 cgroup과 그 자손에서 과거에 사용된 자원 양의 최대치를 보여준다.
>
>
>
> $ cat misc.peak
> res_a 10
> res_b 8
>
> misc.max
> 비루트 cgroup에 표시되는 읽기/쓰기 가능한 플랫 키-값 파일이다. 해당 cgroup과 그 자손에서 허용되는 자원 사용량의 최대치를 설정한다.
>
>
>
> $ cat misc.max
> res_a max
> res_b 4
>
>
> 리밋은 다음과 같이 설정할 수 있다.
>
>
>
> # echo res_a 1 > misc.max
>
>
> 리밋을 다시 max로 되돌리려면 다음과 같이 하면 된다.
>
>
>
> # echo res_a max > misc.max
>
>
> misc.capacity 파일에 표시된 capacity 값보다 더 큰 리밋을 misc.max에 설정하는 것도 가능하다.
>
> misc.events
> 비루트 cgroup에 존재하는 읽기 전용 플랫 키-값 파일이다. 다음 항목이 정의되어 있으며, 별도로 명시하지 않는 한 값이 변경되면 파일 수정 이벤트가 발생한다. 이 파일의 모든 필드는 계층적이다.
>
>
> > max
> > cgroup의 자원 사용량이 max 경계를 초과할 뻔한 횟수.
>
> misc.events.local
> misc.events와 유사하지만, 파일 내 필드는 cgroup에 국한된(비계층적) 로컬 값만을 나타낸다. 이 파일에 대한 파일 수정 이벤트는 로컬 이벤트만 반영한다.
#### 마이그레이션과 소유권[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#migration-and-ownership "Permalink to this heading")
스칼라 Misc 자원은 최초로 사용된 cgroup에 charge되며, 해당 자원이 해제될 때까지 그 cgroup에 남아 있다. 프로세스를 다른 cgroup으로 마이그레이션해도, 이미 할당된 자원이 새 cgroup으로 이동되지는 않는다.
### 기타[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#others "Permalink to this heading")
#### perf_event[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#perf-event "Permalink to this heading")
perf_event 컨트롤러는 레거시 계층에 마운트되지 않은 경우, v2 계층에서 자동으로 활성화되며, perf 이벤트를 항상 cgroup v2 경로로 필터링할 수 있도록 한다. v2 계층이 이미 사용 중이더라도, perf_event 컨트롤러는 여전히 레거시 계층으로 이동시킬 수 있다.
### 비규범적 정보(Non-normative information)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#non-normative-information "Permalink to this heading")
이 섹션은 안정적인 커널 API의 일부로 간주되지 않는 정보를 담고 있으며, 변경될 수 있다.
#### CPU 컨트롤러에서 루트 cgroup 프로세스 동작[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-controller-root-cgroup-process-behaviour "Permalink to this heading")
루트 cgroup에서 CPU 사이클을 분배할 때, 루트 cgroup에 속한 각 스레드는 마치 루트 cgroup의 별도 자식 cgroup에 호스팅되는 것처럼 취급된다. 이 가상 자식 cgroup의 weight는 해당 스레드의 nice 레벨에 따라 결정된다.
이 매핑에 대한 자세한 내용은 kernel/sched/core.c 파일의 sched_prio_to_weight 배열을 참고하라(여기서, 중립적 값인 nice 0에 해당하는 weight가 1024가 아닌 100이 되도록 값이 적절히 스케일 조정되어야 한다).
#### IO 컨트롤러에서 루트 cgroup 프로세스 동작[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#io-controller-root-cgroup-process-behaviour "Permalink to this heading")
루트 cgroup 프로세스는 암묵적(implicit) 리프 자식 노드에 호스팅된다. IO 자원을 분배할 때, 이 암묵적 자식 노드는 weight 값 200을 가진 일반 자식 cgroup과 동일하게 간주된다.
네임스페이스[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#namespace "Permalink to this heading")
------------------------------------------------------------------------------------------------------
### 기본(Basics)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#basics "Permalink to this heading")
cgroup 네임스페이스는 “/proc/$PID/cgroup” 파일과 cgroup 마운트의 뷰(view)를 가상화하는 메커니즘을 제공한다. clone(2) 및 unshare(2)에 CLONE_NEWCGROUP 플래그를 사용하면 새로운 cgroup 네임스페이스를 생성할 수 있다. cgroup 네임스페이스 안에서 실행되는 프로세스는, “/proc/$PID/cgroup” 출력이 해당 네임스페이스의 cgroupns 루트로 제한된다. cgroupns 루트는 cgroup 네임스페이스 생성 시, 생성 프로세스가 속해 있던 cgroup이다.
cgroup 네임스페이스가 없으면, “/proc/$PID/cgroup” 파일은 프로세스가 속한 cgroup의 전체 경로를 보여준다. cgroup과 여러 네임스페이스를 조합하여 프로세스를 격리하는 컨테이너 환경에서는, “/proc/$PID/cgroup” 파일이 격리된 프로세스에게 잠재적 시스템 레벨 정보를 누출할 수 있다. 예를 들어:
```bash
# cat /proc/self/cgroup
0::/batchjobs/container_id1
```
경로 ‘/batchjobs/container_id1’는 시스템 내부 정보로 간주할 수 있으며, 격리된 프로세스에 노출하는 것은 바람직하지 않을 수 있다. cgroup 네임스페이스를 이용하면 이 경로의 가시성을 제한할 수 있다. 예를 들어, cgroup 네임스페이스를 생성하기 전에는 다음과 같이 보인다.
```bash
# ls -l /proc/self/ns/cgroup
lrwxrwxrwx 1 root root 0 2014-07-15 10:37 /proc/self/ns/cgroup -> cgroup:[4026531835]
# cat /proc/self/cgroup
0::/batchjobs/container_id1
```
새 네임스페이스를 unshare한 후에는 뷰가 다음과 같이 바뀐다.
```bash
# ls -l /proc/self/ns/cgroup
lrwxrwxrwx 1 root root 0 2014-07-15 10:35 /proc/self/ns/cgroup -> cgroup:[4026532183]
# cat /proc/self/cgroup
0::/
```
멀티 스레드 프로세스에서 특정 스레드가 cgroup 네임스페이스를 unshare하면, 새 cgroupns는 전체 프로세스(모든 스레드)에 적용된다. 이는 v2 계층에서는 자연스러운 동작이지만, 레거시 계층에서는 예상과 다를 수 있다.
cgroup 네임스페이스는, 내부에 프로세스가 존재하거나 마운트가 이를 pinning하고 있는 동안까지 살아 있다. 마지막 사용자가 사라지면 cgroup 네임스페이스는 파괴된다. 이때 cgroupns 루트와 실제 cgroup들은 그대로 남는다.
### 루트와 뷰(The Root and Views)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#the-root-and-views "Permalink to this heading")
cgroup 네임스페이스의 ‘cgroupns 루트’는 unshare(2)를 호출한 프로세스가 속해 있던 cgroup이다. 예를 들어, /batchjobs/container_id1 cgroup에 속한 프로세스가 unshare를 호출하면, /batchjobs/container_id1 cgroup이 cgroupns 루트가 된다. init_cgroup_ns의 루트는 실제 루트(‘/’) cgroup이다.
cgroupns 루트 cgroup은, 네임스페이스를 생성한 프로세스가 이후 다른 cgroup으로 이동하더라도 변경되지 않는다.
```bash
# ~/unshare -c # 어떤 cgroup 내에서 cgroupns unshare
# cat /proc/self/cgroup
0::/
# mkdir sub_cgrp_1
# echo 0 > sub_cgrp_1/cgroup.procs
# cat /proc/self/cgroup
0::/sub_cgrp_1
```
각 프로세스는 자신이 속한 네임스페이스에 특화된 “/proc/$PID/cgroup” 뷰를 가진다.
cgroup 네임스페이스 안에서 실행되는 프로세스는, “/proc/self/cgroup”에서 자신이 속한 cgroupns 루트 안의 경로만을 볼 수 있다. unshare된 cgroupns 내부에서의 예는 다음과 같다.
```bash
# sleep 100000 &
[1] 7353
# echo 7353 > sub_cgrp_1/cgroup.procs
# cat /proc/7353/cgroup
0::/sub_cgrp_1
```
초기(initial) cgroup 네임스페이스에서 보면, 실제 cgroup 경로가 보인다.
```bash
$ cat /proc/7353/cgroup
0::/batchjobs/container_id1/sub_cgrp_1
```
형제 cgroup 네임스페이스(즉, 다른 cgroup에서 루트가 설정된 네임스페이스)에서 보면, 자신의 cgroupns 루트를 기준으로 한 경로가 보인다. 예를 들어, PID 7353의 cgroup 네임스페이스 루트가 ‘/batchjobs/container_id2’에 있다면, 다음과 같이 보인다.
```bash
# cat /proc/7353/cgroup
0::/../container_id2/sub_cgrp_1
```
이 상대 경로는 항상 ‘/’로 시작하는데, 이는 호출자의 cgroup 네임스페이스 루트를 기준으로 한 경로임을 나타내기 위함이다.
### 마이그레이션과 setns(2)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#migration-and-setns-2 "Permalink to this heading")
cgroup 네임스페이스 내부의 프로세스는, 적절한 외부 cgroup에 대한 접근 권한이 있는 경우, 네임스페이스 루트 안팎으로 이동할 수 있다. 예를 들어, cgroupns 루트가 /batchjobs/container_id1인 네임스페이스 내부에서, 글로벌 계층이 여전히 접근 가능하다고 가정하면 다음과 같이 동작할 수 있다.
```bash
# cat /proc/7353/cgroup
0::/sub_cgrp_1
# echo 7353 > batchjobs/container_id2/cgroup.procs
# cat /proc/7353/cgroup
0::/../container_id2
```
이러한 설정은 권장되지 않는다. cgroup 네임스페이스 내부의 태스크는 가능한 한 자신의 cgroupns 계층 구조에만 노출되는 것이 바람직하다.
setns(2)를 사용해 다른 cgroup 네임스페이스에 attach하는 것은 다음 조건을 만족할 때 허용된다.
1. 현재 유저 네임스페이스에 대해 CAP_SYS_ADMIN을 가져야 한다.
2. 대상 cgroup 네임스페이스의 userns에 대해서도 CAP_SYS_ADMIN을 가져야 한다.
다른 cgroup 네임스페이스에 attach해도, 암묵적인 cgroup 변경은 일어나지 않는다. 일반적으로, 대상 cgroup 네임스페이스 루트 아래로 프로세스를 이동시키는 역할은 별도의 주체가 수행해야 한다.
### 다른 네임스페이스와의 상호작용[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#interaction-with-other-namespaces "Permalink to this heading")
init가 아닌 cgroup 네임스페이스 내부에서 실행되는 프로세스는, cgroup 네임스페이스에 특화된 cgroup 계층을 마운트할 수 있다.
```bash
# mount -t cgroup2 none $MOUNT_POINT
```
이는 cgroupns 루트를 파일시스템 루트로 하는 통합(unified) cgroup 계층을 마운트한다. 해당 프로세스는 자신의 유저 및 마운트 네임스페이스에 대해 CAP_SYS_ADMIN 권한을 가져야 한다.
/proc/self/cgroup 파일의 가상화와, 네임스페이스 전용 cgroupfs 마운트를 통한 cgroup 계층 뷰 제한을 결합하면, 컨테이너 내부에서 제대로 격리된 cgroup 뷰를 제공할 수 있다.
커널 프로그래밍 관련 정보[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#information-on-kernel-programming "Permalink to this heading")
------------------------------------------------------------------------------------------------------------------------------------------------------
이 섹션은 cgroup과의 상호 작용이 필요한 커널 프로그래밍 영역에 대한 정보를 다룬다. cgroup 코어 및 컨트롤러 자체는 다루지 않는다.
### 쓰기백을 위한 파일시스템 지원[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#filesystem-support-for-writeback "Permalink to this heading")
파일시스템은 address_space_operations->`writepages()`를 업데이트하고, 다음 두 함수를 사용해 bio에 주석(annotation)을 추가함으로써 cgroup writeback을 지원할 수 있다.
> wbc_init_bio(@wbc, @bio)
> 각 writeback 데이터를 담은 bio에 대해 호출되어야 하며, 해당 bio를 inode 소유 cgroup 및 대응 요청 큐(request queue)에 연관(bind)한다. 이 함수는 bio에 큐(디바이스)가 연관된 뒤, bio 제출 전에 호출해야 한다.
>
> wbc_account_cgroup_owner(@wbc, @folio, @bytes)
> writeout되는 각 데이터 세그먼트에 대해 호출되어야 한다. 이 함수는 writeback 세션 동안 정확히 언제 호출되는지는 중요하지 않지만, 가장 쉽고 자연스러운 시점은 데이터 세그먼트를 bio에 추가하는 순간이다.
writeback bio에 주석을 추가하면, super_block에 SB_I_CGROUPWB 플래그를 설정함으로써 cgroup writeback 지원을 활성화할 수 있다. 이는 특정 파일시스템 기능(예: 저널링된 데이터 모드)이 cgroup writeback과 호환되지 않는 경우, cgroup writeback을 선택적으로 비활성화하는 데 도움이 된다.
`wbc_init_bio()`는 지정된 bio를 해당 cgroup에 바인딩한다. 설정에 따라, 이 bio는 더 낮은 우선순위에서 실행될 수 있으며, writeback 세션이 공유 리소스(예: 저널 엔트리)를 보유하고 있는 경우 우선순위 역전(priority inversion) 문제를 야기할 수 있다. 이 문제에 대한 간단한 일반 해법은 없다. 파일시스템은 특정 문제 케이스를 우회하기 위해, `wbc_init_bio()` 호출을 생략하고 `bio_associate_blkg()`를 직접 호출하는 방식을 사용할 수 있다.
폐기된 v1 코어 기능[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#deprecated-v1-core-features "Permalink to this heading")
------------------------------------------------------------------------------------------------------------------------------------------
* 여러 계층 및 이름 붙은(named) 계층은 지원되지 않는다.
* 모든 v1 마운트 옵션은 지원되지 않는다.
* “tasks” 파일이 제거되었고, “cgroup.procs”는 정렬되지 않는다.
* “cgroup.clone_children”가 제거되었다.
* /proc/cgroups는 v2에서는 의미가 없다. 대신 루트에서 “cgroup.controllers” 또는 “cgroup.stat” 파일을 사용해야 한다.
v1의 문제점과 v2의 설계 근거[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#issues-with-v1-and-rationales-for-v2 "Permalink to this heading")
------------------------------------------------------------------------------------------------------------------------------------------------------------
### 다중 계층(Multiple Hierarchies)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#multiple-hierarchies "Permalink to this heading")
cgroup v1은 임의 개수의 계층을 허용했으며, 각 계층은 임의 개수의 컨트롤러를 호스팅할 수 있었다. 이는 높은 수준의 유연성을 제공하는 것처럼 보였지만, 실제로는 거의 유용하지 않았다.
예를 들어, 컨트롤러는 각자 단 하나의 인스턴스만 존재하므로, freezer처럼 모든 계층에서 유용할 수 있는 유틸리티 컨트롤러도 단 하나의 계층에서만 사용할 수 있었다. 계층이 한 번 populate되면 컨트롤러를 다른 계층으로 이동할 수 없었기 때문에, 이 문제는 더욱 심각해졌다. 또 다른 문제는, 하나의 계층에 묶인 모든 컨트롤러가 반드시 동일한 계층 구조를 공유해야 했다는 점이다. 특정 컨트롤러에 따라 계층 세분화 정도를 다르게 가져가는 것이 불가능했다.
실제 사용에서는 이러한 문제들 때문에, 어떤 컨트롤러를 같은 계층에 둘 수 있는지가 크게 제한되었고, 대부분의 설정은 각 컨트롤러를 별도의 계층에 배치하는 방식으로 귀결되었다. CPU와 cpuacct처럼 밀접하게 연관된 컨트롤러들만이 같은 계층에 두는 것이 의미가 있었다. 이는 종종 사용자 공간이 여러 유사한 계층을 관리하면서, 계층 관리 작업이 필요할 때마다 각 계층에서 동일한 단계를 반복해야 함을 의미했다.
또한, 다중 계층 지원은 높은 비용을 치르게 했다. 이는 cgroup 코어 구현을 크게 복잡하게 만들었을 뿐 아니라, 보다 일반적인 측면에서 cgroup 사용 방법과 컨트롤러가 할 수 있는 작업에까지 제한을 가했다.
계층 수에는 제한이 없었으므로, 하나의 스레드가 속한 cgroup 멤버십은 유한 길이로 표현될 수 없었다. 키(key)에는 임의 개수의 항목이 포함될 수 있었고 길이에 제한이 없었기 때문에, 이를 다루기 매우 불편했으며, 멤버십 식별만을 위해 존재하는 컨트롤러까지 추가되는 결과로 이어졌다. 이는 다시 계층 수가 폭발적으로 늘어나는 문제를 악화시켰다.
또한, 한 컨트롤러는 다른 컨트롤러들이 어떤 계층 토폴로지를 사용할지에 대해 어떠한 가정도 할 수 없었으므로, 각 컨트롤러는 다른 모든 컨트롤러가 완전히 직교(orthogonal)한 계층에 붙어 있다고 가정해야 했다. 이는 컨트롤러 간 협력을 불가능하게 만들거나, 적어도 매우 번거롭게 만들었다.
대부분의 사용 사례에서, 컨트롤러를 서로 완전히 직교하는 계층에 두는 것은 필요하지 않았다. 일반적으로 필요한 것은, 특정 컨트롤러마다 세분화 수준을 다르게 가질 수 있는 능력이다. 다시 말해, 특정 컨트롤러의 관점에서, 리프에서 루트 방향으로 계층을 부분적으로 접어(collapse) 보는 능력이다. 예를 들어, 어떤 설정에서는 특정 레벨 이후로는 메모리 분배에 관심이 없지만, CPU 사이클 분배는 여전히 세밀하게 제어하고 싶을 수 있다.
### 스레드 단위(Thread Granularity)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#thread-granularity "Permalink to this heading")
cgroup v1은 하나의 프로세스에 속한 스레드들이 서로 다른 cgroup에 속할 수 있도록 허용했다. 이는 일부 컨트롤러에 대해서는 의미가 없었고, 해당 컨트롤러들은 이런 상황을 무시하는 각기 다른 방식을 구현하게 되었다. 무엇보다도, 이는 개별 애플리케이션에 노출되는 API와 시스템 관리 인터페이스 간의 경계를 흐리게 했다.
일반적으로, 프로세스 내부(in-process) 지식은 해당 프로세스에만 존재하므로, 서비스 레벨에서의 프로세스 조직과는 달리, 하나의 프로세스 내 스레드를 분류하려면 대상 프로세스를 소유한 애플리케이션의 적극적인 참여가 필요하다.
cgroup v1은 모호하게 정의된 delegation 모델을 갖고 있었으며, 이는 스레드 단위와 결합되어 남용되었다. cgroup이 개별 애플리케이션에 위임되어, 애플리케이션이 자신의 서브트리를 생성하고 관리하며, 그 서브트리 상에서 자원 분배를 제어하도록 하는 식이었다. 이는 사실상 cgroup을 개별 프로그램에 노출된 시스템콜 수준 API로 끌어올리는 꼴이 되었다.
무엇보다, cgroup 인터페이스는 이런 방식으로 노출되기에는 근본적으로 부적합하다. 프로세스가 자신의 knob에 접근하려면, 먼저 /proc/self/cgroup에서 대상 계층의 경로를 추출하고, knob 이름을 경로에 붙여 전체 경로를 구성한 뒤, 해당 파일을 열고 읽거나 써야 한다. 이는 극단적으로 번거롭고 비정상적인 절차일 뿐만 아니라, 본질적으로 레이스 조건에 취약하다. 필요한 단계 전반에 걸쳐 트랜잭션을 정의할 전통적인 방법이 없으며, 프로세스가 실제로 자신의 서브트리에서 동작하고 있음을 보장할 수 있는 방법도 없다.
cgroup 컨트롤러는, 일반 공개 API로는 절대 허용되지 않았을 법한 다양한 knob들을 구현했는데, 이는 결국 시스템 관리용 pseudo 파일시스템에 단순히 제어 knob를 추가하는 수준에 머물렀기 때문이다. cgroup은 제대로 추상화되거나 정제되지 않은 인터페이스 knob를 갖게 되었고, 이들은 커널 내부 세부 사항을 직접적으로 노출했다. 이러한 knob들이 모호한 delegation 메커니즘을 통해 개별 애플리케이션에 노출되면서, 사실상 충분한 검토 없이 공개 API를 구현하기 위한 단축 경로로 cgroup이 남용된 셈이다.
이는 사용자 공간과 커널 모두에게 고통을 안겼다. 사용자 공간은 오동작하거나 제대로 추상화되지 않은 인터페이스를 떠안게 되었고, 커널은 의도치 않게 내부 구조를 노출한 채로 고착(lock-in)되는 결과를 초래했다.
### 내부 노드와 스레드 간 경쟁(Competition Between Inner Nodes and Threads)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#competition-between-inner-nodes-and-threads "Permalink to this heading")
cgroup v1은 스레드가 어떤 cgroup에 속하든 허용했기 때문에, 부모 cgroup에 속한 스레드와 자식 cgroup에 속한 스레드가 같은 자원을 두고 경쟁하는 흥미로운 문제가 발생했다. 이는 서로 다른 두 타입의 엔티티가 경쟁하는 상황이었고, 이를 해결할 명백한 방법이 없었다. 각기 다른 컨트롤러들은 서로 다른 방식을 택했다.
cpu 컨트롤러는 스레드와 cgroup을 동등한 것으로 간주하고, nice 레벨을 cgroup weight에 매핑했다. 이는 일부 상황에서는 괜찮았지만, 자식들이 서로 간 CPU 사이클을 특정 비율로 분배받길 원하고, 부모의 내부 스레드 수가 변동하는 경우에는 문제가 발생했다. 경쟁 엔티티 수가 변동함에 따라 비율이 계속 바뀌었기 때문이다. 또한, nice 레벨과 weight 사이의 매핑이 명확하거나 보편적이지 않았고, 스레드에는 사용할 수 없는 다양한 다른 knob도 존재했다.
io 컨트롤러는 각 cgroup에 내부 스레드를 수용하기 위한 숨겨진 리프 노드를 암묵적으로 생성했다. 이 숨겨진 리프는 `leaf_` 접두사가 붙은 knob의 별도 복사본을 가졌다. 이는 내부 스레드에 대해 동등한 제어를 허용했지만, 몇 가지 심각한 문제를 동반했다. 필요 없을 수도 있는 추가 중첩 레벨을 항상 도입했고, 인터페이스를 지저분하게 만들었으며, 구현을 크게 복잡하게 만들었다.
memory 컨트롤러는 내부 태스크와 자식 cgroup 간 경쟁에 대해 제어할 방법이 없었고, 동작 또한 명확하게 정의되지 않았다. 특정 워크로드에 동작을 맞추기 위해 임시방편적인 동작과 knob를 추가하려는 시도가 있었는데, 이는 장기적으로 해결하기 극도로 어려운 문제로 이어졌을 것이다.
여러 컨트롤러들이 내부 태스크 관련 문제로 골머리를 앓으며, 이를 처리하기 위해 서로 다른 방식을 도입했지만, 이들 접근법은 모두 심각한 결함을 가지고 있었고, 게다가 각기 다른 동작 때문에 cgroup 전체가 매우 불일치한 상태가 되었다.
이는 cgroup 코어 레벨에서 일관되게 해결해야 하는 문제임이 분명하다.
### 기타 인터페이스 문제(Other Interface Issues)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#other-interface-issues "Permalink to this heading")
cgroup v1은 적절한 감독 없이 성장하면서, 수많은 특이점(idiosyncrasy)과 불일치를 낳았다. cgroup 코어 측면에서, 빈 cgroup 알림 방식도 문제였다. 각 이벤트마다 사용자 공간 helper 바이너리를 fork 및 exec하는 방식이었는데, 이벤트 전달은 재귀적이지도, 위임 가능하지도 않았다. 이 메커니즘의 제약 때문에, 커널은 이벤트 전달을 필터링하는 추가 메커니즘을 제공해야 했고, 이는 인터페이스를 더욱 복잡하게 만들었다.
컨트롤러 인터페이스도 문제였다. 극단적인 예로, 계층적 구성을 완전히 무시하고 모든 cgroup을 루트 바로 아래에 있는 것처럼 취급하는 컨트롤러도 있었다. 일부 컨트롤러는 많은 양의 비일관적인 구현 세부 사항을 사용자 공간에 그대로 노출했다.
컨트롤러 간 일관성도 없었다. 새로운 cgroup이 생성되었을 때, 어떤 컨트롤러는 기본적으로 추가 제한을 부과하지 않으려 했지만, 다른 컨트롤러는 명시적으로 설정하기 전에는 어떤 자원도 사용하지 못하게 막았다. 동일한 유형의 제어에 대해, 컨트롤러마다 전혀 다른 이름과 형식을 사용하는 knob들을 제공하기도 했다. 심지어 동일한 컨트롤러 내부에서도, 통계 및 정보성(informational) knob에 대해 서로 다른 형식과 단위를 사용했다.
cgroup v2는 적절한 곳에서 공통 규칙을 정하고, 컨트롤러를 업데이트하여, 최소한의 일관된 인터페이스만을 노출하도록 한다.
### 컨트롤러별 문제와 해결책(Controller Issues and Remedies)[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#controller-issues-and-remedies "Permalink to this heading")
#### 메모리[¶](https://docs.kernel.org/admin-guide/cgroup-v2.html#id1 "Permalink to this heading")
기존의 하위 경계인 soft limit은, 기본적으로 설정되지 않은 상태의 리밋으로 정의된다. 그 결과, 글로벌 리클레임이 선호하는 cgroup 집합은 opt-in 방식이 되며, opt-out 방식이 아니다. 대부분 부정적인 조회(soft limit이 설정되지 않은 cgroup)에 대한 최적화 비용이 너무 커서, 구현 규모가 매우 큼에도 불구하고 기본적으로 바람직한 동작조차 제공하지 못했다. 우선, soft limit은 계층적 의미를 갖지 않는다. 모든 설정된 그룹은 전역 rbtree에 모여, 계층 위치와 무관하게 동등한 peer로 취급된다. 이는 서브트리 위임을 불가능하게 만든다. 둘째, soft limit 리클레임 패스는 지나치게 공격적이라, 높은 할당 지연을 유발할 뿐 아니라 과도한 리클레임(overreclaim)으로 시스템 성능을 떨어뜨려, 기능 자체가 자가당착(self-defeating)에 빠지게 만든다.
반면 memory.low 경계는 상위에서 아래로(top-down) 할당되는 보호 예약(reserve)이다. cgroup은 유효 low 이하에서 리클레임 보호를 받으며, 이렇게 하면 서브트리 위임이 가능해진다. 또한, 유효 low를 초과한 부분에 대해서는, 초과분에 비례한 리클레임 압력을 받는다.
기존의 상위 경계인 hard limit은, OOM killer를 호출하더라도 절대 넘지 못하는 엄격한 리밋으로 정의된다. 그러나 이는, 사용 가능한 메모리를 최대한 활용하려는 목표와는 어긋난다. 워크로드의 메모리 소비는 실행 중에 변하기 때문에, 사용자는 종종 리밋을 오버커밋해야 한다. 하지만 strict upper limit를 두고 오버커밋을 하려면, 워킹 셋 크기를 상당히 정확하게 예측하거나, 리밋에 여유분을 두어야 한다. 워킹 셋 추정은 어렵고 오류가 발생하기 쉬우며, 잘못 추정하면 OOM kill로 이어지기 때문에, 대부분의 사용자는 여유분을 넉넉하게 잡는 편을 택하고, 그 결과 메모리가 낭비되는 경우가 많다.
반면 memory.high 경계는 훨씬 보수적으로 설정할 수 있다. 이 경계를 넘으면, 해당 cgroup의 할당은 direct 리클레임을 강제함으로써 초과분을 정리하게 되지만, OOM killer는 호출되지 않는다. 따라서, 너무 공격적인 high 경계를 설정하더라도 프로세스가 종료되지는 않으며, 대신 점진적인 성능 저하가 발생한다. 사용자는 이를 모니터링하면서, 허용 가능한 최소 메모리 footprint를 찾을 때까지 경계를 조정할 수 있다.
극단적인 경우, 많은 동시 할당과 cgroup 내부 리클레임 실패로 인해 progress가 완전히 멈추면, high 경계가 초과될 수 있다. 그러나 이 경우에도, 시스템의 나머지 부분 또는 다른 cgroup에서 사용 가능한 여유분을 사용해 할당을 만족시키는 편이, 그룹을 kill하는 것보다 대개 낫다. 그렇지 않으면, memory.max를 통해 이러한 spillover를 제한하고, 최종적으로는 버그가 있거나 악의적인 애플리케이션을 격리할 수 있다.
기존 memory.limit_in_bytes를 현재 사용량보다 낮게 설정하는 작업은, 동시 charge 때문에 설정이 실패할 수 있는 레이스 조건에 취약했다. 반면 memory.max는, 먼저 새 리밋을 설정하여 새로운 charge를 막고, 그 다음 리클레임 및 OOM kill을 수행해 새 리밋이 만족될 때까지 진행한다(또는 memory.max를 쓰려던 태스크가 kill될 수 있다).
기존의 메모리+swap 결합 계정 및 제한 기능은, 실제 swap 공간 제어로 대체되었다.
기존 설계에서 결합된 메모리+swap 기능에 대한 주된 논거는, 글로벌 또는 상위 cgroup 압력 하에서, 자식 그룹(잠재적으로 신뢰할 수 없는 설정을 가지는)의 익명 메모리를 해당 자식 설정과 관계없이 언제나 swap-out할 수 있어야 한다는 것이었다. 하지만, 신뢰할 수 없는 그룹은, 자신의 익명 메모리를 tight loop로 반복 참조하는 등의 방식으로, 다른 그룹의 swap-out을 방해할 수 있으며, 관리자는 신뢰할 수 없는 작업을 오버커밋할 때도 항상 완전한 swappability를 기대할 수는 없다.
한편, 신뢰할 수 있는 작업에 대해서는, 결합된 카운터는 직관적인 사용자 공간 인터페이스가 아니며, cgroup 컨트롤러가 특정 물리 자원을 계정하고 제한해야 한다는 기본 원칙과도 어긋난다. swap 공간은 시스템의 다른 자원과 마찬가지로 하나의 자원이며, unified 계층에서는 이를 별도로 분배할 수 있도록 한다.