boot_cpu_init()

<kernel v5.0>

CPU 상태 관리

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

  • CPU 상태는 4개의 비트맵 변수를 사용하여 Bitmap Operations 관련 함수로 관리된다.
  • CPU 상태 관련한 비트맵 전역 변수들은 배열로 관리하기 때문에 최대 수와 관련 없이 지원 가능하다.
  • SMP 지원을 위해 CPU 최대 수가 점차 늘어나고 있으며 현재 커널 5.0 기준 아키텍처별 최대 CPU 수
    • arm
      • 2~32, default: 4
    • arm64
      • 2~4096, defatult: 4
    • 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

 

특정 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);
}

부트업 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~31)를 지정한다.

include/linux/cpumask.h

static inline void
set_cpu_online(unsigned int cpu, bool online)
{
        if (online)
                cpumask_set_cpu(cpu, &__cpu_online_mask);
        else
                cpumask_clear_cpu(cpu, &__cpu_online_mask);
}

각 CPU 상태 비트맵 전역 변수

  • CPU 상태는 5개의 비트맵으로 관리되는 전역변수로 정의되어 있다.
    • unsigned long cpu_all_bits[]
    • unsigned long __cpu_possible_mask[]
    • unsigned long __cpu_online_mask[]
    • unsigned long __cpu_present_mask[]
    • unsigned long __cpu_active_mask[]

 

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);

 

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

 

CPU 비트맵


DECLARE_BITMAP()

include/linux/types.h

#define DECLARE_BITMAP(name,bits) \
        unsigned long name[BITS_TO_LONGS(bits)]

@name이라는 이름으로 unsigned long 타입의 배열이다. 배열 크기는 @bits를 처리할 수 있을 만큼의 unsigned long 수  만큼 지정한다.

  • 예) name=foo, bits=65, ARM
    • unsigned long foo[4]
  • 예) name=foo, bits=65, ARM64
    • unsigned long foo[2]

 

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

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

static inline unsigned int cpumask_check(unsigned int cpu)
{
#ifdef CONFIG_DEBUG_PER_CPU_MAPS
        WARN_ON_ONCE(cpu >= nr_cpumask_bits);
#endif /* CONFIG_DEBUG_PER_CPU_MAPS */
        return cpu;
}

cpu라는 비트맵 인수가 최대 CPU 처리 수를 넘는지 체크한다.

 

cpumask_bits()

include/linux/cpumask.h

#define cpumask_bits(maskp) ((maskp)->bits)

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

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

 

cpumask_t 구조체

include/linux/cpumask.h

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 비트맵을 선언한다.

 

BITS_TO_LONGS()

include/linux/bitops.h

#define BITS_TO_LONGS(nr)       DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

인수에 해당하는 비트맵이 long data 형으로 몇 개가 필요한지 리턴

  • 예)  nr=128, ARM
    • 4
  • 예)  nr=128, ARM64
    • 2

 

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))

 

BITMAP_LAST_WORD_MASK()

include/linux/bitmap.h

#define BITMAP_LAST_WORD_MASK(nbits)                                    \
(                                                                       \
        ((nbits) % BITS_PER_LONG) ?                                     \
                (1UL<<((nbits) % BITS_PER_LONG))-1 : ~0UL               \
)

cpu 수에 대한 마스크 값을 반환한다. 단 unsigned long 비트 수를 넘어가는 비트의 경우 마지막 unsigned long에 대한 마스크만 반환한다.

32비트 시스템에서 인수에 따른 결과는

    • nbits = 0: 0xffff_ffff (0이 아님에 주의)
    • nbits = 1: 0x0000_0001
    • nbits = 16: 0x0000_ffff
    • nbits = 32: 0xffff_ffff
    • nbits = 40: 0x0000_000f

 

64비트 시스템에서 인수에 따른 결과는

    • nbits = 0: 0xffff_ffff_ffff_ffff (0이 아님에 주의)
    • nbits = 1: 0x0000_0000_0000_0001
    • nbits = 16: 0x0000_0000_0000_ffff
    • nbits = 32: 0x0000_0000_ffff_ffff
    • nbits = 40: 0x0000_00ff_ffff_ffff
    • nbits = 66: 0x0000_0000_0000_0003

 

참고

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다