boot_cpu_init()

<kernel v5.10>

CPU 상태 관리

CPU 상태를 관리하는 몇 개의 전역 변수에 현재 CPU의 상태를 설정한다.

  • CPU 상태는 4개의 비트맵 변수를 사용하여 Bitmap Operations 관련 함수로 관리된다.
  • CPU 상태 관련한 비트맵 전역 변수들은 배열로 관리하기 때문에 최대 수와 관련 없이 지원 가능하다.
  • SMP 지원을 위해 CPU 최대 수가 점차 늘어나고 있으며 현재 커널 5.0 기준 아키텍처별 최대 CPU 수
    • arm
      • 2~32, default: 4
    • arm64
      • 2~4096, defatult: 256
    • x86_32
      • 2~8, default: 8
      • big-smp: 2~64, default: 32
    • x86_64
      • 2~512, default: 64
      • big-smp: 2~8192, default: 8192
    • mips
      • 2~64, default: 커널 옵션에 따라 4, 8, 16, 32, 64
      • big-smp: 2~1024, default: 1024
    • powerpc
      • 커널 옵션에 따라 128, 2048

 

CPU 상태

CPU 마다 다음 4 가지 상태 여부를 저장할 수 있는 비트맵들을 가지고 있다.

  •  online
    • 현재 online 상태이고 태스크가 러닝 가능한 상태
    • cpu up 시 online이 먼저 설정된다.
  • active
    • 현재 스케줄러 active되어 동작 가능한 상태
      • CPU가 hotplug되어 준비된 상태(present=true, active=false, online=false)에서 cpu up 하면 다음 두 단계로 나뉘어 상태가 바뀐다.
        • 스케쥴링은 정지된 상태에서 태스크는 러닝 가능한 상태(active=false, online=true)
        • 이어서 스케줄링 가능한 상태(active=true, online=true)로 바뀜
      • 스케쥴링 상태(online=true, active=true, online=true)에서 cpu down 하면 두 단계로 나뉘어 상태가 바뀐다.
        • 스케줄링은 정지된 상태에서 태스크는 러닝 가능한 상태(active=false, online=true)
        • 이어서 완전히 기동을 멈춘 상태(active=false, online=false)로 바뀜
    • runqueue migration 등에 사용됨.
    • cpu down 시 active가 먼저 클리어된다.
  • present 
    • 장착되어 cpu가 인식된 상태
    • 현재 시스템에서 CPU를 인식한 경우에만 true이며, possible의 부분집합
    • 처음 hotplug되기 전 상태는 online과 active는 둘 다 false 상태임.
    • 시스템에 CPU의 허용치 이내(possible=true)에서 CPU 카드를 hotplug한 경우 CPU가 정상 인식된 경우 가용 가능한 CPU 자원으로 만든다.(present=true)
  • possible
    • 현재 동작 또는 hot-plug 가능한 상태
    • 현재 동작하거나 확장할 수 있는 상태비트로 부팅 시 고정되는 비트로 앞으로 상태를 바꿀 수 없다.
    • 향후 hotplug될 카드의 CPU 수 만큼 미리 설정해 놓아야한다.
    • 시스템에 CPU 카드를 허용치 이상 hotplug 시킨 경우(해당 CPU의 possible 상태가 false) present로 바뀌지 않는다.
    • NR_CPUS 를 초과할 수 없음

 

CPU Hotplug

  • kernel 3.8 에 x86 generic이 올라가서 x86 프로세서에서 처음 적용되었다.
  • CPU 다운을 시키는 경우 먼저 active가 false가 되고 태스크의 수행이 완료되는 대로 스케줄러가 runqueue에 있는 태스크를 제거하여 online 마저 false가 된다.

 

다음 그림은 커널 버전과 hotplug 커널 옵션 여부에 따른 cpu 전환 상태 다이어그램을 보여준다.

 

CPU hotplug 시나리오 (CPU: 4 → 5)
  • 최대 CPU를 8개로 설정(NR_CPUS=8, 최대 8개 CPU까지 확장 가능하다는 의미)
  • 각 CPU 상태 비트들은 8개가 사용된다.
  • 부팅 시 4개 CPU가 동작하고 hotplug로 추가 1개의 CPU를 추가하는 시나리오
 *                      possible        present         active          online
 *                      --------------------------------------------------------
 * 1. 첫 부팅 후:       11111111        00001111        00001111        00001111
 * 2. CPU 카드 추가:    11111111        00011111        00001111        00001111
 * 3. CPU 업 요청:      11111111        00011111        00001111        00011111
 *    런큐 active       11111111        00011111        00011111        00011111 
 * 4. CPU 다운 요청:    11111111        00011111        00011111        00011111
 *    런큐 deactive     11111111        00011111        00001111        00011111
 * 5. CPU 카드 제거:    11111111        00001111        00001111        00001111

 

명령으로 CPU 동작 상태 변경

 현재 상태 확인

Intel Core-i5 노트북에서 확인 시
  • 2개의 코어 x 2개의 스레드(하이퍼 스레드 기능) = total 4개의 CPU가 동작하는 것으로 나타남.

 

$ cd /sys/devices/system/cpu
$ cat kernel_max
255
$ cat offline

$ cat online
0-3
$ cat present
0-3
$ cat possible
0-3
$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 61
Model name:            Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
Stepping:              4
CPU MHz:               2200.257
CPU max MHz:           2700.0000
CPU min MHz:           500.0000
BogoMIPS:              4390.00
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              3072K
NUMA node0 CPU(s):     0-3

 

라즈베리파이2에서 확인 시
  • ARM Cortex A7이 4 코어가 동작
  • NR_CPUS=4, HOTPLUG_CPU=n

 

$ cd /sys/devices/system/cpu
$ cat kernel_max
3 
$ cat offline

$ cat online
0-3
$ cat present
0-3
$ cat possible
0-3
$ lscpu
Architecture:          armv7l
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    1
Core(s) per socket:    4
Socket(s):             1

 

라즈베리파이4에서 확인 시
  • ARM Cortex A72 4 코어가 동작
  • NR_CPUS=4, HOTPLUG_CPU=n

 

$ cd /sys/devices/system/cpu
$ cat kernel_max
63
$ cat offline

$ cat online
0-3
$ cat present
0-3
$ cat possible
0-3
$ lscpu
Architecture:        aarch64
Byte Order:          Little Endian
CPU(s):              4
On-line CPU(s) list: 0-3
Thread(s) per core:  1
Core(s) per socket:  4
Socket(s):           1
Vendor ID:           ARM
Model:               3
Model name:          Cortex-A72
Stepping:            r0p3
CPU max MHz:         1500.0000
CPU min MHz:         600.0000
BogoMIPS:            108.00
Flags:               fp asimd evtstrm crc32 cpuid

 

특정 CPU 코어 기동 정지 및 기동

CPU 1을 offline 상태로 변경

echo 0 > /sys/devices/system/cpu/cpu1/online
CPU1: shutdown
psci: CPU1 killed

 

CPU 1을 online 상태로 변경

echo 1 > /sys/devices/system/cpu/cpu1/online
Detected VIPT I-cache on CPU1
CPU1: Booted secondary processor 0x0000000001 [0x410fd034]

 

CPU 상태 확인

$ cd /sys/devices/system/cpu
$ cat offline
2
$ cat online
0-1,3
$ cat possible
0-3
$ cat present
0-3
$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0,1,3
Off-line CPU(s) list:  2
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 61
Model name:            Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
Stepping:              4
CPU MHz:               2200.000
CPU max MHz:           2700.0000
CPU min MHz:           500.0000
BogoMIPS:              4390.00
Virtualization:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              3072K
NUMA node0 CPU(s):     0,1,3

 

초기화

부트업 과정 중에 cpu의 초기화는 다음과 같이 두 가지로 나뉜다.

  • 부트 cpu에서 초기화
    • start_kernel() -> boot_cpu_init() -> 4 가지 상태를 true로 변경한다.
    • start_kernel() -> setup_arch() -> smp_init_cpus() -> smp_cpu_setup() -> possible 상태를 true로 변경한다.
    • kernel_init() 스레드 -> kernel_init_freeable() ->  smp_prepare_cpus() -> present 상태를 true로 변경한다.
  • secondary cpu up 후 초기화
    • secondary_start_kernel() -> online 상태를 true로 변경한다.

 

CPU 상태 변경

이 후 cpu 상태의 변화는 cpu-hotplug 및 IPI(Inter Process Interrupt) 요청에 따라 수행된다.

  • cpu-hotplug
    • cpu active
      • sched_cpu_activate() -> active 상태를 true로 변경한다.
    • cpu deactive
      • sched_cpu_deactivate() -> active 상태를 false로 변경한다.
    • cpu disable
      • takedown_cpu() -> take_cpu_down() -> __cpu_disable() -> online 상태를 false로 변경한다.
  • IPI
    • cpu stop
      • ipi_cpu_stop() -> online 상태를 false로 변경한다.

 

boot_cpu_init()

kernel/cpu.c

/*
 *      Activate the first processor.
 */
static void __init boot_cpu_init(void)
{
        int cpu = smp_processor_id();

        /* Mark the boot cpu "present", "online" etc for SMP and UP case */
        set_cpu_online(cpu, true);
        set_cpu_active(cpu, true);
        set_cpu_present(cpu, true);
        set_cpu_possible(cpu, true);

#ifdef CONFIG_SMP
        __boot_cpu_id = cpu;
#endif
}

부트업 cpu의 4 가지 상태를 모두 true로 설정한다.

  • CPU 상태를 관리하는 몇 개의 비트맵 전역 변수에 현재 CPU의 상태를 설정한다.
  • int cpu = smp_processor_id();
    • 현재 동작하는 프로세서 번호가 리턴 (0 부터~~)
  • 4 가지 cpu 상태를 true로 설정하는데 유형이 모두 같으므로 set_cpu_online() 함수를 위주로 분석한다.

 

boot_cpu_init_a

 

set_cpu_online()

  • 아래 소스는 몇 개의 CPU 상태 조작 함수 중 CPU를 online 또는 offline 상태로 변경하는 함수이다.
  • 인수로 사용하는 cpu는 변경하고자 하는 cpu 번호(0~)를 지정한다.

kernel/cpu.c

void set_cpu_online(unsigned int cpu, bool online)
{
        /*
         * atomic_inc/dec() is required to handle the horrid abuse of this
         * function by the reboot and kexec code which invoke it from
         * IPI/NMI broadcasts when shutting down CPUs. Invocation from
         * regular CPU hotplug is properly serialized.
         *
         * Note, that the fact that __num_online_cpus is of type atomic_t
         * does not protect readers which are not serialized against
         * concurrent hotplug operations.
         */
        if (online) {
                if (!cpumask_test_and_set_cpu(cpu, &__cpu_online_mask))
                        atomic_inc(&__num_online_cpus);
        } else {
                if (cpumask_test_and_clear_cpu(cpu, &__cpu_online_mask))
                        atomic_dec(&__num_online_cpus);
        }
}

 

각 CPU 상태 비트맵 전역 변수

  • CPU 상태는 5개의 비트맵으로 관리되는 전역변수로 정의되어 있다.

kernel/cpu.c

const DECLARE_BITMAP(cpu_all_bits, NR_CPUS) = CPU_BITS_ALL;
EXPORT_SYMBOL(cpu_all_bits);

#ifdef CONFIG_INIT_ALL_POSSIBLE
struct cpumask __cpu_possible_mask __read_mostly
        = {CPU_BITS_ALL};
#else
struct cpumask __cpu_possible_mask __read_mostly;
#endif
EXPORT_SYMBOL(__cpu_possible_mask);

struct cpumask __cpu_online_mask __read_mostly;
EXPORT_SYMBOL(__cpu_online_mask);

struct cpumask __cpu_present_mask __read_mostly;
EXPORT_SYMBOL(__cpu_present_mask);

struct cpumask __cpu_active_mask __read_mostly;
EXPORT_SYMBOL(__cpu_active_mask);
  • cpu_all_bits라는 이름으로 비트맵을 선언한다. 배열 크기는 NR_CPUS를 처리할 수 있을 만큼의 unsigned long 수  만큼 지정한다.
    • 예) NR_CPUS=96, ARM
      • unsigned long cpu_all_bits[3]
    • 예) NR_CPUS=96, ARM64
      • unsigned long cpu_all_bits[2]

 

다음 그림은 2개의 cpu를 가진 ARM64 시스템이 부팅된 후의 모습을 보여준다.

 


CPU 비트맵

 

CPU_BITS_ALL 매크로

include/linux/cpumask.h

#if NR_CPUS <= BITS_PER_LONG
#define CPU_BITS_ALL                                            \
{                                                               \
        [BITS_TO_LONGS(NR_CPUS)-1] = BITMAP_LAST_WORD_MASK(NR_CPUS)     \
}

#else /* NR_CPUS > BITS_PER_LONG */

#define CPU_BITS_ALL                                            \
{                                                               \
        [0 ... BITS_TO_LONGS(NR_CPUS)-2] = ~0UL,                \
        [BITS_TO_LONGS(NR_CPUS)-1] = BITMAP_LAST_WORD_MASK(NR_CPUS)     \
}
#endif /* NR_CPUS > BITS_PER_LONG */

CPU 비트맵의 모든 비트를 1로 할 수 있는 상수를 계산하여 컴파일 타임에 static하게 unsigned long 배열로 제공한다.

  • 예) NR_CPUS=4, ARM
    • { [0] = 0x0000_000f }
  • 예) NR_CPUS=8, ARM64
    • { [0] = 0x0000_0000_0000_00ff }
  • 예) NR_CPUS=65, ARM64
    • { [0] = 0xffff_ffff_ffff_ffff, [1] = 0x0000_0000_0000_0001 }

 

CPU_MASK_ALL 매크로

include/linux/cpumask.h

#if NR_CPUS <= BITS_PER_LONG
#define CPU_MASK_ALL                                                    \
(cpumask_t) { {                                                         \
        [BITS_TO_LONGS(NR_CPUS)-1] = BITMAP_LAST_WORD_MASK(NR_CPUS)     \
} }
#else
#define CPU_MASK_ALL                                                    \
(cpumask_t) { {                                                         \
        [0 ... BITS_TO_LONGS(NR_CPUS)-2] = ~0UL,                        \
        [BITS_TO_LONGS(NR_CPUS)-1] = BITMAP_LAST_WORD_MASK(NR_CPUS)     \
} }
#endif /* NR_CPUS > BITS_PER_LONG */

CPU_BITS_ALL 매크로와 유사하나 cpumask_t 구조체 타입으로 반환한다.

 

다음 그림은 CPU_MASK_ALL 매크로가 사용되었을 때 선언되는 cpu 비트맵을 보여준다.

 

cpumask_set_cpu()

include/linux/cpumask.h

/**
 * cpumask_set_cpu - set a cpu in a cpumask
 * @cpu: cpu number (< nr_cpu_ids)
 * @dstp: the cpumask pointer
 */
static inline void cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp)
{
        set_bit(cpumask_check(cpu), cpumask_bits(dstp));
}

@dstp 비트맵에서 @cpu 비트를 1로 설정한다.

  • dstp는 설정하고자(dest) 하는 비트맵 전역 변수를 가리키는 cpumask 구조체 포인터이다.
  • set_bit()라는 Bitmap Operations  함수를 호출하여 dstp 비트맵에서 cpu에 해당하는 비트를 1로 atomic 하게 설정한다.

 

cpumask_check()

include/linux/cpumask.h

/* verify cpu argument to cpumask_* operators */
static inline unsigned int cpumask_check(unsigned int cpu)
{
        cpu_max_bits_warn(cpu, nr_cpumask_bits);
        return cpu;
}

@cpu 번호가 최대 CPU 처리 수를 넘는지 체크한다.

 

cpumask_bits()

include/linux/cpumask.h

/**
 * cpumask_bits - get the bits in a cpumask
 * @maskp: the struct cpumask *
 *
 * You should only assume nr_cpu_ids bits of this mask are valid.  This is
 * a macro so it's const-correct.
 */
#define cpumask_bits(maskp) ((maskp)->bits)

cpumask 구조체의 비트맵을 가리킨다.

  • maskp는 cpumask 구조체 포인터이다.

 

cpumask_t 구조체

include/linux/cpumask.h

/* Don't assign or return these: may not be this big! */
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;

NR_CPUS 수 만큼을 처리할 수 있는 cpu 비트맵을 static하게 준비한다.

  • 예) NR_CPUS=8, ARM
    • unsigned long(4 바이트) 타입 1 개(총 4바이트)를 사용하여 cpu 비트맵을 선언한다.
  • 예) NR_CPUS=65, ARM64
    • unsigned long(8 바이트) 타입 2 개(총 16바이트) 를 사용하여 cpu 비트맵을 선언한다.

 

to_cpumask()

include/linux/cpumask.h

/**
 * to_cpumask - convert an NR_CPUS bitmap to a struct cpumask *
 * @bitmap: the bitmap
 *
 * There are a few places where cpumask_var_t isn't appropriate and
 * static cpumasks must be used (eg. very early boot), yet we don't
 * expose the definition of 'struct cpumask'.
 *
 * This does the conversion, and can be used as a constant initializer.
 */
#define to_cpumask(bitmap)                                              \
        ((struct cpumask *)(1 ? (bitmap)                                \
                            : (void *)sizeof(__check_is_bitmap(bitmap))))

static inline int __check_is_bitmap(const unsigned long *bitmap)
{
        return 1;
}

인수를 cpumask 구조체 포인터로 변환하여 리턴 (타입 캐스트)

  • 항상 true인데 왜 false에 대한 루틴을 호출하게 놓았을까?
    • 실제 실행되지 않지만 컴파일 시 unsigned long * 타입이 아닌 경우 컴파일 오류 발생 목적

 

DIV_ROUND_UP()

include/linux/kernel.h

#define DIV_ROUND_UP(x, y)  (((x) + (y) - 1) / (y))

@x 값을 @y 단위로 올림 정렬한다.

 

참고

 

Docker vs LXC vs Hypervisor

  • LXC는 cgroup과 namespace기반으로 동작한다.
  • Docker에서 사용하는 container의 경우 0.9 버전 이전에는 LXC의 것을 사용했었는데 이제는 자체 container를 개발하여 사용하게 되었다.

docker

참고

Control Groups

Control Groups

History

  • 2006년 구글 엔지니어가 시작
  • 커널 v2.6.24에 merge되고 LXC, systemd 등으로 확산
  • 커널 v4.5에 cgroup v2 merge
    • 현재 v1에 있는 12개의 서브시스템(컨트롤러) 중 I/O, memory와 PID 컨트롤러가 먼저 포함
    • cgroup v1과 일단 같이 공존

 

특징

  • cgroup은 태스크들 세트의 aggregating/partitioning 매커니즘이며, 태스크들 세트에는 계층화 그룹으로 관리.
  • cgroup은 파일시스템이며 마운트하여 사용
    • mount -t tmpfs cgroup_root /sys/fs/cgroup
    • mkdir /sys/fs/cgroup/cpuset
    • mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
  • cgroup 자체만으로는 동작 중인 task의 그루핑만 한다.
  • 자원(cpu, memory, disk, device)등의 분배는 subsystem이 필요

 

기능

  • Access: cgroup별 디바이스들의 사용
  • Resource limiting: 자원(memory, cpu, device, …)의 사용제한
  • Prioritization: 누구에게 CPU와 메모리를 더 많이…
  • Accounting: cgroup별 자원 사용량
  • Control: freezing & check pointing
  • Injection: 패킷 태깅

 

사용 방법

  • 사용하고자 하는 서브시스템들을 각각 mount한 후 해당 cgroup에 대한 서브 디렉토리를 만들고 속성 파일을 설정하여 사용하는 방법
    • 예) mount -t cgroup -o net_prio none /sys/fs/cgroup/net_prio
  • cgroup-tools(libcgroup)를 설치하여 사용하는 방법
    • 예) apt install cgroup-tools
    • 예) yum install libcgroup

 

Cgroup Tool 명령어

다음은 cgroup을 관리하는 명령어들이다.

  • cgclassify
    • 동작 중인 태스크를 지정한 cgroup으로 이동
  • cgclear
    • cgroup 파일 시스템을 언로드
  • cgcreate
    • 새로운 cgroup을 생성
  • cgdelete
    • cgroup을 삭제
  • cgexec
    • 태스크를 지정한 cgroup에서 실행
  • cgget
    • 지정한 cgroup의 파라미터를 출력
  • cgset
    • 지정한 cgroup의 파라미터를 설정
  • cgsnapshot
    • 지정한 cgroup의 설정 파일을 생성
  • lscgroup
    • 모든 cgroup들을 리스트
  • lssubsys
    • 지정한 서브시템에 포함된 cgroup 계층화 리스트
  • cgconfigparser
    • cgroup 파일 시스템 셋업

 

다음 파일은 libcgroup 설정 파일이다.

  • cgconfig.conf

 

subsystem (Controller)

커널 v5.10 기준 14개의 서브시스템을 지원한다.

  • cpuset, cpu, cpuacct, io, memory, devices, freezer, net_cls, perf_event, net_prio, hugetlb, pids, rdma, debug

 

cgroup1

 


장점

CPU 코어 할당

  • CPU 코어별 jobs 배치
  • 코어 스위칭 코스트 감소

cgroup2

 

CPU Usage 할당

  • jobs별 우선 순위
  • 리소스 세그멘테이션

cgroup3

 

Device Access

  • isolation (visibllity)
  • segmentation
  • whitelist/blacklist

cgroup4

 

계층 관리

cgroup5

 


서브시스템별 사용 사례

 

현 시스템 내부 subsystem 목록

현재 mount되어 사용가능한 cgroup 서브시스템 목록을 다음과 같이 3가지 방법으로 알아본다.

 

1. proc 인터페이스 사용
$ cat /proc/cgroups
#subsys_name    hierarchy       num_cgroups     enabled
cpuset  3       9       1
cpu     9       9       1
cpuacct 9       9       1
blkio   6       9       1
memory  4       9       1
devices 10      88      1
freezer 5       9       1
net_cls 8       9       1
perf_event      7       9       1
net_prio        8       9       1
hugetlb 2       9       1

 

2. lssubsys 명령어 사용
$ lssubsys
cpuset
cpu
cpuacct
blkio
memory
devices
freezer
net_cls
perf_event
net_prio
hugetlb
pids
rdma

 

3. 디렉토리를 출력
ls /sys/fs/cgroup/
blkio  cpu  cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  net_prio  perf_event  pids  rdma  unified

 

CONFIG_SCHED_AUTOGROUP

유저별 그룹화하여 사용할 수 있도록 사용자의 로긴/로그오프시에 cgroup이 자동으로 생성/소멸된다. 이 기능을 사용하는 경우 task별로 균등하게 자원을 배치하는 것을 유저별로 균등하게 자원을 배치할 수 있는 장점이 있다.

 

다음과 같은 계층 구조를 사용한다.

  • <subsystem>/system.slice
    • 시스템 서비스가 사용되는 cgroup 이다.
  • <subsystem>/user.slice/user-<유저번호>.slice/ session-c<세션번호>.scope
    • 유저와 세션에 따라 cgroup이 자동으로 추가된다.

memory 서브시스템

다음은 memory 서브시스템의 루트 디렉토리 내용을 보여준다.

$ ls /sys/fs/cgroup/memory
cgroup.clone_children  cpuacct.stat          cpu.cfs_period_us  cpu.stat           tasks
cgroup.procs           cpuacct.usage         cpu.cfs_quota_us   notify_on_release  user.slice
cgroup.sane_behavior   cpuacct.usage_percpu  cpu.shares         release_agent

 

메모리 배정

다음과 같이 cpuset 서브시스템에 A라는 cgroup을 생성하고 100M 메모리를 배정한 후, 특정 태스크(sample_app)를 A에 지정하여 실행한다.

$ cgcreate -g memory:/A

$ lscgroup -g memory:/
cpuset:/
cpuset:/A

$ cgset -r memory.limit_in_bytes=104857600 A
$ cgget -g memory:/A
/A:
/A:
memory.use_hierarchy: 1
memory.kmem.tcp.usage_in_bytes: 0
memory.soft_limit_in_bytes: 9223372036854771712
memory.force_empty:
memory.pressure_level:
memory.move_charge_at_immigrate: 0
memory.kmem.tcp.max_usage_in_bytes: 0
memory.max_usage_in_bytes: 0
memory.oom_control: oom_kill_disable 0
        under_oom 0
        oom_kill 0
memory.stat: cache 0
        rss 0
        rss_huge 0
        shmem 0
        mapped_file 0
        dirty 0
        writeback 0
        pgpgin 0
        pgpgout 0
        pgfault 0
        pgmajfault 0
        inactive_anon 0
        active_anon 0
        inactive_file 0
        active_file 0
        unevictable 0
        hierarchical_memory_limit 104857600
        total_cache 0
        total_rss 0
        total_rss_huge 0
        total_shmem 0
        total_mapped_file 0
        total_dirty 0
        total_writeback 0
        total_pgpgin 0
        total_pgpgout 0
        total_pgfault 0
        total_pgmajfault 0
        total_inactive_anon 0
        total_active_anon 0
        total_inactive_file 0
        total_active_file 0
        total_unevictable 0
memory.kmem.slabinfo:
memory.limit_in_bytes: 1048576000
memory.swappiness: 60
memory.kmem.failcnt: 0
memory.kmem.max_usage_in_bytes: 0
memory.usage_in_bytes: 0
memory.failcnt: 0
memory.kmem.tcp.failcnt: 0
memory.kmem.limit_in_bytes: 9223372036854771712
memory.kmem.usage_in_bytes: 0
memory.kmem.tcp.limit_in_bytes: 9223372036854771712

$ cgexec -g memory:/A sample_app

 

Swapness 설정

글로벌 설정은 다음 proc 인터페이스를 통해 변경할 수 있다.

  • proc/sys/vm/swappiness

cgroup별로도 Swapness를 설정할 수 있다.

  • <memcg>/memory.swappiness

 

VMPressure 설정

 


cpuset 서브시스템

 

Core 배정

다음과 같이 cpuset 서브시스템에 A라는 cgroup을 생성하고 2~3번 core를 배정한 후, 특정 태스크(sample_app)를 A에 지정하여 실행한다.

  • cpuset.mems에도 사용할 노드 번호를 지정해줘야 한다.
$ cgcreate -g cpuset:/A

$ lscgroup -g cpuset:/
cpuset:/
cpuset:/A

$ cgset -r cpuset.cpus="2-3" A
$ cgset -r cpuset.mems=0 A
$ cgget -g cpuset:/A
/A:
cpuset.memory_pressure: 0
cpuset.memory_migrate: 0
cpuset.mem_exclusive: 0
cpuset.memory_spread_slab: 0
cpuset.cpu_exclusive: 0
cpuset.effective_mems: 0
cpuset.effective_cpus: 2-3
cpuset.sched_load_balance: 1
cpuset.mems: 0
cpuset.mem_hardwall: 0
cpuset.sched_relax_domain_level: -1
cpuset.cpus: 2-3
cpuset.memory_spread_page: 0

$ cgexec -g cpuset:/A sample_app

 


cpu 서브시스템

 

커널/유저 사용 시간 확인

커널이 사용한 시간과 유저가 사용한 시간을 틱 수로 보여준다.

$ cat /sys/fs/cgroup/cpu/A/cpuacct.stat
user 47289
system 5

 

cpu 점유율(shares) 배정

  • A , B 두 그룹을 만들고 일반 태스크의 우선 순위(nice 0)보다 A 그룹에 2배의 우선 순위(nice -3과 유사), B 그룹에 4배의 우선 순위(nice -6)를 배정하였다.
$ cgcreate -g cpu:/A
$ cgcreate -g cpu:/B 
$ lscgroup -g cpu:/
cpu,cpuacct:/
cpu,cpuacct:/A
cpu,cpuacct:/B
$ cgset -r cpu.shares=2048 A
$ cgset -r cpu.shares=4096 B
$ cgget -g cpu:/A
/A:
cpu.cfs_period_us: 100000
cpu.stat: nr_periods 0
        nr_throttled 0
        throttled_time 0
cpu.shares: 2048
cpu.cfs_quota_us: -1
$ cgclassify -g cpu:/A <A 그룹에 배정할 pid들>
$ cgclassify -g cpu:/B <B 그룹에 배정할 pid들>

 

CFS 태스크의 Bandwidth 배정

$ cgcreate -g cpu:/A

$ lscgroup -g cpu:/
cpu,cpuacct:/
cpu,cpuacct:/A
$ cgset -r cpu.cfs_period_us=100000 A
$ cgset -r cpu.cfs_quota_us=50000 A
$ cgget -g cpu:/A
/A:
cpu.cfs_period_us: 100000
cpu.stat: nr_periods 2
        nr_throttled 0
        throttled_time 0
cpu.shares: 1024
cpu.cfs_quota_us: 50000
$ cgexec -g cpu:/A sample_app

 

그룹 RT Bandwidth 설정

 

Uclamp 설정

다음 속성 파일들을 사용한다.

  • <cpucg>/cpu.uclamp.{min,max}
    • 0 ~ max
  • /proc/sys/kernel/sched_util_clamp_util_{min,max}
    • 1024

 


구조

cgroup6

 

cgroup7

 


 

 

참고

 

 

Control Group v2

 

cgroup_init_early()

<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 구현에서 사용된다.
  • 코드 라인 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 마다 생성된다.
  • 코드 라인 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

 

참고