棋子 · 2022年05月06日

多核处理器启动过程浅析

在早些年前,一个中央处理器(CPU)里面只有一个处理器核(Core)。那时候CPU的性能提升主要靠的是提升处理器工作主频。定性分析,我们假定一个软件在编译完成后,对应的需要执行的指令总数是固定的,那么处理器主频越高,执行这些指令用的时间越少,也就是处理器的性能越高。定量分析,处理器的性能公式:CPU时间 = IC x CPI x时钟周期时间,其中IC是指令的条数,CPI是每时钟周期指令数。从上述公式可以看出提升CPU性能可以从三方面入手:时钟频率,CPI 和指令的条数。

随着集成电路的发展,人们发现提高处理器主频越来越难以实现了。那么,能不能把原来的一个任务分解成多个子任务并行执行,这样是不是就会提升处理器的性能呢?在前面我们讲过,虽然现在的处理器有了操作系统加持,看起来能够并行执行很多个不同的程序。但是实际上在某一段时间内,一个处理器核只能执行一个程序,操作系统只是通过时间片轮转的方式让所有的程序分时执行,看起来像所有程序并行执行一样。那如果有很多个处理器核,每个核执行一个程序,是不是会提升整体性能呢?这两个问题的答案都是肯定的。有了多核处理器,就可以并行执行多个程序,也可以并行执行一个程序(前提是这个程序可拆解)。对于并行执行的性能提升,定量分析可以参考Amdahl定律。

不管单核处理器还是多核处理器设计,都是一个非常宏大的话题。以上只是泛泛而谈,不再展开。我们今天具体看看处理器的启动过程。在分析多核处理器启动之前,我们先来看看一个单核的计算机系统是如何启动的。假设大家对内存管理,TLB,缓存(Cache),DDR,PCIe这些有一些基础知识。

9a0d1b4dca9ade68860cf18c67148191.png

当我们按下电源开关以后,系统的时钟生成单元会最先工作,这时候处在参考时钟ref_clk下(通常是10-100MHz),待到PLL输出稳定后,时钟生成单元会把给处理器的时钟切换到正常工作频率,并释放处理器的复位信号,通常我们称这个复位为冷复位。

无论采用何种指令系统的处理器, 复位后的第一条指令都会从一个预先定义的特定地址取回。处理器的执行就从这条指令开始。处理器的启动过程, 实际上就是一个特定程序的执行过程。这个程序我们称为BIOS(Basic Input Output System, 基本输入输出系统),也有称为Bootloader,或者统统归为固件(Firmware)。现代计算机系统的启动代码很复杂了,但是本文是从硬件角度去分析,所以就不严格区分这几个软件概念了。

处理器预先定义的起始地址会在系统总线中映射到一个存储设备上,这个设备可能是内部的ROM,也可能是外部Flash。如果把处理器从开始到结束比作一场大戏,启动过程就是序幕,那么现在才刚刚开始准备工作。

当CPU拿到第一条指令,启动过程就开始了。所谓启动初始化过程,就是把计算机系统内部每个模块的工作状态调整到一个确定的状态,其实就是一个熵减的过程。现在的处理器设计大都是基于Load/Store架构,所以简单说,初始化过程就是处理器通过一大堆的load和store指令把复位后某些没有固定态的寄存器或者模块设置到固定态(严格意义上,这么说不十分准确)。

处理器启动后的第一条指令的地址一定是实地址,或者说是直接地址翻译模式(虚地址和实地址有直接的对应关系)下的地址,因为这时候的TLB还没有被初始化,尚不能正常工作。这时候的指令或数据访问是uncacheable的,也就是不经过高速缓存,原因一样,高速缓存还没有被初始化。

在初始化过程的前期,一般最先初始化调试接口,通常是UART串口。这样做的好处是在早期就可以提供一个简单的交互接口,如果后面的初始化过程跑不下去了,也能进行简单的调试。通常情况下,I/O设备的寄存器会跟内存空间映射到一起,也就是所谓的memory mapped方式,这样处理器可以像访问内存空间一样去访问这些I/O设备(我们先不考虑某些系统中可以通过特殊指令访问I/O设备的做法)。

调试接口的初始化完成后,就是TLB初始化。TLB作为一个地址映射的管理模块,主要负责操作系统里地址空间的管理。系统刚上电时,TLB里的内容是残留或者随机的,换句话说,TLB里面的所有表项都是错误的。TLB的初始化主要是将全部表项初始化为无效,将TLB的每项逐一清空, 以免程序中使用的地址被未初始化的TLB表项所错误映射。这个过程可以由硬件完成,也可以由软件完成(通过特殊指令),不同指令集的处理器有不同的方式。

同理,在系统复位之后,Cache同样也处于一个未经初始化的状态, 也就是说Cache里面可能包含残留的或随机的数据, 如果不经初始化, 对于Cache空间的访问也可能会导致错误的命中。大多数情况下对Cache的初始化就是对Tag的初始化,只要将其中的Cache块状态设置为无效, 其它部分的随机数据就不会产生影响。现在的处理器设计中,缓存系统大多数情况下会采用分层结构,对于多核处理器,一般L1缓存是独享的,最后一级缓存是共享的。不管单核处理器系统还是多核处理器系统,都要对所有层级的缓存做初始化。

接下来就是内存的初始化了,在此步骤之前的代码执行要么是处理器直接读ROM/Flash里面的指令,要么是把外部存储器的读到芯片内部RAM,处理器从RAM读取指令。不管哪种方式,处理器执行的速度都会比较慢。内存控制器的初始化包括两个相对独立的部分, 一是根据内存的行地址、列地址等对内存地址映射进行配置, 二是根据协议对内存信号调整的支持方式对内存读写信号相关的寄存器进行训练(Training), 以保证传输时的数据完整性。

内存初始化完成后,就要对外部设备进行初始化,首先要完成的是外部设备的探测和加载。这部分主要是指PCIe的训练,设备枚举,并加载驱动等动作。这也是一个比较大的话题,不展开了。

至此,为了加载操作系统的基本环境已完成,可以进入操作系统内核了。单核处理器的启动初始化过程也就结束了。

对于多核处理器系统,情况复杂一些。一般是有一个主核(有时叫core 0)先去完成上述的操作。主核的启动工作完成后,再去唤醒其它的处理器核(可以称为从核)。从核负责初始化私有的TLB和cache等资源,启动之后进入空闲(Idle)状态,直至进入操作系统再由主核进行调度。多核处理器的内核间通信一般会通过信箱(Mailbox)机制或者核间中断机制。信箱机制可以是设计专用的信箱寄存器,主要是为了在内存还没有初始化前就让所有处理器核能够有效通。如果是通过信箱机制,从核在等待唤醒的时候要去定时查询自己的信箱寄存器,如果发现主核唤醒自己,就要从空闲状态出来了。可以看出,信箱寄存器机制需要处理器轮询,所以通信效率不高,在休眠/唤醒这类不频繁的操作中还可以,如果是比较频繁的核间通信就不适用了,要采取中断机制。

以上是多核处理器启动的基本原理,至于具体实现,不同的厂商有不同的设计。比如在ARM体系里面,往往会有一个小的系统控制单元(MSCP,Management & System Control Processor)辅助完成简单外设初始化,安全认证等等工作,然后由主核和从核完成剩余的启动工作。

原文链接:老秦谈芯
作者:老秦谈芯

推荐阅读

更多IC设计技术干货请关注IC设计技术专栏。
推荐阅读
关注数
19121
内容数
1295
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息