ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스 커널] SMP - cpumask
    Linux/kernel 2023. 8. 3. 02:33

    글의 참고

    - https://lwn.net/Articles/537570/

    - https://www.kernel.org/doc/html/v4.12/core-api/cpu_hotplug.html

    - https://forum.osdev.org/viewtopic.php?f=1&t=23445

    - http://www.wowotech.net/pm_subsystem/cpu_hotplug.html

    - https://zhuanlan.zhihu.com/p/536776611


    글의 전제

    - 밑줄로 작성된 글은 강조 표시를 의미한다.

    - 그림 출처는 항시 그림 아래에 표시했다.


    글의 내용

    - Overview

    " 리눅스 커널에서 `CPU hotplug` 기능이 지원되면서, 현재 동작중인 CPU 가 동적으로 Off 가 될 수 있게 되었다. 그리고, 당연히 Off 된 CPU 가 동적으로 다시 On 되는 것도 가능해졌다. 그런데, 이게 장점만 있는 것은 아니다. 동적으로 상태가 바뀌다 보니 CPU 상태를 정확하게 트래킹하기 위해서 몇 가지 상태들이 더 추가하게 되었다. 예를 들어, 기존에는 on & off 만 있어도 되는 상황에서 `동적 상태` 가 추가되다보니 `상태가 변하는 중` 을 나타낼 필요성이 있을 수 도 있다. 커널은 이러한 CPU 상태를 `bitmask` 자료 구조를 사용해서 관리한다.

    // include/linux/cpumask.h - v6.5
    /*
     * The following particular system cpumasks and operations manage
     * possible, present, active and online cpus.
     *
     *     cpu_possible_mask- has bit 'cpu' set iff cpu is populatable
     *     cpu_present_mask - has bit 'cpu' set iff cpu is populated
     *     cpu_online_mask  - has bit 'cpu' set iff cpu available to scheduler
     *     cpu_active_mask  - has bit 'cpu' set iff cpu available to migration
     *
     *  If !CONFIG_HOTPLUG_CPU, present == possible, and active == online.
     *
     *  The cpu_possible_mask is fixed at boot time, as the set of CPU id's
     *  that it is possible might ever be plugged in at anytime during the
     *  life of that system boot.  The cpu_present_mask is dynamic(*),
     *  representing which CPUs are currently plugged in.  And
     *  cpu_online_mask is the dynamic subset of cpu_present_mask,
     *  indicating those CPUs available for scheduling.
     *
     *  If HOTPLUG is enabled, then cpu_present_mask varies dynamically,
     *  depending on what ACPI reports as currently plugged in, otherwise
     *  cpu_present_mask is just a copy of cpu_possible_mask.
     *
     *  (*) Well, cpu_present_mask is dynamic in the hotplug case.  If not
     *      hotplug, it's a copy of cpu_possible_mask, hence fixed at boot.
     *
     * Subtleties:
     * 1) UP arch's (NR_CPUS == 1, CONFIG_SMP not defined) hardcode
     *    assumption that their single CPU is online.  The UP
     *    cpu_{online,possible,present}_masks are placebos.  Changing them
     *    will have no useful affect on the following num_*_cpus()
     *    and cpu_*() macros in the UP case.  This ugliness is a UP
     *    optimization - don't waste any instructions or memory references
     *    asking if you're online or how many CPUs there are if there is
     *    only one CPU.
     */
    
    extern struct cpumask __cpu_possible_mask;
    extern struct cpumask __cpu_online_mask;
    extern struct cpumask __cpu_present_mask;
    extern struct cpumask __cpu_active_mask;
    extern struct cpumask __cpu_dying_mask;
    #define cpu_possible_mask ((const struct cpumask *)&__cpu_possible_mask)
    #define cpu_online_mask   ((const struct cpumask *)&__cpu_online_mask)
    #define cpu_present_mask  ((const struct cpumask *)&__cpu_present_mask)
    #define cpu_active_mask   ((const struct cpumask *)&__cpu_active_mask)
    #define cpu_dying_mask    ((const struct cpumask *)&__cpu_dying_mask)

    " 위 주석에서 주로 cpu_possible_mask 와 cpu_present_mask 에 대한 내용을 다룬다. 그런데, 말이 너무 어렵다. 이럴 때는 소스 분석을 통해 내용을 파악하는 것이 가장 좋다.

     

     

     

    1. possible CPU

    " 아마도 시스템에 존재할 것으로 파악되는 CPU 를 의미한다. `아마도` 는 무슨 뜻일까? `possible CPU` 는 디바이스 트리에서 파싱된 CPU 를 의미한다. 디바이스 트리가 하드웨어에 대한 정보를 작성하기 때문에, 만약 CPU[1] 에 대한 내용이 디바이스 트리에 있다면, 물리적으로 CPU[1] 이 존재할 것이라고 가정할 수 있다. 그러나, 실수로 작성한 것 일 수도 있다. 즉, 없을 수 도 있다. 그러기 때문에, `아마도` 를 붙인 것이다(디바이스 트리도 사람이 작성하는 것이기 때문에 100% 신뢰할 수 는 없다). possible CPU 는 디바이스 트리에서 파싱되기 때문에, 시스템 초기화 시점에 생성된다고 볼 수 있다. 그리고, possible CPU 는 디바이스 트리에서 파싱되기 때문에 한 번 정해지면 수정될 수 가 없다(디바이스 트리는 SoC 를 표현하기 때문에, 동적일 수 가 없다. 물론, USB 나 PCI 등 PnP 를 제외하는 것을 제외하고, CPU를 물리적으로 붙였다 땔 수 있을까?). 그렇다면, possible CPU 인식은 언제되는걸까?

    - possible CPU 로 인식되는 과정은 boot CPU / secondary CPU 로 나뉜다.
     1. boot CPU 의 possible 인식 : boot_cpu_init()
     2. secondary CPU 의 possible 인식 : smp_cpu_setup()

     

    " 재미있는 건 boot_cpu_init & smp_init_cpus 함수 모두 boot CPU 가 호출하는 함수다. secondary CPU 의 startup 루틴에는 위에 함수들이 호출되지 않는다. 어떻게 이럴 수 있을까? possbie CPU 가 디바이스 트리를 기준으로 결정되기 때문이다. 즉, 자신이 직접 possible CPU 를 인지해야 했다면, 각 CPU 가 자신이 possible 인지를 판단하겠지만, possible CPU 는 단순히 디바이스 트리에 명시되어 있다면, possible CPU 가 되기 때문에, boot CPU 에서 모두 처리하는 것이다.

     

     

    1.1 How to set boot CPU to possible status ?

    " boot_cpu_init 함수는 boot CPU 의 4가지 상태를 모두 SET 한다. 즉, 리눅스 커널에서는 boot CPU 가 boot_cpu_init 함수를 호출하는 시점에 possible / present / online / active 상태에 모두 해당된다고 보는 것이다. 그리고, __boot_cpu_id 전역 변수에 boot CPU 의 번호를 저장한다.

    // kernel/cpu.c - v6.5
    /*
     * Activate the first processor.
     */
    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
    }

     

     

    1.2 How to set secondary CPU`s to possible status ?

    " secondary CPU`s 의 possible CPU 로 등록하는 과정은 setup_arch 함수에서 시작된다. `setup_arch -> smp_init_cpus -> smp_cpu_setup` 함수를 통해서 secondary CPU 가 possible status 가 되는 것을 볼 수 있다. 이 때, 주의할 점은 smp_cpu_setup 함수가 호출되기 위해서는 반드시 `cpu_logical_map` 함수가 INVALID_HWID 를 반환해서는 안된다. 이 함수가 INVALID_HWID 를 반환할 경우, 디바이스 트리에서 해당 CPU 번호에 대한 CPU ID 가 등록되어 있지 않은 것이다.

    // arch/arm64/kernel/setup.c - v6.5
    void __init __no_sanitize_address setup_arch(char **cmdline_p)
    {
    	....
    	smp_init_cpus();
    	....
    }
    // arch/arm64/kernel/smp.c - v6.5
    /*
     * Enumerate the possible CPU set from the device tree or ACPI and build the
     * cpu logical map array containing MPIDR values related to logical
     * cpus. Assumes that cpu_logical_map(0) has already been initialized.
     */
    void __init smp_init_cpus(void)
    {
    	int i;
    
    	if (acpi_disabled)
    		of_parse_and_init_cpus();
    	else
    		acpi_parse_and_init_cpus();
    	....
        
    	/*
    	 * We need to set the cpu_logical_map entries before enabling
    	 * the cpus so that cpu processor description entries (DT cpu nodes
    	 * and ACPI MADT entries) can be retrieved by matching the cpu hwid
    	 * with entries in cpu_logical_map while initializing the cpus.
    	 * If the cpu set-up fails, invalidate the cpu_logical_map entry.
    	 */
    	for (i = 1; i < nr_cpu_ids; i++) {
    		if (cpu_logical_map(i) != INVALID_HWID) {
    			if (smp_cpu_setup(i))
    				set_cpu_logical_map(i, INVALID_HWID);
    		}
    	}
    }
    // arch/arm64/kernel/smp.c - v6.5
    /*
     * Initialize cpu operations for a logical cpu and
     * set it in the possible mask on success
     */
    static int __init smp_cpu_setup(int cpu)
    {
    	const struct cpu_operations *ops;
    
    	if (init_cpu_ops(cpu))
    		return -ENODEV;
    
    	ops = get_cpu_ops(cpu);
    	if (ops->cpu_init(cpu))
    		return -ENODEV;
            
    	set_cpu_possible(cpu, true);
    
    	return 0;
    }

    " 사실 위에서 가장 핵심 함수는 `of_parse_and_init_cpus` 함수다. 이 함수를 통해서 디바이스 트리에서 각 CPU ID 를 파싱한다. 이 함수에 대한 내용은 이 글을 참고하자.

     

     

     

    2. present CPU

    " 아래 문서에서 알 수 있다싶이, present 라는 말은 시스템에 실제 존재한다는 것을 의미한다. 즉, cpu_present_mask 에 설정된 CPU 는 물리적으로 존재하지만 online 상태는 아닌 것을 의미한다. 그렇다면, 커널은 CPU 가 존재한다는 것은 어떻게 알아내는 것일까?

    cpu_present_mask: Bitmap of CPUs currently present in the system. Not all of them may be online. When physical hotplug is processed by the relevant subsystem (e.g ACPI) can change and new bit either be added or removed from the map depending on the event is hot-add/hot-remove. There are currently no locking rules as of now. Typical usage is to init topology during boot, at which time hotplug is disabled.

    - 참고 : https://lwn.net/Articles/537570/

     

     

    " 아래의 내용을 보면, 커널은 시스템 펌웨어(BIOS or UEFI) 로부터 CPU 개수를 알게된다. x86 에서 부트 로더가 시작되기전에 BIOS or UEFI 펌웨어가 먼저 동작해서 기본적인 하드웨어 초기화 및 진단 검사를 수행한다. 이 때, 시스템 펌웨어는 CPU 개수 또한 파악해준다. x86 을 기준으로 설명하면, `ACPI MADT 테이블` 혹은 `MP 테이블` 에서 LAPIC ID`s 개수를 파악한다. 이 때, LAPIC 는 로컬 인터럽트 컨트롤러로 Per-CPU 디바이스다. 즉, LAPIC 의 개수는 CPU 개수와 같다고 볼 수 있다. 정리하면, 리눅스 커널은 시스템 펌웨어에게 CPU 개수를 정보를 받을 확률이 높다는 것이다[참고1]

    Q: How do we determine how many CPUs are available for hotplug.

    A: There is no clear spec defined way from ACPI that can give us that information today. Based on some input from Natalie of Unisys, that the ACPI MADT (Multiple APIC Description Tables) marks those possible CPUs in a system with disabled status.

    Andi implemented some simple heuristics that count the number of disabled CPUs in MADT as hotpluggable CPUS. In the case there are no disabled CPUS we assume 1/2 the number of CPUs currently present can be hotplugged.

    - 참고 : https://lwn.net/Articles/537570/

     

     

    " start_kernel 함수의 제일 마지막에 arch_call_rest_init 함수가 호출된다. 이 함수는 다시 rest_init -> kernel_init -> kernel_init_freeable 함수를 호출한다(start_kernel() 함수를 호출한다는 것은 cpu_present_mask 의 최초 초기화 또한 boot CPU 가 진행한다는 것을 알 수 있다. 왜 그럴까? 만약, secondary CPU`s 에서 개별적으로 cpu_present_mask 비트를 SET 한다고 해보자. 이 가정은 애초에 모순으로 시작한다. cpu_present_mask 에 SET 이 되어있어야 CPU 가 실제 존재함을 의미하고, 실제 실행할 수 있다는 것을 의미한다. 즉, 누군가가 먼저 secondary CPU`s 의 존재 여부를 확인한 후에 실행될 수 있음을 의미한다). 그리고, 이 함수에서 다시 smp_prepare_cpus 함수를 호출하는데, 이 함수는 2가지 역할을 수행한다.

    1. CPU 토폴로지 구성
    2. secondary CPU`s 에 present status 설정(set_cpu_present)
    static noinline void __init kernel_init_freeable(void)
    {
    	....
    	smp_prepare_cpus(setup_max_cpus);
        	....
    }
    // arch/arm64/kernel/smp.c - v6.5
    void __init smp_prepare_cpus(unsigned int max_cpus)
    {
    	const struct cpu_operations *ops;
    	int err;
    	unsigned int cpu;
    	unsigned int this_cpu;
    
    	....
    	/*
    	 * If UP is mandated by "nosmp" (which implies "maxcpus=0"), don't set
    	 * secondary CPUs present.
    	 */
    	if (max_cpus == 0) // --- 1
    		return;
    
    	/*
    	 * Initialise the present map (which describes the set of CPUs
    	 * actually populated at the present time) and release the
    	 * secondaries from the bootloader.
    	 */
    	for_each_possible_cpu(cpu) {
    
    		per_cpu(cpu_number, cpu) = cpu;
    
    		if (cpu == smp_processor_id())
    			continue;
    
    		ops = get_cpu_ops(cpu);
    		if (!ops)
    			continue;
    
    		err = ops->cpu_prepare(cpu);
    		if (err)
    			continue;
    
    		set_cpu_present(cpu, true);
    		numa_store_cpu_info(cpu);
    	}
    }
    1. max_cpus 는 kernel_init_freeable 함수에서 전달된 인자다. 여기서 setup_max_cpus 가 전달되는데, 이 값은 SMP 환경에서 NR_CPUS 로 하드 코딩 되어있다. 즉, SMP 에서는 0 이 될 수 없다. 0 이라면, single-core 시스템을 의미한다.
    // init/main.c
    #ifndef CONFIG_SMP
        static const unsigned int setup_max_cpus = NR_CPUS;
        ....
    #endif

    2. secondary CPU 가 possible 상태가 될 때를 기억해보자. 먼저, `struct cpu_operations->cpu_init` 함수를 호출한 뒤, set_cpu_possible 함수가 호출되었다. present 도 동일하다. `struct cpu_operations->cpu_prepare` 함수가 선행되서 호출된 후에 set_cpu_present 함수가 호출된다. 그리고, set_cpu_present 함수를 감싸는 루프문을 보면, `for_each_possible_cpu` 매크로 함수다. 즉, present status 는 반드시 possible cpu 를 기준으로 SET 된다는 것을 알 수 있다. 그렇다면, secondary CPU 들이 possible status 가 SET 될 때, 루프문이 뭐였을까?  

    이 글에서는 설명하지 않았지만, for_each_of_cpu_node 매크로 함수다. 이 매크로 함수는 디바이스 트리에서 `/cpus` 노드와 `/cpus` 노드의 자식 노드인 `cpu` 노드를 기반으로 동작한다.

     

    " 그렇다면, 정확히 present status 는 어떤 상태를 의미하는걸까? 이 상태를 명확하게 정의하기 위해서는 set_cpu_present 함수가 호출되는 문맥을 잘 파악해야 한다. smp_prepare_cpus 함수는 start_kernel 가장 마지막 단계인 arch_call_rest_init 함수에서 호출된다. 즉, 시스템의 초기화 마무리 과정에서 present status 가 설정되는 것이다. 이 말은 possible status 는 디바이스 트리에 명시되어 있으므로, 물리적으로 존재할 수 있다는 것에 추측에 초점을 둔다면, present 는 실제로 CPU 가 물리적으로 존재하며 Power-on 이 되어있다는 것을 의미한다. 그리고, set_cpu_present(cpu, 1) 함수가 호출되기 앞서서 CPU 토폴로지가 완성된다. 이 말은 현재 CPU 가 어디 몇 번째 코어인지, 어디 클러스터에 속하는지, NUMA 구조라면 몇 번째 노드인지 등이 확인되었다는 것을 의미한다.

     

    " 그러나, present status 는 동적으로 값이 변경될 수 있다. 그렇다면, 언제 CLEAR 될까? hot plug-out 될 때, 호출된다. 즉, present status 는 현재 CPU가 hot plug-out 일 때는 CLEAR 되고, hot plug-in 일 때는 SET 된다. 구체적인 함수의 호출 관계는 아래와 같다.

     

    " `cpu_die_early` 함수가 호출되면 present status 가 CLEAR 된다. 커널이 hotplug 를 지원한다면, __cpu_try_die 함수를 호출하게 된다. 그리고, 중요한 건 hotplug 가 지원되면 __cpu_try_die 까지만 호출되고 그 뒤에는 호출되지 않는다. 왜냐면, __cpu_try_die 함수를 호출하면 내부적으로 power down 이 시작되기 때문이다. 이 때, 그 이후에 해당 CPU 에게 wakeup 은 없다. 부팅을 다시하는 방법뿐이다. 그래서, hotplug 가 지원된다면, __cpu_try_die 함수까지만 실행되고, 뒤에는 실행되지 않는다.

    //arch/arm64/kernel/smp.c - v6.5
    /*
     * Kill the calling secondary CPU, early in bringup before it is turned
     * online.
     */
    void __noreturn cpu_die_early(void)
    {
    	int cpu = smp_processor_id();
    
    	pr_crit("CPU%d: will not boot\n", cpu);
    
    	/* Mark this CPU absent */
    	set_cpu_present(cpu, 0);
    	rcu_report_dead(cpu);
    
    	if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) {
    		update_cpu_boot_status(CPU_KILL_ME);
    		__cpu_try_die(cpu);
    	}
    
    	update_cpu_boot_status(CPU_STUCK_IN_KERNEL);
    
    	cpu_park_loop();
    }
    // arch/arm64/kernel/smp.c - v6.5
    static void __cpu_try_die(int cpu)
    {
    #ifdef CONFIG_HOTPLUG_CPU
    	const struct cpu_operations *ops = get_cpu_ops(cpu);
    
    	if (ops && ops->cpu_die)
    		ops->cpu_die(cpu);
    #endif
    }

     

    " PSCI 는 arm 프로세서를 어떻게 Power management 해야 하는지에 관한 스펙이다. 자세한 내용은 이 글을 참고하자. 아래는 PSCI 0.2 를 기준으로 설명하고 있다.

    #define PSCI_0_2_FN_BASE			0x84000000
    #define PSCI_0_2_FN(n)				(PSCI_0_2_FN_BASE + (n))
    #define PSCI_0_2_FN_CPU_OFF			PSCI_0_2_FN(2) // 0x8400_0002
    // arch/arm64/kernel/psci.c - v6.5
    static void cpu_psci_cpu_die(unsigned int cpu)
    {
    	/*
    	 * There are no known implementations of PSCI actually using the
    	 * power state field, pass a sensible default for now.
    	 */
    	u32 state = PSCI_POWER_STATE_TYPE_POWER_DOWN <<
    		    PSCI_0_2_POWER_STATE_TYPE_SHIFT;
    
    	psci_ops.cpu_off(state);
    }
    // drivers/firmware/psci/psci.c - v6.5
    static void __init psci_0_2_set_functions(void)
    {
    	pr_info("Using standard PSCI v0.2 function IDs\n");
    
    	psci_ops = (struct psci_operations){
    		.get_version = psci_0_2_get_version,
    		.cpu_suspend = psci_0_2_cpu_suspend,
    		.cpu_off = psci_0_2_cpu_off,
    		.cpu_on = psci_0_2_cpu_on,
    		.migrate = psci_0_2_migrate,
    		.affinity_info = psci_affinity_info,
    		.migrate_info_type = psci_migrate_info_type,
    	};
    
    	register_restart_handler(&psci_sys_reset_nb);
    
    	pm_power_off = psci_sys_poweroff;
    }
    // drivers/firmware/psci/psci.c - v6.5
    static int psci_0_2_cpu_off(u32 state)
    {
    	return __psci_cpu_off(PSCI_0_2_FN_CPU_OFF, state);
    }

     

    " 아래 PSCI 스펙에서 볼 수 있다시피, 0x8400_0002 는 `CPU_OFF` ID 다. 그런데, 리눅스 커널에서는 CPU_OFF ID 에 파라미터를 전달하고 있는데, 스펙에서는 파라미터에 대한 정보가 존재하지 않는다.


     

     

     

    3. online CPU

    " CPU 가 정상적으로 모든 부팅 과정을 완료했다는 것을 어떻게 알 수 있을까? 혹은, CPU hotplug 상태를 어떻게 알 수 있을까?  possible 혹은 present 로는 이것을 알 수 없다. 왜냐면, possible 은 디바이스 트리에 의존하고, present 는 CPU 존재 여부를 나타내기 때문이다. 그렇기 때문에, 시스템이 완전히 부팅 되기전에 이 값들은 대부분은 설정된다. 그리고, 이 값들은 하드웨어의 특성을 나타내는 값들이기 때문에 부팅중에 번 설정되면 cpu_possible_mask & cpu_present_mask 값은 바뀌지 않는다. 그렇다면, 이제 눈치를 챗을 것이다. online status 가 부팅 완료와 hotplug 상태를 나타낸다는 것을 말이다.

    // arch/arm64/kernel/smp.c - v6.5
    /*
     * This is the secondary CPU boot entry.  We're using this CPUs
     * idle thread stack, but a set of temporary page tables.
     */
    asmlinkage notrace void secondary_start_kernel(void)
    {
    	....
    	/*
    	 * OK, now it's safe to let the boot CPU continue.  Wait for
    	 * the CPU migration code to notice that the CPU is online
    	 * before we continue.
    	 */
    	pr_info("CPU%u: Booted secondary processor 0x%010lx [0x%08x]\n",
    					 cpu, (unsigned long)mpidr,
    					 read_cpuid_id());
    	update_cpu_boot_status(CPU_BOOT_SUCCESS);
    	set_cpu_online(cpu, true);
    	....
    }

     

    " secondary CPU`s 들은 secondary_start_kernel 함수를 호출함으로써 startup 프로세스가 시작된다. 이 때, 마지막 시점에 부팅을 모두 완료했다는 의미로 set_cpu_online 함수를 호출한다. 

     

    " 참고로, set_cpu_online 함수는 다른 set_cpu_XXXXXX 함수들과 기능 자체가 다르다. 단순히 부팅 완료만 나타내는 거라면, 다른 set_cpu_XXXXXX 함수들처럼 cpumask_[set|clear]_cpu 함수를 호출하기만 하면된다. 그러나, CPU hotplug 기능에 필요한 다른 변수까지 체크해야 하기 때문에(__num_online_cpus), 약간 더 복잡해졌다. 

     // include/linux/cpumask.h
    void set_cpu_online(unsigned int cpu, bool online);
    // kernel/cpu.c - v6.5
    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_present_mask 와 cpu_online_mask 는 모두 hot-plug 와 관련이 있어 보이는데, 어떤 차이가 있을까? cpu_online_mask 을 CLEAR 하면, `논리적` shutdown 을 의미한다. 실제, 전원이 차단된 것은 아니고, 인터럽트 및 타이머, 스케줄링 등 CPU 가 아무 동작도 하지 않게 된다. 그러나, cpu_present_mask 를 CLEAR 하면, 실제 전원을 shutdown 시킨다. cpu_online_mask 는 `__cpu_disable()` 함수와 관련이 깊고, __cpu_die() 함수와 관련이 깊다.

    The offline case
    Once a CPU has been logically shutdown the teardown callbacks of registered hotplug states will be invoked, starting with CPUHP_ONLINE and terminating at state CPUHP_OFFLINE. This includes:
    ....

    __cpu_disable()
    Arch interface to shutdown a CPU, no more interrupts can be handled by the kernel after the routine returns. This includes the shutdown of the timer.

    __cpu_die()
    This actually supposed to ensure death of the CPU. Actually look at some example code in other arch that implement CPU hotplug. The processor is taken down from the idle() loop for that specific architecture. __cpu_die() typically waits for some per_cpu state to be set, to ensure the processor dead routine is called to be sure positively.

    - 참고 : https://www.kernel.org/doc/html/v4.12/core-api/cpu_hotplug.html

     

     

     

    4 active CPU

    - 싱글코어 시대에 스케줄러의 역할은 단순했다. 주로 태스크를 관리하고, 최대한 공정하고 정의로운 원칙을 통해 태스크들에게 제한된 CPU 자원을 할당하는 것이었다.

    - 그러나 SMP 시스템, 특히 CPU hotplug를 지원하는 시스템에서, 스케줄러는 다음과 같은 특별한 주의가 필요합니다.

     

    : SMP 환경에서, 특히나 `CPU hotplug` 를 지원하는 시스템에서 스케줄러에게 다음에 일이 추가된다.

    1. 시스템에 새로운 CPU 를 추가 : 실행되기를 기다리는 프로세스(waiting or blocked process)들을 새로운 CPU 에게 할당한다.

    2. 시스템에서 CPU 를 제거 : 제거 될 CPU 에서 동작 중이던 프로세스들을 현재 alive 한 CPU 들에게 할당해야 한다(이걸 `migration` 이라고 한다). 

    : 스케줄러는 위에 내용들을 파악하기 위해서 지속적으로 `CPU hotplug` 관한 내용들을 모니터링 해야한다. 그런데, 스케줄러와 CPU hotplug 기능은 서로 독립적인 기능이다. 즉, 서로 관련이 없는 동작이다. 그렇기 때문에, `CPU hotplug` 가 되었을 때, 스케줄러가 이 내용을 알기 위한 별도의 메커니즘이 필요하게 된다. 이 때, 리눅스 커널이 선택한 방법은 `notifier mechanism` 이다.

     

    : 시스템에서 CPU 의 상태 변화가 감지되면, 커널의 CPU control 모듈은 스케줄러에게 이 내용을 알리고(notify), 스케줄러는 먼저 CPU 의 변화 상태(CPU_DOWN_FAILED, CPU_DOWN_PREPARE 등)를 파악한다. 그리고, 스케줄러는 CPU 변화 상태에 대응하는 set_cpu_active 함수를 호출해서 해당 CPU 를 cpu_active_mask 에 추가하거나 혹은 제거한다. 결국 active state 는 아래와 같이 정리할 수 있다.   

    - 스케줄러의 관점에서 CPU active status 는 스케줄링 대상이 될지 말지를 결정한다. 즉, 태스크를 해당 CPU 에게 할당할지 말지를 결정하는 상태라고 정의할 수 있다.

    : 어떻게 보면, active status 는 CPU power management 와는 관련이 없어 보일 수 있다. 왜냐면, 스케줄러만 관심을 갖고 있기 때문이다. 그러나, 스케줄러가 태스크를 할당하지 않는다는 것은, 즉, CPU active status 가 아니라는 것은, CPU 를 IDLE 상태로 진입시킬 수 있다는 의미이므로 간접적으로는 power management 와도 관련이 있다고 볼 수 있다.

     

     

    - CPU control flow

    : CPU 를 control 한다는 것은 크게 2가지 동작으로 요약해 볼 수 있다.

    1. UP : CPU 를 On 시킨다. 해당 동작은 `cpu_up` 함수와 관련이 있다.
    2. DOWN : CPU 를 Off 시킨다. 해당 동작은 `cpu_down` 함수와 관련이 있다.

     

    : 위에 동작들은 다시 시스템 아키텍처에 따라 3가지 동작으로 나눠 볼 수 있다.

    1. single-core CPU : single-core 구조에서는 시스템에 딱 하나의 CPU 만 존재하기 때문에 해당 CPU 이 up/down 에 따라, 프로세스들의 라이프 사이클 또한 CPU 와 동일하게 된다. UP 시스템의 라이프 사이클은 다음과 같다.
    1) 시스템에 전원이 인가되면, CPU가 ROM code 를 실행한다. 그리고, 부트 로더를 컨트롤이 넘어온다. 부트 로더는 리눅스 커널을 RAM에 로딩한 뒤, 컨트롤을 커널에게 넘긴다. 이 과정 자체가 CPU 의 `UP` 프로세스가 된다.

    2) 시스템이 열심히 작업들을 수행한다. 

    3) 원래라면 시스템과 CPU는 동일어로 보지는 않는다. 그러나, single-core 에서 코드를 실행할 수 있는 주체가 딱 한대의 CPU 밖에 없기 때문에, `시스템이 동작 중` 이라는 표현이 `CPU 가 현재 UP 상태이다` 와 동의어가 된다. 즉, 시스템의 라이프 사이클과 CPU의 라이프 사이클이 정확히 일치하는 상태가 되어버린 것이다.

    4) 시스템이 shutdown 된다. 당연히, 한대만 존재하는 CPU 는 `DOWN` 상태가 된다. 

    2. multi-core CPU : 리눅스 커널에서 multi-core, 즉, SMP 를 컨트롤할 때, boot CPU 라는 개념을 사용한다. 이 CPU 는 시스템 초기화 및 다른 CPU`s 들을 startup 시킨다. 왜 boot CPU 라는 용어를 정의했을까? 간단히 생각해보자. SMP 가 도입되면서, 동기화 문제가 심각해졌다. 그런데, 시스템이 초기화되는 시점에 여러 CPU 가 동시에 실행되면 어떻게 될까? 상상에 맡기도록 하겠다. 본론으로 돌아가자. SMP 의 라이프 사이클은 다음과 같다.
    1) SMP 에서 최초 부트-업 시퀀스는 single-core 와 크게 다르지 않다.

    2) 부트-업 시퀀스 과정에서 boot CPU 는 secondary CPU`s 들을 startup 시킨다.

    3) 현재 SMP 에서는 `CPU hotplug` 를 지원하지 않으므로, 부트-업 시점에 모든 CPU 가 `UP` 은 되지만, 동적으로 `DOWN` 은 할 수가 없다. 즉, 시스템에 4개의 CPU가 존재하면, 한 번에 다 UP 되고, 한 번에 전부 DOWN 되는 구조인 것이다.

    4) 시스템이 shotdown 된다. 이 때, 모든 CPU 가 DOWN 된다. 시스템 DOWN 은 모든 CPU, DEVICE 등을 DOWN 시키는 것과 같다. 

    3. SMP supported hotplug CPU : 시스템 부트-업이 완료된 이후, 모든 CPU 가 동적으로 DOWN 이 가능하다(참고로, 대개 boot CPU 는 hotplug 를 허용하지 않는다). DOWN 이 되었으면 당연히 UP 도 된다. 물론, 이게 전부 동적으로 이루어진다. SMP supported hotplug CPU 의 라이프 사이클은 다음과 같다.
    1) 부트-업 시퀀스는 위에 2개의 케이스와 크게 다르지 않다.

    2) 부트-업 시퀀스 과정에서 boot CPU 는 secondary CPU`s 들을 startup 시킨다. 그런데, SMP 와는 여기부터 과정이 조금 달라진다. 리눅스 커널에서 `cpu_up` 인터페이스를 각 CPU 제조사에게 제공한다. 제조사들은 이 인터페이스를 통해서 secondary CPU`s 들을 UP 시킨다. `multi-core CPU`의 (2) 번 절차와의 차이는 `cpu_up` 인터페이스를 사용함으로써 부트 시퀀스 이후에도 동적으로 CPU를 UP 시킬 수 있음을 의미한다. `multi-core CPU`의 (2) 번 절차에서는 무조건 secondary CPU`s 를 UP 시킬 수 밖에 없다. 심지어, 동적으로 DOWN 시키는 방법이 없기 때문에 별도의 API 가 필요없다. 왜냐면, UP/DOWN 이 동적으로 이루어지지 않기 때문에, 해당 기능을 함수로 만들 필요가 없는 것이다(시스템 power-on 시점에 딱 한번, 시스템 shut-down 시점에 딱 한 번만 수행될 것이니깐).

    3) 만약, 시스템이 할 일이 없으면 어떻게 할까? 전력을 아껴야 한다. CPU hotplug 를 지원하기 때문에, CPU`s 들을 동적으로 DOWN 시킬 수 있다. 이 때, `cpu_down` 인터페이스가 사용된다(커널이 제공하며, 각 CPU 제조사에서 구현해야 함). 시스템에 다시 로드가 걸리 기 시작하면, `cpu_up` 인터페이스를 통해 CPU 를 다시 UP 시킬 수 있다. 

    4) 시스템이 shotdown 된다. 이 때, 모든 CPU 가 DOWN 된다.

     

Designed by Tistory.