系统启动流程

问题:

1)加电后CPU从哪里执行代码?
2)boot loader如何加载到RAM?
3)内核镜像如何加载到RAM并运行的?注意分两次加载,需要解压。
4)protect mode何时开启?分页模式何时开启?
5)0号进程、1号进程何时创建的?

一、流程

CPU加电复位后处于real mode且CS、EIP被设为固定值,这两个寄存器决定了CPU执行的第一条指令的地址。该地址被映射到ROM中,ROM中的代码就是我们说的BIOS(Basic Input/Output System)。

BIOS的一个工作是把boot loader加载到RAM,BIOS假设boot loader很小可以存放在一个扇区中,该扇区是磁盘的引导扇区,扇区的最后两个字节为0x55AA,所以BIOS只需要加载引导扇区512 bytes到RAM,就认为boot loader已经加载完成。


而实际上boot loader一般比较大,一个扇区放不下,所以boot loader程序会做特殊处理,一方面满足BIOS的需求,另一方面满足自己的需求,boot loader把自己分成两部分,第一部分由BIOS加载到RAM,第二部分由第一部分加载,从而把完整的boot loader加载到内核。


boot loader已经加载完成,现在boot loader开始加载内核镜像到RAM中,同样的,内核镜像也分两次加载,第一次加载内核镜像的前512 bytes及setup()函数,然后加载内核镜像的第二部分,注意内核镜像的两部分在RAM中并不是连续存放的,而且是压缩的。


压缩的内核镜像已经全部加载到RAM,setup()函数设置cr0寄存器的PE位,cpu从real mode切换到protect mode。
执行内核镜像第二部分起始代码,解压内核,存放在RAM 1M开始的地方。
解压后的内核镜像已经生产在RAM中,执行该镜像起始位置的代码,启用分页,创建0号进程、1号进程,执行/sbin/init。

二、详细分析

1)系统上电,从FFFFFFF0物理地址开始启动

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3》:
9.1.4 First Instruction Executed
The first instruction that is fetched and executed following a hardware reset is located at physical address FFFFFFF0H. This address is 16 bytes below the processor’s uppermost physical address. The EPROM containing the software-initialization code must be located at this address.The address FFFFFFF0H is beyond the 1-MByte addressable range of the processor while in real-address mode. The processor is initialized to this starting address as follows. The CS register has two parts: the visible segment selector part and the hidden base address part. In real-address mode, the base address is normally formed by shifting the 16-bit segment selector value 4 bits to the left to produce a 20-bit base address. However, during a hardware reset, the segment selector in the CS register is loaded with F000H and the base address is loaded with FFFF0000H. The starting address is thus formed by adding the base address to the value in the EIP register (that is, FFFF0000 + FFF0H = FFFFFFF0H).

FFFFFFF0H地址被硬件映射到ROM中,这个地址与处理器最高寻址的物理地址只有16 bytes空间,一般存放一条jump指令,jump至ROM中BIOS程序执行。

2)BIOS把boot loader装载到RAM

linux的boot loader叫做LILO(Linux Loader),LILO比较大,单个扇区存不下,所以被分成两部分。
BIOS根据CMOS里设置的磁盘启动顺序,找到可用磁盘第一个可用的引导扇区(该扇区的最后两个字节时0x55AA),也就是LILO的第一部分,将其拷贝到0X00007C00开始的位置(这个地址是BIOS里设置的,LILO会把程序移动到另一个地方),然后跳转到这个地址,执行LILO part1代码。


LILO part1将自身移到0x00096A00

LILO part1建立实模式栈0x00098000~0x000969ff

LILO part1把LILO第二部分拷贝到0x00096c00开始的RAM中。

3)boot loader把内核映像装载到RAM

LILO part2拷贝内核映像的前512个字节,存放在RAM 0x00090000地址


LILO part2拷贝setup()函数代码,存放在0x90000+512byte的位置,即0X90200处


接着加载内核的其他部分
如果是make zImage编译的小内核镜像,LILO 将其加载到0x10000处:


 setup函数会把0x10000处小内核镜像移动到0x1000处(内核镜像是压缩的,需要一些临时空间解压,这些空间是紧挨内核镜像的),如果是大内核镜像不需要移动。


如果是make bzImage编译的大内核镜像,LILO 将其加载到0x100000处:

setup函数设置cr0寄存器的PE位,cpu从real mode切换到protect mode,但PG依然为0,分页没有开启。

setup函数跳转到arch/i386/boot/compressed/head.s中的startup_32()函数,该函数存放在“image其他部分”的起始位置。

startup_32函数调用decompress_kernel()函数解压内核,如果是小内核镜像,解压后的镜像存放在0x00100000。如果是大内核镜像,解压后镜像存放在压缩镜像后的临时缓存区,解压后的镜像会移动到0x00100000处,所以不管是小内核镜像还是大内核镜像,解压后最终的内存分布都是一样的:


 startup_32跳转到0x00100000处执行,解压后的iamge 0x100000处存放的是arch/i386/kernel/head.S中的startup_32()函数。

startup_32函数初始化swapper_pg_dir临时内核页表。把页全局目录地址存放在cr3,设置cr0的PG位,启用分页,创建0号进程。

startup_32跳转到start_kernel()函数。做各种初始化工作,如sched_init等等,调用kernel_thread()函数创建1号进程,该进程创建其他内核线程并执行/sbin/init程序。
 

posted @ 2019-02-14 10:56  geshifei  阅读(10)  评论(0编辑  收藏  举报  来源