揭开钢琴的盖子:操做系统比如一个架美丽的钢琴,咱们能够用上面的琴键弹出优美的旋律。可是咱们不能知足于只会弹奏,若是咱们要更深刻理解钢琴,必须打开钢琴的盖子,一探究竟。因此学习操做系统,不能停留上系统API的调用,须要能更好更高效的调用API,知道API的局限性与缺点,就必须打开操做系统的盖子,探究操做系统API下的底层原理。html
从咱们按下电源键使得计算机通电,计算机的各个部件是怎么运行起来的呢。咱们如今使用的计算都遵循冯诺依曼结构,在咱们探讨计算机的启动前,先弄明白咱们的计算机的结构。ios
1. 冯诺依曼结构计算机的工做原理
- 计算机的核心工做部件是CPU,CPU内部从上电开始反复执行着两个动做:1)取址;2)执行
- 计算机根据一系列的操做指令来执行不一样的动做,这些指令就是计算机程序。
- 计算机运行的程序是以二进制的方式存在内存中,程序中的数据与指令不加区别的都存储在内存上。
2. 计算机的启动过程:
图1:从系统加电起所执行的程序
- X86 PC岁开机时,CPU处于实模式,这时候内存的计算方式是
段基址 << 4 + 段内偏移
- CPU的第一条指令是经过
CS:IP
来取得,而此时CS=0xFFFF,IP=0x0000。这是硬件设定好的。
- 因此最开始执行的指令地址就是0xFFFF0,这个内存地址映射在主板的BIOS ROM(只读存储区)中。
- ROM中的程序会检测RAM、键盘、显示器、软硬磁盘是否正确工做。同时会从地址0开始设置BIOS的中断向量表。
- ROM中的程序继续执行,将启动设备磁盘0磁道0扇区,一个512字节的扇区读到内存0x07c00处。0x07c00应试是一个历史遗留的问题,后续把system模块拷贝到地址开始处时,预留的空间将不够,因此bootset须要把0x07c00这一块操做系统引导与设置模块拷贝走。这算是一个历史包袱。
- 设置cs=0x07c0,ip=0x0000。
- ROM中的程序执行结束,转到0x07c00处开始执行。
启动设备是能够经过BIOS程序来设置的,信息写在CMOS中。CMOS(64B-128B)中存的还有实时钟,硬件配置信息等。(开始时按住Del键能够进入启动设置的配置界面,能够设置光盘启动或U盘启动等)。函数
图2:内核在磁盘上的分布状况
3. Bootsect.s作了哪些事
- 把0x7c00开始的512个字节,拷贝到0x90000处。(0x90000 - 0x90020)
- 设置栈ss = 0x9000,sp = 0xff00,这里把sp设置的够大,防止栈的区域把下面的操做系统代码覆盖了。
- 调用BIOS ox13中断,将第2-5个扇区拷贝到0x90020开始的内存处。若是出错,就反复读取。
- 获取磁盘的参数:磁道数等
- 打印字符串信息:system is loading
- 读入system部分(几百个扇区),读入到内存为0x10000处。(在0x90000的下面)
- 转到地址为0x90020的地址处执行,也就是开始执行setup部分的代码了。
4. Steup模块作了什么事
主要工做是完成操做系统启动前的设置工做。学习
- 读取光标的位置信息放在09000的头2个字节处。由于这时候bootsect模块的代码已经没有用了,能够覆盖了。
- 读出扩展内存的大小,放在接着的2个字节处。
- 获取显卡参数,硬盘参数等等。
- 将system模块的内容从0x10000处开始移到0x00000处,即内存的起始位置。之因此Load进来的时候为何不一次性放在0x00000处,是由于0x00000处开始放的bios中断。如今bios中断已经不须要了,因此能够覆盖了。
- 这时候开始,BIOS的中断向量表已经被覆盖了,后面就再也不须要BIOS的中断了。
- 设置中断向量表与全局描述符表的一部份内容。
- 把cr0的最后一位设置为1,也就是说从实模型进入保护模式。
jmpi 0, 8
。 cs = 8,取到的段基址实际上是0x0000,那么这句话就是跳转到地址为0x00000的地方开始执行,也就是system模块的开始部分。
保护模式下地址翻译与中断处理的改变:测试
cp:ip的翻译过程是:从cs的前12位取出GDT的偏移量(这里是1),从gtd的对应表项中取得基地址,再和ip合并为一个完整的地址。
int n: n指明了IDT表中的序号。从IDT表中获取中断处理函数的入口地址。操作系统
5. system-head
System的第一部分就是head.s部分的代码,这部分代码实际处于绝对地址0处开始的地方。该部分的代码是在保护模式下执行的,所使用的是AT&T格式的汇编指令与以前使用的as86汇编指令不一样。这部分的代码主要完成了下面几件事情。翻译
- 初步始中断描述符中的256项门描述符。
- 检查A20地址线是否打开。关于A20地址线的解释
- 测试系统是否含有数据协处理器,并设置寄存器CR0对应的位。
- 初始化内存页目录表,为内存分页管理做好准备工做。页目录表放在了绝对物理地址为0开始处,也就是head.s程序物理内存位置,程序会被覆盖掉。80286当时24根地址线,寻址16M,因此页表要能寻址16MB。若是内存页大小为4k,那页表就有4K个表项,一个表项按4个字节算,那页表就须要16K个字节(4页)。这里只用到了1级页表,在后续的发展中出现了二级页表,3级页表。
- 最后跳转到system模块中的初始化程序init/main.c中继续执行。
head.s程序执行结束后,已经正式完成了内存页目录页表的设置,并从新设置了内核实际使用的中断描述符表idt和全局描述符表gtd。另外还为软盘驱动开辟了1kb的缓冲区。此时system模块在内存中的详细映像以下图所示:code
图3:System内存中的映像示意图
6. 整体执行线路
总体上能够分类6个阶段,头2个阶段为boostset,中间3个阶段为setup,最后一个阶段为system的head模块。htm
图4:启动引导的整个过程当中,内核在内存中的位置以及移动后的位置状况
7. 参考资料
[1] 《Linux内核彻底剖析基于0.12内核》 赵炯著。
[2] 网易云课堂,哈尔滨工业大学《操做系统之应用》 李治军。blog