smp系统启动分析

Download Report

Transcript smp系统启动分析

SMP系统启动
SMP系统启动
系统初始化简述
 Intel多处理器规范

 CPU间通信
 MP配置表
 APIC的中断控制

SMP系统的引导
系统初始化简述

系统启动流程
 史前时代:BIOS
 远古时代:引导装入程序
Boot Loader
 中世纪:setup()函数
 文艺复兴时期:startup_32()函数
 现代:start_kernel()函数
Step1.BIOS


当PC加电时,硬件电路在CPU引脚产生RESET信号,并将CS、EIP设置成固定
的值。
Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFFFFF0处
的代码,也就是ROM-BIOS起始位置的代码。BIOS启动过程执行以下四个操
作:
1.
上电自检(POST),这个阶段显示BIOS版本号等信息
2.
初始化硬件设备,保证硬件设备操作不会引起IRQ线与I/O端口的冲突。
本步骤最后显示系统中所安装的所有PCI设备的一个列表。
3.
4.
按用户定义顺序访问引导扇区,搜索一个操作系统来启动。
将启动盘的第一个扇区的内容(MBR)拷贝到RAM中从物理地址
0x00007c00开始的位置,然后跳转到这个地址处,开始执行刚
刚装载进来的代码。
Step2.引导装入程序boot loader


引导装入程序boot loader是BIOS用来把操作系统的内
核映像装载到内存中所调用的一个程序。硬盘MBR或软
盘引导扇区内的代码就是boot loader或boot loader的
一部分。以下以LILO为例简要说明。
LILO的功能由几个程序共同实现:



Map Installer:用于管理启动文件。
Boot Loader:这就是由BIOS读入内存的那部分LILO程序,它负
责把Linux的内核或其他操作系统的引导分区读入内存。另外,
它还提供一个命令行接口供用户选择所要启动的系统、加入启
动参数。
其他文件:用于存放Map Installer记录的map文件(/boot
/map)、存放LILO配置信息的配置文件(/etc/lilo.conf)。
Step2.引导装入程序boot loader

LILO的运行


从软盘启动:当从软盘引导Linux时,BootLoader比
较简单,其代码在arch/i386/boot/bootsec.S汇编文
件中。当编译Linux内核源代码时,就获得一个新的
内核映像,这个汇编语言文件所产生的可执行代码放
在映像文件的开始处,拷贝到软盘第一扇区就创建了
启动软盘。BIOS将Boot Loader读入至内存中的物理
地址0x07c00处,控制权转给Boot Loader。
Boot Loader执行以下操作:
Step2.引导装入程序boot loader






把自己从地址0x07c00移到0x90000。
利用地址0x03ff,建立“实模式”栈。
建立磁盘参数表,这个表由BIOS用来处理软盘设备驱动信
息。
通过调用BIOS的一个过程显示“Loading”信息。
然后,调用BIOS的一个过程从软盘装入内核映像的setup()
代码,并把这段代码放入从地址0x90200开始的地方。
最后再调用BIOS的一个过程。这个过程从软盘装入内核映
像的其余部分,并把映像放在内存中从地址0x10000开始的
地方,或者从地址0x100000开始的地方。前者叫做“低地
址”的小内核映像(以“make zImage”进行的编译),后
者叫做“高地址”的大内核映像(以“make bzImage”进行
的编译),
Step2.引导装入程序boot loader

从硬盘启动:

大多数情况下,Linux内核从硬盘装入。LILO可以装在MBR或
者活动分区的引导扇区,最终结果是相同的:在启动过程中
装入程序被执行时,用户可以选择装入哪个操作系统。

BIOS照样将引导扇区读入至内存中的0x00007c00处,控制
权转给Boot Loader,Boot Loader把自身移动至0x90000处,
并在0x9B000处建立堆栈,读入第二级的引导扇区,并将控
制权交给它。

第二级引导扇区的代码依次从磁盘读取可用操作系统的映射
表,并提供给用户一个提示符供用户选择。Boot Loader把相
应分区的引导扇区拷贝到内存中并执行它,或者直接把内核
映像拷贝到内存中。

假定Linux内核映像必须被导入,LILO引导装入程序依赖于
BIOS例程。装入程序显示“Loading Linux”信息,把内核映
像中所集成的引导装入程序拷贝到地址0x0009000,把setup()
代码拷贝到地址0x00090200,并把内核映像的其余部分拷贝
到地址0x00010000或0x00100000。然后,跳转到setup()。
1.BIOS将引导扇区
读入至内存中
0x07c00处,控
制权转给boot
loader
2.boot loader把自
身移动到0x90000处,
并在0x9B000处建立
堆栈,向0x9A200处
3.boot
增长 loader将第
二级的引导扇区读
入至内存0x9B000
处,并将控制权交
给它
0x00000
0x007BE
0x007FE
0x07C00
0x07E00
0x10000
0x90000
0x90200
0x9A000
0x9A200
0x9B000
0x9D000
0x9D200
0x9D600
0x9D800
0x9DA00
0x9DC00
0xA0000
分区表
1982字节
64字节
MBR或软盘引导扇区
512字节
软盘引导扇区
512字节
堆栈
第二级引导扇区
3.5K字节
8K字节
driver swapper
1K字节
最后,控制权交给
5.接下来,操作系统的
setup.S,LILO运行
代码将被读入到内存的
后计算机内存分布
0x90200处,而系统的
情况如图所示。
内核读入0x10000处,
4.二级引导扇区代码
在读入过程中,存放
把描述符表读入内存
map文件的扇区被读入
中0x9D200处,把包
至内存的0x9D000处。
含有命令行解释程序
的扇区读入至内存的
0x9D600处,接下来,
等待用户输入,将用
户选择或者缺省配置
对应的扇区读入至内
存0x9D600,覆盖命
令行解释程序空间,
生成的启动参数保存
在0x9D800处。
0x00000
0x007BE
0x007FE
0x07C00
0x07E00
0x10000
0x90000
0x90200
0x9A000
0x9A200
0x9B000
0x9D000
0x9D200
0x9D600
0x9D800
0x9DA00
0x9DC00
0xA0000
分区表
1982字节
64字节
MBR或软盘引导扇区
512字节
内核
软盘引导扇区
448K字节
512字节
Linux的setup.S
39.5K字节
堆栈
第二级引导扇区
3.5K字节
8K字节
Map信息
512字节
描述符表
1K字节
命令行参数
命令行解释程序
键盘信息交换区
启动参数
driver swapper
512字节
512字节
1K字节
1K字节
Step3.setup()函数


setup()函数代码位于内核映像文件的偏移量0x200处,紧跟在内核
所集成的引导装入程序之后,因此引导装入程序可以方便地确定
setup()的位置,并将其拷贝到从物理地址0x00090200开始的内存
中。
setup函数初始化计算机中的硬件设备,并为内核程序的执行建立
环境,本质上执行以下操作:
1.
2.
3.
4.
5.
6.
通过BIOS中断获取内存容量信息
设置键盘的响应速度
设置显示器的基本模式
获取硬盘信息
检测是否有PS/2鼠标
检查对高级电源管理(APM)BIOS的支持
Step3.setup()函数
7.
8.
9.
10.
11.
12.
如果内核映像被低装载到内存中(在物理地址0x00010000处),就把它
移动到物理地址0x00001000处。反之,如果内核映像被高装载到内存中
则不必移动。
★这个步骤是必需的,因为存放在磁盘上的内核映像都是压缩的,解压程
序需要一些空闲空间作为临时缓冲区(紧挨着内存中的内核映像)。
建立一个临时中断描述符表(IDT)和一个临时全局描述符表(GDT)。
如果需要,重置浮点单元(FPU)。
重新编写可编程中断控制器(PIC),并把16个硬件中断映射到从32到47
的中断向量。内核必须执行这个步骤,因为BIOS错误地把硬件中断映射
到从0到15的中断向量,而这些中断向量早已用于CPU的异常。
通过设置CR0状态寄存器中的PE位把CPU从实模式切换到保护模式。
跳转到startup_32汇编语言函数。
Step4.startup_32()函数



将Linux内核的映像装入内存中,并且做好必要的准备之后,CPU就
通过一条长程转移指令转到映像代码段开头的入口startup_32()。对
于SMP结构的系统,此时运行的只是“主CPU”,即引导处理器BSP;
而其他“次CPU”,即应用处理器AP,则处于停机状态,等待BSP的
启动。
AP在受到启动进入内核时,同样要从startup_32开始执行,所以从
startup_32开始的代码是公共的。但是有些操作仅由BSP执行,另有
一些操作仅由AP执行,可以依据%bx寄存器的值为0/1进行区分。
尽管存在公共的代码,但AP与BSP并不能并发地执行,实际上,BSP
首先执行这段程序,完成以后才逐个地启动AP执行,并且等待其完成。
所以,在同一时间中,系统中最多只有一个处理器在执行这段程序。
Step4.startup_32()函数


startup_32()执行以下操作:

初始化段寄存器和一个临时堆栈

用0填充由_edata和_end符号标识的内核未初始化数据区。

调用decompress_kernel()函数解压内核映像。如果内核映像是低装载
的,解压后放在物理地址0x00100000处;否则,先解压到临时缓冲
区,然后移动到从物理地址0x00100000开始的位置。

跳转到物理地址0x00100000处。
接下来, startup_32()为第一个Linux进程建立执行环境:

把段寄存器初始化为最终值。

为0号进程建立内核态堆栈。

调用setup_idt(),用空中断处理程序填充IDT。

把从BIOS中获得的系统参数和传递给操作系统的参数放入第一个页框
中。

识别处理器模式

用GDT和IDT表的地址填充GDTR和IDTR寄存器

跳转到start_kernel()函数。★
Step4.startup_32()函数

事实上,GDTR与IDTR填充完毕后,CPU完成了本身的初始化,在SMP结构的系
统中,BSP与AP分道扬镳。
[startup_32]
#ifdef CONFIG_SMP
movb ready, %cl
cmpb $1,%cl
je 1f
#ready对完成此阶段初始化的CPU进行计数,BSP为1
# the first CPU calls start_kernel
#BSP调用start_kernel继续进行下一阶段的初始化
# all other CPUs call initialize_secondary
call SYMBOL_NAME(initialize_secondary) #由于BSP已经为之做好准备,AP直接通过
initialize_secondary()转入其空转进程
jmp L6
1:
#endif
call SYMBOL_NAME(start_kernel)
L6:
jmp L6
# main should never return here, but
# just in case, we know what happens.
#AP的运行要到BSP完成系统中基础设施的初始化(如内存管理和进程管理)后才会启动,而且
BSP在每启动一个AP以后都会等待其完成初始化
Step5.start_kernel()函数

start_kernel()函数完成Linux内核的初始化工作,对于BP或者单处理器系统
中的CPU,执行的操作如下:










输出Linux版本信息(printk(linux_banner))
设置与体系结构相关的环境(setup_arch())
页表结构初始化(paging_init())
使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口(trap_init())
使用alpha_mv结构和entry.S入口初始化系统IRQ(init_IRQ())
核心进程调度器初始化(包括初始化几个缺省的Bottom-half,sched_init())
时间、定时器初始化(包括读取CMOS时钟、估测主频、初始化定时器中断等,
time_init())
提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理,
(parse_options())
控制台初始化(为输出信息而先于PCI初始化,console_init())
剖析器数据结构初始化(prof_buffer和prof_len变量)
Step5.start_kernel()函数














核心Cache初始化(描述Cache信息的Cache,kmem_cache_init())
延迟校准(获得时钟jiffies与CPU主频ticks的延迟,calibrate_delay())
内存初始化(设置内存上下界和页表项初始值,mem_init())
创建和设置内部及通用cache("slab_cache",kmem_cache_sizes_init())
创建uid taskcount SLAB cache("uid_cache",uidcache_init())
创建文件cache("files_cache",filescache_init())
创建目录cache("dentry_cache",dcache_init())
创建与虚存相关的cache("vm_area_struct","mm_struct",vma_init())
块设备读写缓冲区初始化(同时创建"buffer_head"cache用户加速访问,
buffer_init())
创建页cache(内存页hash表初始化,page_cache_init())
创建信号队列cache(“signal_queue”,signals_init())
创建uid taskcount SLAB cache("uid_cache",uidcache_init())
创建文件cache("files_cache",filescache_init())
创建目录cache("dentry_cache",dcache_init())
Step5.start_kernel()函数










创建与虚存相关的cache("vm_area_struct","mm_struct",vma_init())
块设备读写缓冲区初始化(同时创建"buffer_head"cache用户加速访问,
buffer_init())
创建页cache(内存页hash表初始化,page_cache_init())
创建信号队列cache("signal_queue",signals_init())
初始化内存inode表(inode_init())
创建内存文件描述符表("filp_cache",file_table_init())
检查体系结构漏洞(对于alpha,此函数为空,check_bugs())
SMP机器其余CPU(除当前引导CPU)初始化(对于没有配置SMP的内核,
此函数为空,smp_init())
启动init过程(创建第一个核心线程,调用init()函数,原执行序列调用
cpu_idle() 等待调度,init())
至此start_kernel()结束,基本的核心环境已经建立起来了。
SMP系统启动
系统初始化简述
 Intel多处理器规范

 CPU间通信
 MP配置表
 APIC的中断控制

SMP系统的引导
Multi-Processor Specification
Version 1.4


Intel制定了关于多处理器的规范,所有的基于Intel处理器
设计的SMP系统都是按照这个规范而设计的。在整个多处
理器规范中, APIC(高级可编程中断控制器)是整个规
范的核心。在SMP系统中,各个CPU之间的通信就是在
APIC的基础上进行的。通过给中断附加的动作,它们之
间可以通过发送中断来执行不同的处理,不同的CPU可以
实现相互控制。
除了局部的APIC,整个系统还有至少一个I/O APIC来处理
由I/O设备引起的中断。SMP系统中,I/O APIC取代了普
通的中断控制器的作用。
Multi-Processor Specification
Version 1.4

SMP系统的互连
高级可编程中断控制器是基于在局部APIC和I/O APIC之间分发中断控
制函数的分布式结构。局部APIC和I/O APIC通过中断控制通信总线
(ICC)进行通信,如上图所示。
 在多处理器中,多个局部APIC和I/O APIC构成一个单一的实体进行工
作,和另外的APIC之间通过ICC进行通信。所有的APIC共同在多处理
器系统中负责从源中断到目的中断的传递。

Multi-Processor Specification
Version 1.4

SMP系统的互连

APIC通过以下的处理来帮助达到可扩展性的目的:
 消除中断相关的通信对内存总线的影响,让系统内存总线更好的
被处理器使用;
 帮助处理器和其他处理器共享中断处理负担。
Multi-Processor Specification
Version 1.4

CPU间通信


局部APIC提供了IPI(Inter-Processor Interrupt),通过IPI允许任
何一个处理器中断其它任何一个处理器或者一组处理器。通常
有多种类型的IPI,在这些IPI中,INIT IPI和STARTUP IPI是专
门设计用来处理系统的启动和关闭的,且与APIC相关。
Intel 系列APIC一般分为两类:
 一类是独立的APIC,一般出现在比较老的486系统中,独
立的APIC的版本号一般是0x (如82489DX APIC)。
 另一类是集成的APIC,从Pentium系统开始,一般的APIC
都是集成的。集成的APIC的版本号为1x。
 程序中就是通过比较版本号来确定系统中APIC的类型。
Multi-Processor Specification
Version 1.4

CPU间通信


INIT IPI:两类APIC都提供了对INIT IPI的支持。INIT 是APIC提
供的以RESET为向量的中断。当接收到INIT IPI时,局部的
APIC将传递INIT信号给CPU。CPU收到这个信号时,将重新初
始化除了cache、浮点单元及写缓冲以外的本身状态,然后系
统将从RESET的向量位置开始执行。
STARTUP IPI:由集成的APIC提供。这种中断将使目的CPU在
实模式下从地址000VV000H开始执行,VV是STARTUP IPI传
递的8位中断向量。



STARTUP中断向量被限定在低1M内存、以4K为界的内存区。
STARTUP IPI是不可屏蔽的
这个中断既不需要目的APIC使能也不需要中断表被编程。
Multi-Processor Specification
Version 1.4

CPU间通信

通常BSP激活AP的操作如下图所示:
Multi-Processor Specification
Version 1.4

MP配置表


在SMP系统启动时,系统将首先响应的CPU作为系统的启动
CPU(Bootstrap Processor),接着BIOS将对系统中所有的
CPU进行检测,并将所有检测到的AP睡眠,以防止它们和BSP
一样执行BIOS的代码;随后初始化APIC和其它的多处理器部
件;最后BIOS将配置相应的数据结构。
所涉及的两个重要的数据结构如下:
 MP Floating Pointer:多处理器浮动指针
 MP Configuraton Table:多处理器配置表
(定义位于/include/asm-i386/smp.h )
Multi-Processor Specification
Version 1.4

MP配置表
 多处理器浮动指针
struct intel_mp_floating
{
char mpf_signature[4];
/* "_MP_"
unsigned long mpf_physptr; /* 配置表地址
*/
unsigned char mpf_length;
/* 结构长度
unsigned char mpf_specification;
/* 规范版本的低号
unsigned char mpf_checksum;
/* 校验值
unsigned char mpf_feature1; /* 标准/配置的?
*/
unsigned char mpf_feature2; /* bit7 IMCR|PIC
*/
unsigned char mpf_feature3; /* 未使用
*/
unsigned char mpf_feature4; /* 未使用
*/
unsigned char mpf_feature5; /* 未使用
*/
}
中断模式配置寄存器IMCR由两个可读/写或只写的接收地址、数据的端口
组成:22h,23h。为了访问IMCR,可以先向22h写70h选中IMCR,然后将数
据写入端口23h。
*/
*/
*/
*/
Multi-Processor Specification
Version 1.4

MP配置表

多处理器配置表
struct mp_config_table
{
char mpc_signature[4];
#define MPC_SIGNATURE "PCMP"
unsigned short mpc_length;
char mpc_spec;
char mpc_checksum;
char mpc_oem[8];
char mpc_productid[12];
unsigned long mpc_oemptr;
unsigned short mpc_oemsize;
unsigned short mpc_oemcount;
unsigned long mpc_lapic;
unsigned long reserved;
};
/*配置表的大小
/* 规范版本的高号 */
/* 校验值
*/
*/
/*如果没有为0
*/
/*如果没有为0
*/
/*在基本配置表中的配置入口数目*/
/* 局部APIC的地址
*/
Multi-Processor Specification
Version 1.4

MP配置表

浮动指针的地址在规范中限定了四个位置:
1.
2.
3.
4.

常规内存的最低端的1K(物理地址0开始的1K);
常规内存的最高端的1K(现有的SMP系统中缺省的认为是639K640K);
BIOS的数据区(物理地址0xF0000开始的64K);
如果系统是在EISA/MCA总线结构的机器,浮动指针就有可能在
扩展BIOS数据区(EBDA)后的1K(物理地址0x40e开始的1K);
在BSP引导系统后,系统将会通过set_arch()调用相关的
smp_scan_config来对这几个区域进行扫描以确定浮动指针的
位置,然后得到系统中关于多处理器的配置
Multi-Processor Specification
Version 1.4

APIC的中断控制


APIC定义了三种中断控制方式:

PIC模式:该模式下绕过了所有的APIC控制器,强制系统在单
CPU的条件下运行;

Virtual Wire模式:在这种模式下,中断的处理仍然只有一个
CPU参与,工作模式为:中断只通过BSP的局部APIC,或者允
许中断由IO APIC传递给BSP的局部APIC后,由BSP进行中断处
理。

对称处理:各个CPU可以通过I/O APIC平等的对外部设备引起的
中断进行处理,系统允许部分中断通过I/O APIC而保留给局部处
理器处理。
在前两种工作模式下,中断的处理都是留给BSP来处理的。系统启
动时,缺省的工作模式是PIC模式。MPv1.4规范规定:在BIOS进行
过初始化后,通常的工作模式可以是PIC或者Virtual Wire模式。
Multi-Processor Specification
Version 1.4

APIC的中断控制


在linux2.4.0中,处理器对中断采用对称处理方式。这部分的
初始化控制通过init_sym_mode()例程进行设置,
[smp_boot_cpus() > setup_IO_APIC()> init_sym_mode()] 主要完
成设置中断处理例程、开启I/O APIC的重定向表入口。系统的
硬件实现了其它的细节,不必依赖于操作系统来提供其它的
操作而使整个系统工作在对称处理模式。
这种模式带来的优势很明显,当一个类型的中断在一个CPU
上处理时,只要该处理例程发送了EOI给I/O APIC,另外的同
样类型的中断就能够在系统中剩余的CPU上再次响应。对于
需要频繁产生中断的设备的中断处理,如网络设备需要系统
频繁的产生中断来进行数据收/发,SMP可以缩短设备中断响
应时间,提高设备的吞吐量。
SMP系统启动
系统初始化简述
 Intel多处理器规范

 CPU间通信
 MP配置表
 APIC的中断控制

SMP系统的引导
SMP系统的引导

概述


Linux规定了只有BSP能够在系统最新启动的时候进入系统的启动,
其它的AP将保持在待机状态。BSP执行启动代码,装入内核。此
后,BSP将设置分页和其它的控制寄存器,进入startup_kernel,
类似单CPU情况。在BSP执行了startup_kernel代码后,通过标志
的修改而改变执行,以让AP能够执行自身的一些低级的配置,但
AP不需处理代码中重复的初始化。
在BSP所做的初始化工作中,初始化代码应该首先扫描获得
MP_Floating_Pointer和MP_Configuration_Table,BSP应该在系
统使用这部分的内存前将数据中的有用部分保存在系统配置区。
然后,系统将在init_IRQ例程中对所有的中断程序进行中断的设置
工作:包括普通的遗留的中断和IPI。
SMP系统的引导

概述

完成内核一些必须的初始化后,系统调用smp_boot_cpus()来激活
各 个 CPU 。 BSP 通 过 对 APIC 控 制 寄 存 器 编 程 来 发 送
INIT/STARTUP IPI给AP。AP将根据IPI中断向量对应的地址执行
初始化指令。内核将使用为每个CPU单独分配的一页为栈。在启
动每一个AP前,系统将把启动的代码复制到该栈页的低端。这部
分重定位的代码位于trampoline.S中,从当前代码段计算出希望的
栈的基地址,接着进入保护模式,跳转到内核入口点的代码。在
AP进入内核例程start_kernel()后,AP将执行和BSP不同的代码。
SMP系统的引导

系统启动示意图
SMP系统的引导

所涉及主要函数功能简介

smp_scan_config():在BSP引导系统后,对内存区域进行扫描以确定浮
动指针的位置,得到系统中关于多处理器的配置。
[set_arch() > smp_scan_config()]

smp_boot_cpus():依次启动系统中的各个CPU,让它们各自完成CPU
本身的初始化,如建立页式映射等。
[start_kernel()>smp_init()>smp_boot_cpus()]

do_boot_cpu():为各个应用处理器AP投入运行做好准备,并启动其运
行[start_kernel()>smp_init()>smp_boot_cpus()>do_boot_cpu()]
 fork_by_hand():为目标CPU创建起一个内核线程,以后就可以通过进
程调度从系统中挑选其他进程运行。
[start_kernel()>smp_init()>smp_boot_cpus()>do_boot_cpu()]

setup_trampoline():AP的“跳板”程序,跳转到head.S中的
startup_32。[start_kernel()>smp_init()>smp_boot_cpus()>do_boot_cpu()]>
setup_trampoline()]
SMP系统的引导

所涉及主要函数功能简介

initialize_secondary():执行jmp跳转到thread.eip指向的start_secondary
(),由于堆栈指针已修改,实际上永远不会从这个函数返回
[startup_32()>initialize_secondary()]

start_secondary() :使AP完成进一步的初始化(cpu_init()、初始化
APIC等),为进程调度作准备,自旋等待起跑命令,最后进入cpu_idle()
供调度。
[startup_32()>initialize_secondary()>start_secondary()]
★这个函数调用永远不会返回,因为cpu_idle()的主体是个无限循环,只要系统中
有就绪进程在等待执行,就调度其运行,否则就使CPU进入硬件睡眠状态,直至有
中断发生时才恢复运行。

smp_commence() :主CPU通过smp_commence()将全局量
smp_commenced设置成1,向所有CPU发出起跑命令。
[start_kernel()>smp_init()]