一、启动BIOS,准备实模式下的中断向量表和中断服务程序
纯硬件动作:CPU硬件设计为加电即进入16位实模式状态运行,加电瞬间强行将CS:IP指向0xFFFF0(BIOS程序入口地址)。
CS:IP计算方式:CS左移4位加上IP的值作为地址去取内容
实模式:特性是20位的存储器地址空间(即寻址最大范围是1MB)
IP/EIP:指令指针寄存器,存在于CPU中,记录将要执行的指令在代码段内的偏移地址,和CS组合为将要执行的指令的内存地址。实模式为绝对地址,指令指针16位,即IP;保护模式下为线性地址,指令指针32位,即EIP。
CS:代码段寄存器,存在于CPU中,指向CPU当前执行代码在内存中的区域。
BIOS在内存中建立中断向量表和中断服务程序:
中断向量表:记录所有中断号对应的中断服务程序的内存地址。256个中断向量,每个4B,其中两个字节是CS的值,两个字节是IP的值。每个中断向量都指向一个具体的中断服务程序。
中断服务程序:通过中断向量表的索引对中断进行响应服务
INT:中断
二、加载操作系统内核程序并为保护模式做准备:
1.加载第一部分内核代码——引导程序(bootsect)
CPU收到int 0x19中断 ---> 在中断向量表找到int 0x19中断 ---> 中断向量把CPU指向0x0E6F2,即int 0x19对应的中断服务程序(即启动加载服务程序,这个中断服务程序的作用是把软盘第一扇区中的程序512B加载到内存0x07C00处) ---> 第一扇区的内容就是bootsect,作用是把软盘中的操作系统程序载入内存
0x07C00(BOOTSEG):“约定”,bootsect被迫加载到0x07C00处
2.加载第二部分内核代码——SETUP
bootsect规划内存:对后续所涉及的内存位置进行设置,包括SETUPLEN(setup程序扇区数)、SETUPSEG(setup程序被加载到的位置)、BOOTSEG(启动扇区被BIOS加载的位置)、INITSEG(启动扇区将要移动到的新位置)、SYSSEG(内核被加载的位置)、ENDSEG(内核末尾位置)、ROOT_DEV(根文件系统设备号)
复制bootsect:bootsect启动程序将它自身512B从内存0x07C00(BOOTSEG)处复制到内存0x90000(INITSEG)处。此时段寄存器CS指向0x07C0。执行完跳转后CS的值变为0x9000(INITSEG),IP的值为从0x9000(INITSEG)到go:mov ax,cs这一行对应指令的偏移。也就是说,此时CS:IP指向go:mov ax,cs这一行。Linus设计意图是跳转之后,在新位置接着执行,而不是死循环。
booesect复制到了新的地方,下面对DS(数据段寄存器)、ES(附加段寄存器)、SS(栈基址寄存器)和SP(栈顶指针寄存器)进行调整。SS和SP联合使用,构成了栈数据在内存中的位置值。
将setup程序加载到内存中:借助BIOS提供的int 0x13中断向量所指向的中断服务程序完成。
int 0x19:BIOS执行,只负责把第一扇区代码加载到0x07C00位置。
int 0x13:bootsect执行,可以根据设计者的意图,把指定扇区代码加载到内存指定位置。所以使用0x13中断时,就要将指定扇区、加载的内存位置等信息传递给服务程序,即传参。参数传递完毕后,执行int 0x13指令,产生0x13中断,通过中断向量表找到中断服务程序,将第二个扇区开始的4个扇区,即set.s对应的程序加载至内存的SETUPSEG(0x90200)处。可以看出,在内存中bootsect后紧接着setup。
3.加载第三部分内核代码——system模块
bootsect借助BIOS提供的int 0x13中断加载第三批代码,这次加载的扇区数是240个。加载工作是由bootsect调用read_it子程序完成的,这个子程序将第六个扇区开始的240个扇区加载至内存的SYSSEG(0x10000)处往后的120KB空间中。由于读取的扇区数较多,时间较长,所以需要对软盘设备进行更多的监控,对读盘结果不断进行检测。
到此为止,第三批程序已经加载完毕,整个操作系统代码已全部加载至内存。bootsect主体工作已经完成,还需要确定一下根设备号。
到此为止,bootsect程序任务已经完成,下面通过执行“jumpi 0,SETUPSEG”这句跳转至0x90200处,即setup程序接着bootsect程序执行。
setup程序开始执行 ,它做的第一件事就是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据。这些机器系统数据被加载到内存0x90000~0x901FC处。BIOS提取的机器系统数据(510KB)将覆盖bootsect(512KB)程序所在的部分区域,仅有2KB未被覆盖。
三、为执行32位的main函数做过渡工作
1.关中断并将system移动到内存起始位置0x00000
先关中断,将CPU的标志寄存器EFLAGS(32位)中的中断允许标志IF置0。
cli:关中断,sti:开中断
接下来,setup程序将位于0x10000的内核程序复制到0x00000处。0x00000这个位置本来存放BIOS建立的中断向量表和BIOS数据区,这个操作把中断向量表和BIOS数据区完全覆盖,直到新的终端服务体系构建完毕之前,操作系统不再具备响应和处理中断的能力。
2.设置中断描述符表和全局描述符表
GDT:全局描述符表,可理解为所有进程的总目录,存放每一个任务局部描述符表(LDT)地址和任务状态段(TSS)地址,完成进程中各段的寻址、现场保护和现场恢复。
GDTR:GDT基址寄存器,在操作系统对GDT初始化完成后,可以用LGDT指令将GDT基地址加载至GDTR
IDT:中断描述符表,保护模式下所有中断服务程序的入口地址,类似于实模式下的中断向量表。16位中断机制用的是中断向量表,中断向量表位置是固定的,起始位置在0x00000处;32位中断机制用的是IDT,位置不固定,由IDTR来锁定其位置。
IDTR:IDT基址寄存器
setup程序自身提供的数据对IDTR和GDTR进行初始化设置。
此时的IDT是空表,因为已关中断。
创建这两个表是分两步进行的:1)在设计内核代码时,已经把两个表写好,并且把需要的数据也写好。2)将IDTR、GDTR指向表,由lidt和lgdt指令完成。
3.打开A20,实现32位寻址
打开A20:意味着CPU可以进行32位寻址,最大寻址空间为4GB。
4.为保护模式下执行head.s做准备
为了建立保护模式下的中断机制,setup程序对可编程中断控制器8259A进行重新编程。
8259A:专门为了对8085A和8086/8088进行中断控制而设计的芯片,是可以用程序控制的中断控制器。单个8259A能管理8级向量优先级中断,在不增加其他电路的情况下,最多可以级联成64级的向量优先级中断系统。
如果不对8259A进行重编程,int 0x00~int 0x1F中断将被覆盖。
setup程序通过下面代码的前两行将CPU工作方式设为保护模式。将CR0寄存器第0位(PE)置1,置1时CPU工作在保护模式下,置0时为实模式。
从setup程序跳转到head程序:jumpi 0,8 //0是段内偏移;8是保护模式下的段选择符,用于选择描述符表和描述符表项以及所要求的特权级。必须把8看作二进制的1000,4bit的每一位都有明确的意义。 这里1000的最后两位00表示内核特权级,用户特权级是11;第三位0表示GDT,LDT是1;1000的1表示所选表的1项来确定代码段的段基址和段限长等信息。
5.head.s开始执行
bootsect(加载bootsect到0x07C00,然后复制到0x90000)--->setup(加载setup到0x90200)--->head(先将head.s汇编成目标代码,将用C语言编写的内核程序编译成目标代码,然后链接成system模块)--->main
head程序在前,内核程序在后。system加载到内存后,setup将system模块复制到0x00000位置,实际上head程序就在0x00000这个位置。
head程序除了做一些调用main的准备工作之外,还用程序自身代码在程序所在的内存空间创建了内核分页机制,即在0x000000的位置创建了页目录表、页表、缓冲区、GDT、IDT,并将head程序已经执行过的代码所占内存空间覆盖。
_pg_dir:标识内核分页机制完成后的内核起始位置,也就是物理内存的起始位置0x000000.。
在实模式下,CS本身就是代码段基址。在保护模式下,CS本身是代码段选择符。
将DS、ES、FS、GS从实模式转变到保护模式。执行完毕后,DS、ES、FS、GS的值都成为0x10。0x10:00010000,最后两位00表示内核特权级,倒数第三位0表示选择GDT,第4、5位10是GDT的2项,也就是第三项。
SS也要转变为栈段选择符,栈顶指针也成为32位的csp。
0x10将SS设置为与前面4个段选择符的值相同。这样SS与前面讲过的4个段选择符相同,段基址都是指向0x000000,段限长都是8MB,特权级都是内核特权级。栈顶增长方向是从高地址向低地址的。
中断描述符