linux 启动流程
0x00 What x86
x86是Intel的CPU架构,最初是由16位的8086处理器组成的
CPU和内存之间传递数据需要用到总线
- 访问内存中某个地址的数据 把这类总线叫地址总线
- 内存中真正的数据 这类总线叫做数据总线
8086处理器的地址总线是20位, ip寄存器和通用寄存器是16位, 那么总线与寄存器之间的关系是把CS和DS寄存器中的值左移4位 。即: 起始地址16+偏移量
拥有20位地址总线的8086能够区分出的地址最多为
2
20
=
1
M
2^{20}=1M
220=1M
因为偏移量只能是16位 所以一个段最多为
2
16
=
64
k
2^{16}=64k
216=64k
32位系统架构下如何兼容8086
32位处理器有32根地址总线,可以访问
2
32
=
4
G
2^{32}=4G
232=4G
- 实模式
- 保护模式
0x10从BIOS到bootloader
在计算机主板上 有一个东西叫ROM(Read Only Memory , 只读存储器)。ROM中初始化了一些程序,即 BIOS (Basic Input and Output System,基本输入输出系统)。
0x11 x86中的ROM
在x86系统中,将1M空间最上面的0xF0000到0xFFFFF这64k映射给ROM,也就是说这部分地址可以访问ROM。当电脑通电后会做一些重置工作:
- 将CS设置成0xFFFF
- 将IP设置成0x0000
- 所以第一条指令指向0xFFFF0,正是ROM的地址范围
- 使用JMP指令跳转到ROM的初始化工作的代码中
0x12启动盘的工作流程
启动盘:一般位于第一个扇区,占512个字节,以0xAA55结束
Grub2( Grand Unified Bootloader Version 2) 的主要作用是将代码放入启动盘区域,工作流程如下:
-
安装boot.img ,它是由boot.S编译而成,一共512字节,安装到第一个扇区,此扇区被称为MBR( Master Boot Record , 主引导记录)
-
BIOS完成任务后,会将boot.img从硬盘加载到内存中的0x7c00中运行
-
boot.img加载grub2的另一个镜像core.img,core.img 包括以下几个部分:
- lzma_decompress.img
- diskboot.img
- Kernel.img
- 其他的模块
-
boot 先加载core.img的第一个扇区,会执行diskboot. img
- diskboot.img会将core.img的其他部分加载进来
- diskboot.img会解压lzma_decompress.img ,lzma_decompress.img 对应的代码是startup_raw.S
- diskboot.img解压kernel.img
- 最后是各个模块module对应的映像
-
在实模式下,1M内存可能存放不下我们需要加载的东西,所以在真正解压缩之前,lzma_decompress.img 会调用real_to_prot,切换到保护模式,这样就能在更大的寻址空间里面,加载更多的东西。
0x13从实模式到保护模式
Tips:
-
段的起始地址放在内存的某个地方,这个地方是一个表格,表格中的一项一项是段描述符(Segment Descriptor)
-
段描述符内是真正的段起始位置,而段寄存器里面保存的是这个表格中的哪一项,称为选择子(Selector)
-
启用分段,辅助进程管理
在内存中建立段描述符表,将寄存器里面的段寄存器变成段选择子,指向某个段描述符,这样就可以实现不同进程的切换了。
-
启动分页,辅助内存管理
-
打开Gate A20 也就是第21根地址线的控制线。由函数DATA32 call real_to_prot打开Gate A20。
-
lzma_decompress.img 解压运行kernel.img(对应的代码是startup.S),kernel.img做以下4件事:
- 解析grup.conf文件:由startup.S文件调用grub_main(grub kernel主函数),grub_load_config() 开始解析grup.conf文件。
- 选择操作系统:grub_main最后会调用grub_command_execute(“normal”,0,0),最终会调用grub_normal_execute()函数。这个函数里面grub_show_menu() 会显示操作系统列表。
- 例如选择linux16,会先读取内核头部数据进行检查,检查通过后加载完整系统内核:调用grub_menu_execute_entry(),开始解析并执行选择的系统文件
- 启动系统内核:调用grub_command_execute(“boot”, 0 , 0) 才开始真正启动内核
0x14 Gate A20的理解
例子:想访问0x12345这块内存,
-
可以使用0x1234段 偏移0x5。即:0x1234*0x10+0x0005=0x12345
-
也可以使用0x1230段 偏移0x45。即:0x1230*0x10+0x45=0x12345
那么一共出现的组合会是:
- 0xFFFF:0x000F
- 0xFFF0:0x00FF
- 0xFF00:0x0FFF
- 0xF000:0xFFFF
- 0xFFFE:0x001F
- 0xFFFD:0x002F
- 等等~~~~
以上这些组合内存都是1MB以内的。但如果使用0xFFFF:0x0010 即 0x100000 就超过1MB了,此时内存会重新指向0x00000 。那么0xFFFF:0x0011就会指向0x00001地址。
当Intel 80286处理器问世后,它拥有24位地址总线,可访问的内存变成了
2
24
=
16
M
2^{24}=16M
224=16M
Intel80286提供了实模式用来兼容8086下的所有软件,但出现了一个处理器的bug : 当地址超过1M之后 实模式不会产生环绕,比如 0xFFFF:0x0010 将会映射到0x100000地址上,0xFFFF:0x0011 将会映射到0x100001 ,0xFFFF:0xFFFF将会映射到0x10FFEF上。
于是A20诞生了,默认情况下是关闭的(允许访问0xFFFF:0x0010 - 0xFFFF:0xFFFF)
- 实模式下:
- 打开-> 在0x100000-0x10FFEF 会寻找真正地址
- 关闭-> 回绕到 0x0000-FFEF
- 保护模式:
- 打开->可连续访问内存
- 关闭->只能访问到奇数的1M段,即0x00000-0xFFFFF、0x200000-0x2FFFFF、0x300000-0x3FFFFF…