1.1启动第一步:
当按下电源键的那一刻,RAM中什么也没有,但是ROM中有生产商烧录的BIOS启动块。此时,cpu由于刚启动,默认为16位实模式,CS:IP默认指向0xFFFF0这个地址位置。这个地址即是BIOS启动块的地址。
1.2bios中运行流程
1.2.1bios基本流程
显示显卡信息
显示内存信息
…
建立中断向量表和中断服务程序
在内存最开始的位置0x00000 ~ 0x003FF构建中断向量表[1KB],在紧挨着它的位置用256字节构建bios数据区(0x00400 ~ 0x004FF),在大约57KB之后,加载了8KB左右的中断向量表相应的若干中断服务函数。[这些中断服务函数的作用是把系统内核程序从硬盘加载到内存中]
1.2.2加载引导程序bootsect
当bios执行自检完成,硬件体系和BIOS联手,使bios触发一个int 0x19中断,该中断服务函数作用是把软盘第一扇区的512字节加载到指定内存位置。这个指定位置为【0x7c00】。
1.3 bootsect流程
当bios加载完bootsect,就跳到了bootsect中继续执行。
jmp 0x7c00;
其bootsect执行业务如下:
1.3.1复制bootsect
1.将自身bootsect这512字节从0x7c00复制到0x90000处
2.程序切换到cs = 0x90000处继续执行,IP不变
3.将ds es ss 都设置成与CS相同的位置0x9000,[由于ss sp被设置,之后就可以使用栈了]
为什么要拷贝多此一举?
rom里面的程序是芯片厂商制定的,软件开发和硬件不是一个公司,其实rom只做了一件事,就是将磁盘第一个扇区搬移到内存0x7c00处,这个地址相当于一个默认约定。但是软件厂商有很多,每个软件厂商都有自己的规划,如果各个厂商想将bootsect放到其他地址,需要自己重新规划,重新移动。
1.3.2将setup程序加载到内存
1.触发int 0x13中断,执行中断服务程序,将硬盘第二扇区开始的4个扇区【即是setup.s对应的程序】拷贝到0x90200之后,范围为【0x90200 ~ 0x909FF】
1.3.3将system模块加载到内存
1.触发int 0x13中断 将从第六扇区开始的240个扇区加载到内存的0x10000处。大小为120KB.
1.3.4确定根设备号(rootdevice)
目前来看,根设备号就是一个数字,该数字对应的是一个包含硬件信息的软盘,该软盘中含有格式化好的文件系统。
1.3.5 通过jmp跳转到setup代码中
跳转目的地0x90200;
跳转指令 jmpi 0, SETUPSEG
1.4 setup流程
1.4.1 获取系统数据
1.利用BIOS提供中断服务程序从设备上提取内核运行所需的机器系统数据,包括光标位置,显示页面等数据,并分别从中断向量0x41和0x46向量指向的内存地址处获得硬盘参数表1/硬盘参数表2,把它们存放在内存地址0x9000:0x0080和0x9000:0x0090处。
这些机器系统数据加载到内存0x90000-0x901FC处,覆盖了已经完成使命的bootsect代码。
内存地址 | 长度 | 名称 | 描述 |
---|---|---|---|
0x90000 | 2 | 光标位置 | 列号 行号 |
0x90002 | 2 | 拓展内存数 | 系统从1MB开始的扩展内存数值【KB】 |
0x90004 | 2 | 显示页面 | 当前显示页面 |
0x90006 | 1 | 显示模式 | |
0x90007 | 1 | 字符列数 | |
0x90008 | 2 | ?? | |
0x9000A | 2 | 显示内存 | 显示内存 0x00-64K 0x01-128… |
… | |||
0x90080 | 16 | 硬盘参数表 | 第一个硬盘参数表 |
0x90090 | 16 | 硬盘参数表 | 第二个硬盘参数表 没有则清零 |
… | |||
0x901FC | 2 | 根设备号 | 根文件系统所在的设备号 |
1.4.2开始转变为保护模式
1.4.2.1关中断,并将system移到0x00000
意义:将system从0x10000移到0x00000,将会废除BIOS在这区域的中断向量表和中断服务函数。而为了度过这一段没有中断向量表的时期,我们必须关中断。这样收回完成历史使命的bios向量表和中断服务函数所占用的内存,同时,也让内核代码占据内存物理地址最开始,天然的,有利位置。
1.4.2.2设置中断描述符表和全局描述符表
GDTR: GDT基地址寄存器,通过这个寄存器可以找到GDT首地址
GDT: 全局描述符表,在系统中唯一存放段寄存器内容(段描述符)的数组。用来在保护模式下管理段描述的数据结构,对操作系统自身的运行以及管理/调度进程有着重大意义。
这里设置了内核代码段/内核数据段
IDT: 保护模式下所有中断服务程序的入口地址,类似于是模式下的中断向量表
IDTR: 保存IDT的起始地址
由于已关中断,无需调用中断服务函数,所以目前IDTR设置为空
1.4.2.3打开A20,实现32位寻址
打开A20,意味着CPU可以进行32位寻址,最大寻址空间为4GB。
回头看下,一起是CS:IP = 0xFFFFF; 现在5个F 扩展到8个F。 4 * 8总共是32位的寻址,为4GB寻址。
1.4.2.3对8259A重新编程
将中断号重新编程布局,采用新的中断。显然,之前使用过的老的内存搬移中断已经不需要了,而这个时候的中断系统也将更加完善。
1.4.2.4CR0寄存器PE位置一,处理器变为保护模式
变为保护模式的一个重要特征就是根据GDT决定后续执行哪里的程序。
jmpi 0, 8
0是段内偏移
8是gdt表中的偏移,每个元素占八位,刚好是第一个元素,内核代码段
该元素中获取内核读代码段的基址位0x0
因此跳到了 0x0000000处。
1.5 总结
可以看到,bootsect 和setup这两个模块主要做了两件事:
1. 内存搬移
2.为保护模式做准备
早期的linux加载使用多次腾挪,在有限的区域内对内存进行最大化利用,每当使用完一段代码,立即将其覆盖。如何在这种代码片之间进行交互这是一个技巧。比如直接将自己拷贝一份,然后换个cs寄存器直接切到另一份代码中运行,又比如无缝连接直接jmp, 又比如通过压栈+ret直接跳转到main,这些都是为了照顾这些片段代码直接的连续操作的技巧。
另一个重点就是描述符,可以通过《80X86汇编语言程序设计教程》来详细学习GDT, IDT。
总的来说,GDT 是用来找段的,代码段,数据段,进程的tss ldt,都要依靠它来跳转到相应的地址中
IDT则是用来挂接中断的,后面许多外设,系统调用都是通过中断的方式和内核交互,以后和这GDT IDT打交道的位置不会少,需要先足够了解才行。**
从现在的角度来看这分linux 0.11 的启动,会发现他是没有安全启动,镜像安全校验一说的,后续安全这块也将越来越重要。