SMP 引导流程

smp boot notes

Preface

多核的出现使得引导的过程有些许不同,但是主线其实和以前是类似的,在实习的过程中也遇到了需要多核引导的需求,所以研究了一阵子

大致过程

在之前都是类似的,由 boot cpu 完成引导,对于其他核,可能系统架构中存在电源管理的模块,需要写相应的寄存器来达到唤醒的操作,至于使能之后的默认地址,也一定是架构相关的,比如默认是物理地址的 0x0000000

那么当boot cpu 完成基础的模块初始化之后,就可以尝试唤醒其他核了,这部分暂时我还没细究,但是关键一定是操作寄存器来使能,可能还有寄存器用来填写使能之后的初始指令的地址,因为这都是可以实现的。

为了能让 smp_processor_id 起作用,跳转到 C 代码之前的关键一步就是得设置栈寄存器指向 init_task ,那么其他的核引导的时候,关键的一点就在于让后面引导的 CPU 拿到自己的栈页面,所以 boot cpu 做的事情就是分配一个页面,然后把地址放在固定的地方,然后唤醒一个核,让它能顺利的引导。

其他的核仍要做一些相应的初始化的,因为很多的寄存器都是每个核都有一个备份,比如仍要打开MMU,以及配置中断模式下的栈,初始化自己的时钟(还包括对中断控制器一些配置)等等,这些初始化函数,都是在 boot cpu 做初始化的代码注册的,让后面的核只需要做一些配置,大部分的工作已经完成了,当然了,它还得通知 boot cpu 完成了初始化,它还得继续唤醒下一个核。

Main CPU

汇编过程

下面从 boot cpu 的角度出发,这类的代码非常多的分析,我会尽量简单的描述,这里的关键有几点

  1. 判断处理器类型,完成特定的初始化

  2. 使能 MMU

  3. 解压 .data 的数据

  4. 初始化 bss

  5. 初始化栈寄存器(指向 init_task

这几点的关键就不需要赘述了吧,提一提第一点是如何实现的吧

关键是 procinfo 这个结构,至于那个宏正是用 offset_of 计算出来的,pc 的指令现在正是在这个结构的 __cpu_flush 这里存放的指令是什么呢?关键得知道,r5 是怎么得到的

其实就是预先所有的结构都存在了一块区域,相当于数组,然后根据里面预先定义的值和处理器的标识符比较,如果相等就说明符合了,那么 r5 也就理所当然的指向对应的 procinfo 了,涉及到的两个变量是由链接脚本指定的

以 v7 处理器为例子,

所以下面这条指令会跳转到__v7_setup这个地方

里面的代码现在我还看不出名堂,都是一些寄存器的配置,不是我们这里讨论的重点了

C 代码

不再分析 start_kernel,这里的重点是 smp

其他核心的唤醒其实在非常后,其实都是由创建的线程来完成初始化的,

上面的函数一路往下就会来到

每一个cpu都会创建一个 idle thread,不仅如此,task stack 本就是密不可分的,所以也可以说,分配了内核栈区域

上面赋值的全局变量,正是当其他的核引导的时候会访问的,当然了,我们需要注意一点,Linux 正是用栈来判断 CPU id 的,而这个 id 其实都是 boot CPU 设置的,因为它们的栈都是它分配(也可以说 init_task,这两者其实紧密相连)

接下来就涉及到了具体的架构了,但最后都会让 cpu 执行到 secondary_startup 这个函数,先不看具体的架构,来关注一下其他核

Secondary CPU

代码有些更改了顺序,更好理解

所以,启用了 MMU 之后,最后来到了 secondary_start_kernel

几个比较关键的点

  1. MMU 相关

  2. 对于 ARM 还需要初始化其他模式的sp 寄存器

  3. 调用 notify_cpu_starting 调用其他模块注册的初始化函数

  4. 通知 boot cpu 完成初始化,注意此时 boot cpu 正在等待

  5. 进入 idle 循环,idle 框架也是值得探索的,但这不是重点

这些初始化,保证了后面起来的 CPU,也有自己时钟,也能完成进程调度等功能,已经和主 CPU 没有任何区别

这里也有一个有意思的问题,就是为什么刚起来的 CPU,就可以使用 smp_processor_id,正是因为它的栈(或者说 idle task)是由 boot cpu 所指定,CPU id 也正是如此,当然这个 id 是虚拟 idCPU 也有自己的 id 寄存器,Linux 使用的是前者,因为这屏蔽了架构,不同的架构的 CPU id 可能千奇百怪(ARM 就是 Mpidr 寄存器)

也就是说,引导CPU,默认 id就是 0,它唤醒的CPU,是我自己加+1,然后给你创建了 init 进程,那么 thread_info->cpu it's up to the boot cpu~

PSCI 平台的唤醒流程

下面以实习公司的架构唤醒流程为例,看看到底是怎么个玩法~ 接着刚刚分析的 smp_ops

而在另外一处

这里嵌套了几层

设备树初始化的,关注关键的几个

来到了这

应该可以猜测的到,是汇编实现的了

SMC 是一个宏,分别针对 Thumb ARM 下做了区分,这两者的指令集不一样。上面的操作想要理解,需要知道 ARM Calling Convention,我以前写过一篇只是简单的介绍了,但是最后的一张图便可以理解,当 bl 指令调用的时候,sp 指向的是 第五个参数的地址,理解这个,上述的操作就很自然了

实际操作的时候发现,SMC 系统调用进入 EL3 后挂死,最后发现问题是因为之前的代码手动写了对应的CPU的内部使能寄存器

ATF

虽然还没分析过它的存在,其实跟 x86 的操作系统非常的相似,以前的 ARM 并不会对内存寄存器等资源设定屏蔽层,如今引入了,而实现就是一个 eret 指令,那不是跟 x86 不谋而合吗,这里是它的 git

同一个文件中,还可以找到

所以目前 ARMv8 情况下,对于 CPU 的唤醒,是由 ATF 实现的,本质应该与电源管理相关,有空这里接着补上

总结

比较关键的地方在于,后续被唤醒的CPU的启动的基址是可以确定的,这是非常重要的,照着 boot cpu 给它设定的路线,thread_info->cpu 在它唤醒之前已经确定,所以它一上来就可以调用 smp_processor_id 作为自己在 per-CPU 变量的偏移,这是非常有意思的,然而提到的人似乎并不多

References

ARM多核引导过程

Last updated