从开机到执行linux的main函数过程
开机的时候 80x86的CPU自动进入实模式,从地址0xFFFF0开始自动执行程序代码,这个地址即是ROM-BIOS的地址,BIOS将执行一些系统检测,并在绝对物理地址0初始化BIOS中断向量,然后,将可启动设备的第一个扇区(称磁盘引导扇区,512byte)读入内存绝对地址0x7c00, 并跳转至该处执行,至此,就完成了内核初始化的工作。
bootsect.s
Linux最最前面的程序是bootsect.s
,将被BIOS加载到内存绝对地址0x7c00处,他将完成以下操作
- 将自己移动到0x90000处
- 将启动设备中后2KB代码(boot/setup.s)读入到0x90200处
- 将内核其他部分(system)读入到0x10000处
- 完成以上操作后,还会做一些其他检测工作,不是我们关注的重点,忽略
- 完成代码加载和一些初始化工作后,执行
jmpi 0, SETUPSEG
这里SETUPSEG =0x90200
,跳到setup.s去执行。
下图前三个场景就是bootsect.s干的
setup.s
setup.s
是一个操作系统加载程序,他的主要作用是利用BIOS中断读取机器系统数据,并保存到0x90000处,以供内核使用。
所取得的参数和保留的位置如下:
前面bootsect.s
将内核代码放置到了0x10000起始处,目的就是为了不把加载到0地址的BIOS中断覆盖掉,setup.s
以及完成对BIOS中断的利用,参数也全部保存了起来,那么就可以移动内核代码到地址0处了。
1 | mov ax, #0x0000 |
完成移动后:
1 | end_move: |
这里加载了两个临时的gdt和idt表,主要看gdt表:
代码段:0x00C0 9A00 0000 07FF
,表示段长为8M,基地址为0,可读可执行
数据段:0x00C0 9200 0000 07FF
,表示段长为8M,基地址为0,可读可写
lgdt指令:要求6字节操作数,前2字节为gdt表限长,后四字节为gdt表基地址。以gdt_48为例,
.word 0x800
即限长为2048个字节,根据一个gdt表占8个字节,这里即有256个gdt表项。.word 512+gdt, 0x9
,即0x90200 + gdt,即指向该段程序的gdt表的地址。
1 | gdt: |
来看最后也是最关键的代码:
1 | mov ax,#0x0001 ! protected mode (PE) bit |
lmws将修改CR0寄存器,完成实模式到保护模式的切换,此时我们就无法直接跳转物理地址了,而必须借助段选择符来完成跳转,所以来看这段指令jmpi 0,8
,即offset:0,段选择符8(0000 0000 0000 1000),下面是段选择符的数据结构:
那么这里的描述符引索即为:0000 0000 0000 1,TI:0,RPL:0,即选择到了我们第一个gdt表,也即内核代码段,根据段描述符,可知要跳转的地址为物理地址0,此时我们就跳转到了system代码段,并在那里开始执行,而system代码段的头部代码就是head.s
,即开始执行heads.s
实模式到保护模式的切换,即寻址方式发生了改变,这是非常重要的一步,实模式下,仅有20位寻址能力,仅有1M,而计算机希望拥有更大的寻址空间,显然20位寻址能力绝对不满足,那怎么切换到更大的寻址模式呢,即切换到保护模式,将寻址能力提升到32位,这个时候内存就变成很大了,那么他是怎么做到这件事的呢?即是改变了指令的解释模式,拿jmpi 0,8举例,这里的8就不在是段了,而是寻找段描述符,利用段描述符去寻找实际的段地址,完成实际的跳转。那么是怎么改变指令的解释模式的呢?即是通过修改CR0寄存器的值。
实模式下:CS左移四位+IP
保护模式下:CS变成了段选择子,来寻找gdt表,寻找到相应的段选择符,从中找到基址再与IP相加,找到对应的实际地址。
head.s
head.s程序在编译成目标生成文件后会和内核其他程序一起链接成system模块,位于system模块的头部,这也是被称为是heads
的原因,从这里开始,程序就完全运行在保护模式下了,heads.s
使用的是AT&T汇编语言。
这段程序实际上处于内存绝对地址0处开始的地方,程序功能也比较单一。
- 加载各个数据段寄存器
- 重新设置中断描述符表idt
- 重新设置全局描述符表gdt
- 检测A20是否开启
- 设置内存管理的分页处理机制,放置在绝对内存地址0的地方,会将head.s的部分程序覆盖
- 最后,利用返回指令将预先放置的在堆栈中的
main.c
程序入口地址弹出,此时开始运行main.c
总结
笔记的描述并没有分析太多源码,因为本身做的操作都是比较简单的逻辑,移动代码以及初始化工作,汇编源码我们还是不心力去看去学习了,建议有兴趣研究的同学可以配合linux0.11+《linux内核完全注释》这本书去看,理解源码的过程尽管费时费事,但收获还是颇多的。