打开电源那神秘的黑色背后发生着什么
- x86 PC刚开机时CPU处于实模式 (实模式即16bit,保护模式即32bit)
- 开始时,CS = 0xFFFF;IP = 0x0000;(CS-> 段寄存器;IP->段内偏移; CS << 4 + IP = 物理地址)
- 寻址 0xFFFF0(ROM BIOS 映射区) (BIOS -> Basic Input Output System;固化到内存地址为 0xFFFF0 处的一段代码)
- 检查RAM,键盘,显示器,软硬磁盘
- 将磁盘0磁道0扇区读入 0x7c00处 (0磁道0扇区为操作系统的引导扇区共 512byte)
- 设置CS = 0x07c0; IP = 0x0000;
- 寻址 0x7c00 开始执行操作系统引导扇区的代码
注解
intel x86 PC机开机接通电源后,在内存0xFFFF0处固化了一段程序即 ROM BIOS ,由于计算机的工作原理是取指执行,如果内存中一片空白则无法进行取指执行,所以该段程序是由硬件已经做好的。刚一上电硬件自动设置 CS = 0xFFFF;IP = 0x0000;由于[CS <<4 + IP = 0xFFFF0] ,所以刚一上电就跳到了 BIOS区进行执行。执行该段固化好的代码-> 1.检查RAM,键盘,显示器,软硬磁盘,2.将磁盘0磁道0扇区的代码读入到 0x7c00处 。然后更新 CS = 0x07c0; IP = 0x0000; 继续取指执行,寻址 0x7c00 开始执行操作系统引导扇区的代码。因此硬盘的第一个扇区上存放着开机后执行的第一段我们可以控制的程序。操作系统的故事从这里开始……
引导扇区代码 bootsect.s
为什么引导扇区的代码使用汇编
在操作系统引导的过程中我们要保证有绝对的控制,使用汇编可以精准的变成机器指令去执行,而使用C语言则首先需要编译,在编译的过程中很可能会发生差错,没办法进行绝对的控制,就很可能发生死机的情况。
bootsect核心代码
1 | BOOTSEG = 0x07c0 |
- 将磁盘上从第 2 到 5 的四个扇区构成的 setup 模块读入到了内存的0x90200 处;
- 然后打出一个 Logo;
注解
对于x86PC来说,bootsect 刚读进来是放在0x07c00这个位置,然后将其转移到0x90000这个位置,并继续执行;利用int 0x13中断,将操作系统的setup读入到0x90200开始的内存处;读入setup之后,bootsect 继续执行,在屏幕上显示开机logo “loading system…”,然后进入 read_it 继续读操作系统模块,并将控制权转移到setup中,执行setup中的内容。
setup.s
setup->OS启动前的配置
1 | SYSSEG = 0x1000 |
- 准备初始化参数 (将硬件参数存放到0x90000处)
- 将system操作系统主体模块移动到0地址处
- 临时建立GDT IDT表,jump 0,8 进入到保护模式下
注解
操作系统是管理各种硬件的,要管理各种硬件就必须首先知道各种硬件参数。管理内存就要知道内存有多少,同时知道都被谁给占着,这就需要数据结构来存储这些信息。所以setup会获取各种硬件的信息并建立相应的数据结构来管理这些硬件。
CS是16bit寄存器,IP也是16bit的寄存器。CS<<4 + IP 最多只能形成一个 20 位地址放到地址总线上,所以最多只能寻址 1M 以内的内存。这对于现在的计算机是远远不够的。所以需要从实模式进入到保护模式下(1M -> 4G)
16bit 模式和 32bit 模式的本质区别是 CPU的解释程序不同
保护模式下的寻址方式不再是 CS<<4 + IP,而是根据CS(选择子) 查表 + IP
System
执行完bootsect与setup模块之后,接着跳到system操作系统的主体模块进行执行。
head.s
head.s是system模块开始的第一个文件,存放在0地址处
1 | stratup_32: movl $0x10,%eax mov %ax,%ds mov %ax,%es |
- setup.s是进入保护模式,head.s是进入保护模式之后的初始化。
- 重新设置 GDT, IDT 表,重新开启A20地址线 (setup里设置的gdt与idt 是临时的,开启A20地址线后寻址范围就从1M变成了4G)
- IDT表是中断函数表,从此int n 不再是DOS中断,而是在IDT表中找到中断函数的地址并执行。
1 | after_page_tables: |
- 在前面开启20号地址线之后就jmp到after_page_tables这个标号处
- 在after_page_tables里面将main函数三个参数、L6、main函数的入口地址都压入栈中
- 在set_paging 执行完毕后,将ret到main() 函数执行
注解
IDT,GDT 的查表都是硬件查表,都是硬件设计好的,目的就是为了速度快
head.s中使用的汇编和bootsect 及 setup的汇编不一样,head.s中使用的是32位的汇编代码,而bootsect及setup中使用的是16位的汇编代码。另外在操作系统的.c文件中还使用一种汇编为”内嵌汇编”。
main.c
main函数完成了各种硬件数据结构的初始化
1 | void main(void) |
总结
bootsect将操作系统从磁盘中读入进内存,setup获取一些硬件参数并进入到保护模式,head初始化一些gdt表,并初始化一些页表,之后跳到main函数,main中有一大堆init,完成对各种硬件数据结构的初始化。
总体可以概括为两步
- 将操作系统读入到内存
- 初始化
只有先将操作系统读入进内存后计算机才能进行取指执行
初始化是因为操作系统是管理计算机硬件的软件,要想管理硬件就先要对每一种硬件做出相应的数据结构。setup,head,main, mem-init 这些搭在一起就是为了 得到硬件参数,初始化关键的数据结构,为将来管理操作系统做准备。
参考资料