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

circle-info

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

所以,启用了 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 寄存器)

circle-info

也就是说,引导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 指向的是 第五个参数的地址,理解这个,上述的操作就很自然了

circle-info

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

ATF

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

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

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

总结

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

References

ARM多核引导过程arrow-up-right

Last updated