might_sleep()

기능

  • CONFIG_PREEMPT_VOLUNTARY 옵션이 동작할 때에만 동작하는 Preemption 포인트
  • 현재 태스크보다 더 높은 우선 순위의 태스크를 빨리 처리할 수 있도록 중간에 리스케쥴링을 허용한다.
  • 리스케쥴링하는 경우 현재 태스크가 preemption되며 이 때 sleep이 일어나게 된다.
  • Preemption Points
    • 리눅스커널은 이렇게 preemption 포인트를 커널의 여러 곳에 뿌려(?) 놓았다.
    • preemption 포인트의 도움을 받아 급한 태스크의 가동에 필요한 latency가 100us 이내로 줄어드는 성과가 있었다.
    • preemption 포인트는 보통 1ms(100us) 이상 소요되는 루틴에는 보통 추가하여 놓는다.

소스 분석

might_sleep()

might_sleep

아래 소스에서 CONFIG_DEBUG_ATOMIC_SLEEP 옵션이 있는 경우에만 디버그를 위해 __might_sleep()루틴을 호출한다.

#ifdef CONFIG_DEBUG_ATOMIC_SLEEP
/**
* might_sleep - annotation for functions that can sleep
*
* this macro will print a stack trace if it is executed in an atomic
* context (spinlock, irq-handler, ...).
*
* This is a useful debugging help to be able to catch problems early and not
* be bitten later when the calling function happens to sleep when it is not
* supposed to.
*/
# define might_sleep() \
        do { __might_sleep(__FILE__, __LINE__, 0); might_resched(); } while (0)
#else
# define might_sleep() do { might_resched(); } while (0)
#endif

might_resched()는 CONFIG_PREEMPT_VOLUNTARY 옵션이 있는 경우에만 _cond_resched()루틴을 호출하고 없는 경우 아무것도 하지 않는다.

include/linux/kernel.h

#ifdef CONFIG_PREEMPT_VOLUNTARY
# define might_resched() _cond_resched()
#else
# define might_resched() do { } while (0)
#endif
_cond_resched() 함수에서는 preemption이 필요한 상황인 경우 preemption 되며 sleep 한다.
  • should_resched()함수는 preemption 가능한 상태(preempt count가 0)이면서 현재 태스크보다 더 높은 우선 순위를 갖은 태스크가 있는 경우에 true
  • preempt_schedule_commoon() 함수는 태스크의 스케쥴링을 다시 한다.

include/linux/kernel.h

int __sched _cond_resched(void)
{
        if (should_resched()) {
                preempt_schedule_common();
                return 1;
        }
        return 0;
}

should_resched()

should_resched 함수는 리스케쥴이 필요한 상황에서 true로 리턴한다.
  • preemption이 enable(preempt_count()가 0) 이면서
  • 리스케쥴 요청이 있는 경우(tif_need_resched()가 true)

include/asm-generic/preempt.h

/*
 * Returns true when we need to resched and can (barring IRQ state).
 */
static __always_inline bool should_resched(void)
{
        return unlikely(!preempt_count() && tif_need_resched());
}
preempt_count()는 현재 스레드 정보(thread_info 구조체)에서 preempt_count를 리턴.
  • preempt_count 값은 preempt_enable() 호출 시 감소되며 preempt_disable() 호출 시 증가된다.
  • preempt_count 값이 0인 경우 preemption이 가능한 상태가 된다.

include/asm-generic/preempt.h

static __always_inline int preempt_count(void)
{
        return current_thread_info()->preempt_count;
}
tif_need_resched() 매크로는 -> test_thread_flag() 매크로를 호출하고 -> 다시 test_ti_thread_flag() 함수를 호출한다.
  • test_bit() 함수는 비트 operation에 사용되는 함수로 해당 비트가 설정되어 있는지를 체크하여 리턴한다.
  • 스레드 플래그
    • 여러 개의 많은 비트로 구성되어 있는데 그 중 TIF_NEED_RESCHED 비트는 리스케쥴이 필요한 경우 세트된다.

include/linux/thread_info.h

static inline int test_ti_thread_flag(struct thread_info *ti, int flag)
{
        return test_bit(flag, (unsigned long *)&ti->flags);
}

#define test_thread_flag(flag) \
        test_ti_thread_flag(current_thread_info(), flag)

#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)

preempt_schedule_common()

preempt_schedule_common() 함수는 실제 리스케쥴을 수행하는 함수이므로 preemption되는 경우 sleep이 일어나는 장소다.
이 함수는 리눅스 커널의 scheduler에 해당하는 주요 함수이므로 scheduler 부분을 분석해야 하므로 이 파트에서는 자세한 분석은 생략한다.
kernel/sched/core.c
static void __sched notrace preempt_schedule_common(void)
{
        do {
                __preempt_count_add(PREEMPT_ACTIVE);
                __schedule();
                __preempt_count_sub(PREEMPT_ACTIVE);

                /*
                 * Check again in case we missed a preemption opportunity
                 * between schedule and now.
                 */
                barrier();
        } while (need_resched());
}

참고

page_address_init()

<kernel v5.10>

페이지 주소 해시 테이블

페이지 주소 해시 테이블은 다음과 같은 용도로 사용된다.

  • 32비트 시스템에서 HIGHMEM 영역을 관리하기 위해 kmap(pkmap)용 hash table로 사용한다.
    • 커널이 사용할 용도의 메모리 할당을 위해 사용하는 영역은 lowmem 영역이다. 그런데 커널이 메모리 부족 시 highmem 영역을 이용하는 경우 이를 매핑하여 사용하기도 하는데, 이의 관리를 위해 사용한다.
    • 참고로 유저 사용 용도의 메모리 할당 역시 highmem을 우선 사용하지만 rmap 자료 구조를 사용하여 매핑된 사용자 주소 공간들을 알아올 수 있다.
  • 이 hash table은 128개의 hash 엔트리를 사용하여 page 주소로 virtual 주소를 빠르게 변환하여 알아올 수 있다.

 

다음 그림은 page 주소에 해당하는 가상 주소를 검색하기 위해 사용되는 페이지 주소 해시 테이블이다.

  • 해쉬 페이지 테이블은 128개의 배열 즉 128개의 해쉬 슬롯으로 이루어진다.
  • 각 해쉬 페이지 테이블 엔트리는 page_address_map 엔트리를 여러 개 리스트에 등록할 수 있다.

page_address_init-1

 

page_address_init()

mm/highmem.c

void __init page_address_init(void)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
                INIT_LIST_HEAD(&page_address_htable[i].lh);
                spin_lock_init(&page_address_htable[i].lock);
        }   
}

highmem을 사용하는 kmap의 빠른 페이지 검색을 위해 해시테이블을 초기화한다.

 

page_address_slot 구조체

mm/highmem.c

/*
 * Hash table bucket
 */
static struct page_address_slot {
        struct list_head lh;                    /* List of page_address_maps */
        spinlock_t lock;                        /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
  • PA_HASH_ORDER=7
    • 해쉬 테이블의 크기를 128개로 설정한다.
  • ____cacheline_aligned_in_smp
    • 성능을 위하여 해당 아키텍처의 L1 d-cache line 크기로 정렬하여 사용한다.
    • __attribute__((__aligned__(SMP_CACHE_BYTES)))
      • SMP_CACHE_BYTES=6
        • rpi2: ARMv7  L1 d-cache line = 64 bytes
  • lh
    • page_address_map 구조체를 여러 개 등록 관리하기 위한 리스트 헤더로 사용한다.
  • lock
    • lh 연결 리스트를 보호하기 위한 spin lock 용도이다.

 

page_address_map 구조체

mm/highmem.c

/*
 * Describes one page->virtual association
 */
struct page_address_map {
        struct page *page;
        void *virtual;
        struct list_head list;
};
  • page
    • 해당 page 구조체가 저장된 주소
  • virtual
    • 매핑된 virtual address
  • list
    • 이 페이지가 관리되고 있는 hash table의 위치

 

page_address_maps[] 전역 변수

mm/highmem.c

static struct page_address_map page_address_maps[LAST_PKMAP];
  • 이 테이블은 HIGHMEM 영역에 대한 L2 table로 kmap(pkmap)이 유지 보수 한다.
    • kmap(), kunmap() 함수를 사용하여 매핑 설정 및 해제된다.
  • ARM에서 L2용도로 사용하는 엔트리는 512개이다.
    • 매핑 영역의 크기는 총 2M 크기를 사용할 수 있다.
    • 매핑 영역의 시작 위치는 PAGE_OFFSET – 2M 이다.

 

page_slot()

mm/highmem.c

static struct page_address_slot *page_slot(const struct page *page)
{       
        return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}

page 구조체 주소로 hash(slot) 값을 알아와 해쉬 테이블의 인덱스로 사용하여 해당 해쉬 테이블의 구조체 주소를 리턴한다.

  • 예)
    • page = 0x10000000 -> hash slot index = 8

 

hash_ptr()

include/linux/hash.h

static inline unsigned long hash_ptr(const void *ptr, unsigned int bits)
{
        return hash_long((unsigned long)ptr, bits);
}
  • 주소 값을 사용하여 hash(slot) 값을 알아온다.
    • bits=7 인 경우 hash(slot) 값이 0~127의 값으로 리턴된다.

 

hash_long()

include/linux/hash.h

/*
 * The "GOLDEN_RATIO_PRIME" is used in ifs/btrfs/brtfs_inode.h and
 * fs/inode.c.  It's not actually prime any more (the previous primes
 * were actively bad for hashing), but the name remains.
 */
#if BITS_PER_LONG == 32
#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_32
#define hash_long(val, bits) hash_32(val, bits)
#elif BITS_PER_LONG == 64
#define hash_long(val, bits) hash_64(val, bits)
#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_64
#else
#error Wordsize not 32 or 64
#endif
  • 32비트/64비트 아키텍처에 따라 hash_32() 함수 또는 hash_64() 함수를 호출한다.

 

GOLDEN_RATIO_32 & GOLDEN_RATIO_64
/*
 * This hash multiplies the input by a large odd number and takes the
 * high bits.  Since multiplication propagates changes to the most
 * significant end only, it is essential that the high bits of the
 * product be used for the hash value.
 *
 * Chuck Lever verified the effectiveness of this technique:
 * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
 *
 * Although a random odd number will do, it turns out that the golden
 * ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice
 * properties.  (See Knuth vol 3, section 6.4, exercise 9.)
 *
 * These are the negative, (1 - phi) = phi**2 = (3 - sqrt(5))/2,
 * which is very slightly easier to multiply by and makes no
 * difference to the hash distribution.
 */
#define GOLDEN_RATIO_32 0x61C88647
#define GOLDEN_RATIO_64 0x61C8864680B583EBull

 

hash_32()

static inline u32 hash_32(u32 val, unsigned int bits)
{
        /* On some cpus multiply is faster, on others gcc will do shifts */
        u32 hash = val * GOLDEN_RATIO_PRIME_32;

        /* High bits are more random, so use them. */
        return hash >> (32 - bits);
}

val * 0x9e370001UL 값을 구한 후 bits 만큼 우측 쉬프트한 값을 리턴한다.

  • page_slot()에서 요청한 bits=7이므로 val * 0x9e370001UL 값을 구한 후 msb 7 bits를 우측으로 쉬프트한 후 리턴한다.
    • 결국 hash(slot) 값은 0~127이 리턴된다.
    • 예)
      • val=0x0 -> hash slot index=0
      • val=0x1 -> hash slot index=79
      • val=0x2 -> hash slot index=30
      • val=0x1000_0000 -> return=8
      • val=0x2000_0000 -> return=16

 

hash_64_generic()

include/linux/hash.h

#ifndef HAVE_ARCH_HASH_64
#define hash_64 hash_64_generic
#endif
static __always_inline u32 hash_64_generic(u64 val, unsigned int bits)
{
#if BITS_PER_LONG == 64
        /* 64x64-bit multiply is efficient on all 64-bit processors */
        return val * GOLDEN_RATIO_64 >> (64 - bits);
#else
        /* Hash 64 bits using only 32x32-bit multiply. */
        return hash_32((u32)val ^ __hash_32(val >> 32), bits);
#endif
}

 

참고

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 단위로 올림 정렬한다.

 

참고

 

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

 

참고