【cpu_operations】CPU hotplug smpboot流程概述(3)

CPU hotplug是什么

CPU core的状态

kernel使用4个bitmap,来保存分别处于4种状态的CPU core:possible、present、active和online。

整理软件流程

start_kernel
  -> boot_cpu_init // 标记boot core的状态为possible、present、active和online
  -> setup_arch
    -> psci_dt_init or psci_acpi_init // 初始化psci的ops,选择psci版本,选择smccc模式svc or hvc
    -> smp_init_cpus
      -> of_parse_and_init_cpu or acpi_parse_and_init_cpus
      -> smp_cpu_setup //从dts或者acpi获取cpu信息,并配置逻辑CPU id
        -> ops->cpu_init  // psci模式为空操作
        -> set_cpu_possible(cpu, true); // 标记为possible
  -> rest_init
    -> kernel_init
      -> kernel_init_freeable
        -> smp_prepare_cpus // 调用arch-dependent的接口
          -> for_each_possible_cpu // 遍历每个possible的CPU
            -> ops->cpu_prepare // psci模式检查cpu_on函数是否为空
            -> set_cpu_present(cpu, true); // 标记为present
        -> smp_init
          -> idle_threads_init //为每个一个possible CPU fork一个idle任务
          -> bringup_nonboot_cpus //
            -> for_each_present_cpu // 判断online CPU的数量,如果超过max停止唤醒更多的CPU
              -> cpu_up(cpu, CPUHP_ONLINE); //唤醒从核
                -> __cpu_up(cpu, idle); // 调用arch-dependent的接口
                  -> ops->cpu_boot(cpu);  // 调用psci命令,唤醒从核
        -> do_basic_setup
          -> do_initcalls
            -> do_initcall_level  // 初始化不同阶段
              -> do_one_initcall  // 初始化每个module

possible CPU

possible的CPUs,代表了系统中可被使用的所有的CPU,在boot阶段确定之后,就不会再修改。以ARM64为例,其初始化的过程如下。

  1. 系统上电后,boot CPU启动,执行start_kernel(init/main.c),并分别调用boot_cpu_init和setup_arch两个接口,进行possible CPU相关的初始化。

  2. boot_cpu_init负责将当前的boot CPU放到possible CPU的bitmap中,同理,boot CPU也是present、oneline、active CPU(因此,后续的描述,都是针对非boot CPU的)。

  3. setup_arch负责根据MPIDR寄存器,以及DTS配置,解析并设置其它的possible CPU。

  4. kernel使用一个整形数组(cpu_logical_map,定义如下),保存物理CPU(由ID标示)和逻辑CPU(数组的index)之间的映射,该数组的长度由NR_CPUS决定。通过read_cpuid_mpidr接口,读取当前CPU(boot CPU)的ID(物理ID),并保存在map表的第一个位置。

/* arch/arm64/include/asm/smp_plat.h */
/*
 * Logical CPU mapping.
 */
extern u64 __cpu_logical_map[NR_CPUS];
extern u64 cpu_logical_map(unsigned int cpu);
  1. 如果使能了SMP,则调用smp_init_cpus接口从DTS中解析其它CPU的HW ID(通过‘reg’关键字),并保存在cpu_logical_map数组中;对所有cpu_logical_map数组中的CPU,smp_cpu_setup执行set_cpu_possible操作,将它们设置为possible状态。

present CPU

main.c文件start_kernel函数执行流程,start_kernel—>rest_init—>kernel_init(pid 1,init task)—>kernel_init_freeable,在kernel_init_freeable中会调用arch-dependent的接口:smp_prepare_cpus。1)构建系统中CPU的拓扑结构;2)拓扑结构构建完成后,根据CPU的拓扑,初始化系统的present CPU mask。

void __init smp_prepare_cpus(unsigned int max_cpus)
{
	init_cpu_topology();

	this_cpu = smp_processor_id();
	store_cpu_topology(this_cpu);
	numa_store_cpu_info(this_cpu);
	numa_add_cpu(this_cpu);

	/*
	 * If UP is mandated by "nosmp" (which implies "maxcpus=0"), don't set
	 * secondary CPUs present.
	 */
	if (max_cpus == 0)
		return;

	for_each_possible_cpu(cpu) {
		if (cpu == smp_processor_id())
			continue;

		err = ops->cpu_prepare(cpu);
		if (err)
			continue;

		set_cpu_present(cpu, true);
	}
}

.cpu_prepare回调主要用于检查某个CPU是否具备执行的条件。如果.cpu_prepare执行成功,则说明该CPU是可以启动的。因此,present CPU的意义是:该CPU已经被kernel识别到,并且具备执行代码的条件,后续可以在需要的时候(如hotpulg的时候),启动该CPU。

online CPU

CPU是否boot,则反映到online mask上,已经boot的CPU,会在secondary_start_kernel中,调用set_cpu_online接口,将其设置为online状态。反之,会在__cpu_disable中将其从online mask中清除。

active CPU

在支持hotplug的SMP系统中,CPU资源可以在任何时候增加或者删除。增加的时候,需要将新增的资源分配给等待的task。删除的时候,需要将那些运行在这些CPU上的task,转移到其它尚存的CPU上(这个过程称作migration)。

每当系统的CPU资源有任何变动,kernel CPU control模块就会通过notifier机制通知调度器,调度器根据相应的event(CPU_DOWN_FAILED、CPU_DOWN_PREPARE等),调用set_cpu_active接口,将某个CPU添加到active mask或者移出active mask。这就是active CPU的意义:从调度器的角度,CPU的状态,即是否对调度器可见,或者说,调度器是否可以把task分配到这个CPU上运行。由此可知,active状态,只是为了方便调度器操作,抽象出的状态,和CPU电源管理之间没有耦合。

CPU hotplug 配置宏

在kernel/cpu.c中,cpu_up接口,只会在使能了CONFIG_SMP配置项(意味着是SMP系统)后才会提供。
而cpu_down接口,则只会在使能了CONFIG_HOTPLUG_CPU配置项(意味着支持CPU hotplug)后才会提供。

CPU hotplug主要流程

echo 0 > /sys/devices/system/cpu/cpuX/online
        online_store(drivers/base/core.c)
                device_online(drivers/base/core.c)
                        cpu_subsys_online(drivers/base/cpu.c)
                                cpu_up(kernel/cpu.c)
                                        _cpu_up(kernel/cpu.c)

基本流程

1)up前后,发送PREPARE、ONLINE、STARTING等notify,以便让关心者作出相应的动作,例如调度器、RCU、workqueue等模块,都需要关注CPU的hotplug动作,以便进行任务的重新分配等操作。

2)执行Arch-specific相关的boot操作,将CPU boot起来,最终通过secondary_start_kernel接口,停留在per-CPU的idle线程上。

per-cpu idle线程

首先,boot CPU在执行初始化动作的时候,会通过“smp_init—>idle_threads_init—>idle_init”的调用,为每个CPU创建一个idle线程,为每个CPU fork一个idle thread,并保存在一个per-CPU的全局变量(idle_threads)中。此时,idle thread只是一个task结构,并没有执行。

arch-specific CPU boot

_cpu_up接口会在完成一些准备动作之后,调用平台相关的__cpu_up接口,由平台代码完成具体的up操作,准备动作包括:
1)获取idle thread的task指针,该指针最终会以参数的形式传递给arch-specific代码。
2)创建一个用于管理CPU hotplug动作的线程(smpboot_create_threads),该线程的具体意义,后面会再说明。
3)发送CPU_UP_PREPARE notify。

secondary_startup

该接口位于arch/arm64/kernel/head.S中,负责secondary CPU启动后的后期操作。__secondary_switched会将保存在secondary_data全局变量中的堆栈取出,保存在该CPU的SP中,并跳转到secondary_start_kernel继续执行,通过CPU的SP指针,是可以获得CPU的当前task,也就是说,当CPU SP被赋值为idle thread的堆栈的那一瞬间,当前的上下文已经是idle thread了,
至于后面的secondary_start_kernel,就比较简单了,使能GIC、Timers,设置CPU为online状态,使能本地IRQ中断。等等。最后,调用cpu_startup_entry,进入cpuidle的loop中

CPU hotplug调测命令

CPU hotplug文件系统

# ls /sys/devices/system/cpu/cpuX/hotplug/
 fail  // 状态切换失败后的状态,初始为`CPUHP_INVALID`
 stage  //  当前状态,可以为`CPUHP_ONLINE`
 target  // 目标转移状态,可以为`CPUHP_OFFLINE`

动态开关core

echo 0 > /sys/devices/system/cpu/cpuX/online      # 关闭CPU
echo 1 > /sys/devices/system/cpu/cpuX/online      # 打开CPU

cmdline maxcpus参数

CPU hotplug还受“maxcpus”命令行参数影响:系统启动的时候,可以通过命令行参数“maxcpus”,告知kernel本次启动所使用的CPU个数,该个数可以小于等于possible CPU的个数。系统初始化时,只会把“maxcpus”所指定个数的CPU置为present状态,因此,CPU hotplug只能管理“maxcpus”所指定个数的CPU。

posted @ 2022-06-27 11:14  zephyr~  阅读(971)  评论(0编辑  收藏  举报