1.3.3 SMP系统的引导
对称多处理器(Symmetric Multi-Processor, SMP)系统包含多个处理器,并且每个处理器的地位平等。在启动过程中,处理器的地位不是平等的,0号处理器称为引导处理器,负责执行引导程序和初始化内核;其他处理器称为从处理器,等待引导处理器完成初始化。引导处理器初始化内核以后,启动从处理器。
引导处理器启动从处理器的方法有3种。
(1)自旋表(spin-table)。
(2)电源状态协调接口(Power State Coordination Interface, PSCI)。
(3)ACPI停车协议(parking-protocol), ACPI是高级配置与电源接口(Advanced Configuration and Power Interface)。
引导处理器怎么获取从处理器的启动方法呢?读者可以参考函数cpu_read_enable_method,获取方法如下。
(1)不支持ACPI的情况:引导处理器从扁平设备树二进制文件中“cpu”节点的属性“enable-method”读取从处理器的启动方法,可选的方法是自旋表或者PSCI。
(2)支持ACPI的情况:如果固定ACPI描述表(Fixed ACPI Description Table, FADT)设置了允许PSCI的引导标志,那么使用PSCI,否则使用ACPI停车协议。
假设使用自旋表启动方法,编译U-Boot程序时需要开启配置宏CONFIG_ARMV8_SPIN_TABLE。如图1.7所示,SMP系统的引导过程如下。
图1.7 ARM64架构下SMP系统的自旋表引导过程
(1)从处理器的第一个关卡是U-Boot程序中的函数spin_table_secondary_jump,从处理器睡眠等待,被唤醒后,检查全局变量spin_table_cpu_release_addr的值是不是0,如果是0,继续睡眠等待。引导处理器将会把全局变量spin_table_cpu_release_addr的值设置为一个函数的地址。
(2)U-Boot程序:引导处理器执行函数boot_prep_linux,为执行内核做准备工作,其中一项准备工作是调用函数spin_table_update_dt,修改扁平设备树二进制文件如下。
1)为每个处理器的“cpu”节点插入一个属性“cpu-release-addr”,把属性值设置为全局变量spin_table_cpu_release_addr的地址,称为处理器放行地址。
2)在内存保留区域(memory reserve map,对应扁平设备树源文件的字段“/memreserve/”)添加全局变量spin_table_cpu_release_addr的地址。
(3)引导处理器在内核函数smp_cpu_setup中,首先调用函数cpu_read_enable_method以获取从处理器的启动方法,然后调用函数smp_spin_table_cpu_init,从扁平设备树二进制文件中“cpu”节点的属性“cpu-release-addr”得到从处理器的放行地址。
(4)引导处理器执行内核函数smp_spin_table_cpu_prepare,针对每个从处理器,把放行地址设置为函数secondary_holding_pen,然后唤醒从处理器。
(5)从处理器被唤醒,执行函数secondary_holding_pen,这个函数设置了第二个关卡,当引导处理器把全局变量secondary_holding_pen_release设置为从处理器的编号时,才会放行。
(6)引导处理器完成内核的初始化,启动所有从处理器,针对每个从处理器,调用函数smp_spin_table_cpu_boot,把全局变量secondary_holding_pen_release设置为从处理器的编号。
(7)从处理器发现引导处理器把全局变量secondary_holding_pen_release设置为自己的编号,通过第二个关卡,执行函数secondary_startup。
(8)从处理器执行函数__secondary_switched:把向量基准地址寄存器(VBAR_EL1)设置为异常向量表的起始地址,设置栈指针寄存器,调用C语言部分的入口函数secondary_start_kernel。
(9)从处理器执行C语言部分的入口函数secondary_start_kernel。
下面是扁平设备树源文件的一个片段,可以看到每个处理器对应一个“cpu”节点,属性“enable-method”指定启动方法,属性“cpu-release-addr”指定放行地址。需要通过字段“/memreserve/”把放行地址设置为内存保留区域,两个参数分别是起始地址和长度。
/memreserve/ 0x80000000 0x00010000; /{ … cpus { #address-cells = <2>; #size-cells = <0>; cpu@0 { device_type = "cpu"; compatible = "arm, armv8"; reg = <0x0 0x0>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x8000fff8>; next-level-cache = <&L2_0>; }; cpu@1 { device_type = "cpu"; compatible = "arm, armv8"; reg = <0x0 0x1>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x8000fff8>; next-level-cache = <&L2_0>; }; L2_0: l2-cache0 { compatible = "cache"; }; }; … };