从开机加电到执行main函数之前的过程

一、启动BIOS,准备实模式下的中断向量表和中断服务程序

纯硬件动作:CPU硬件设计为加电即进入16位实模式状态运行,加电瞬间强行将CS:IP指向0xFFFF0(BIOS程序入口地址)。

CS:IP计算方式:CS左移4位加上IP的值作为地址去取内容

6d6f21f0f2644c05b5dc9f63bb563671.jpeg

实模式:特性是20位的存储器地址空间(即寻址最大范围是1MB)

IP/EIP:指令指针寄存器,存在于CPU中,记录将要执行的指令在代码段内的偏移地址,和CS组合为将要执行的指令的内存地址。实模式为绝对地址,指令指针16位,即IP;保护模式下为线性地址,指令指针32位,即EIP。

CS:代码段寄存器,存在于CPU中,指向CPU当前执行代码在内存中的区域。

BIOS在内存中建立中断向量表和中断服务程序:dd9f043c99c84619b20ee96104121af7.jpeg

 中断向量表:记录所有中断号对应的中断服务程序的内存地址。256个中断向量,每个4B,其中两个字节是CS的值,两个字节是IP的值。每个中断向量都指向一个具体的中断服务程序。

中断服务程序:通过中断向量表的索引对中断进行响应服务

INT:中断

二、加载操作系统内核程序并为保护模式做准备:

1.加载第一部分内核代码——引导程序(bootsect)

CPU收到int 0x19中断 ---> 在中断向量表找到int 0x19中断 ---> 中断向量把CPU指向0x0E6F2,即int 0x19对应的中断服务程序(即启动加载服务程序,这个中断服务程序的作用是把软盘第一扇区中的程序512B加载到内存0x07C00处) ---> 第一扇区的内容就是bootsect,作用是把软盘中的操作系统程序载入内存

0x07C00(BOOTSEG):“约定”,bootsect被迫加载到0x07C00处

c6c54dbbf7c74362ad79c1fe37d0dd3b.jpeg185ee557c56a4600a43ccd89b6ce898d.jpeg

2.加载第二部分内核代码——SETUP

bootsect规划内存:对后续所涉及的内存位置进行设置,包括SETUPLEN(setup程序扇区数)、SETUPSEG(setup程序被加载到的位置)、BOOTSEG(启动扇区被BIOS加载的位置)、INITSEG(启动扇区将要移动到的新位置)、SYSSEG(内核被加载的位置)、ENDSEG(内核末尾位置)、ROOT_DEV(根文件系统设备号)

 6a0035c521844d55b2a22323539a636c.jpeg

复制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设计意图是跳转之后,在新位置接着执行,而不是死循环。

6f12a663a34c41748bbeda7e3842a8f7.jpeg1126220ca0b44cb591c98b826b5770f4.jpeg

booesect复制到了新的地方,下面对DS(数据段寄存器)、ES(附加段寄存器)、SS(栈基址寄存器)和SP(栈顶指针寄存器)进行调整。SS和SP联合使用,构成了栈数据在内存中的位置值。

3b02b2c865f14d3684917f81a3af7ff2.jpeg

将setup程序加载到内存中:借助BIOS提供的int 0x13中断向量所指向的中断服务程序完成。

int 0x19:BIOS执行,只负责把第一扇区代码加载到0x07C00位置。

int 0x13:bootsect执行,可以根据设计者的意图,把指定扇区代码加载到内存指定位置。所以使用0x13中断时,就要将指定扇区、加载的内存位置等信息传递给服务程序,即传参。参数传递完毕后,执行int 0x13指令,产生0x13中断,通过中断向量表找到中断服务程序,将第二个扇区开始的4个扇区,即set.s对应的程序加载至内存的SETUPSEG(0x90200)处。可以看出,在内存中bootsect后紧接着setup。

c6cbdd16acec405bacfdb43233c7ce96.jpege600a23fd38a4b288001a88a75454e1b.jpeg

3.加载第三部分内核代码——system模块

 bootsect借助BIOS提供的int 0x13中断加载第三批代码,这次加载的扇区数是240个。加载工作是由bootsect调用read_it子程序完成的,这个子程序将第六个扇区开始的240个扇区加载至内存的SYSSEG(0x10000)处往后的120KB空间中。由于读取的扇区数较多,时间较长,所以需要对软盘设备进行更多的监控,对读盘结果不断进行检测。

 941980240f9745fb8210ba4a3eaaa1fb.jpegc1a44755d08c4a7c896421ebaf2511c3.jpeg

 到此为止,第三批程序已经加载完毕,整个操作系统代码已全部加载至内存。bootsect主体工作已经完成,还需要确定一下根设备号。

30f89f7aa0ac4d609248030407683077.jpeg

到此为止,bootsect程序任务已经完成,下面通过执行“jumpi 0,SETUPSEG”这句跳转至0x90200处,即setup程序接着bootsect程序执行。

 2f8ae8b67f54411b8e132366c78f9397.jpeg

setup程序开始执行 ,它做的第一件事就是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据。这些机器系统数据被加载到内存0x90000~0x901FC处。BIOS提取的机器系统数据(510KB)将覆盖bootsect(512KB)程序所在的部分区域,仅有2KB未被覆盖。

2dc8a38aa18342959b4a6d6a84820035.jpeg

三、为执行32位的main函数做过渡工作

1.关中断并将system移动到内存起始位置0x00000

先关中断,将CPU的标志寄存器EFLAGS(32位)中的中断允许标志IF置0。

cli:关中断,sti:开中断

0e8a4299d0ef47b88c5ed0a6abaff267.jpeg

接下来,setup程序将位于0x10000的内核程序复制到0x00000处。0x00000这个位置本来存放BIOS建立的中断向量表和BIOS数据区,这个操作把中断向量表和BIOS数据区完全覆盖,直到新的终端服务体系构建完毕之前,操作系统不再具备响应和处理中断的能力。

b3671cf939d84392881da0b4e2bd3d68.png

fdcac5cb6c2d40d29e62e77ec90e771a.jpeg

 2.设置中断描述符表和全局描述符表

GDT:全局描述符表,可理解为所有进程的总目录,存放每一个任务局部描述符表(LDT)地址和任务状态段(TSS)地址,完成进程中各段的寻址、现场保护和现场恢复。

GDTR:GDT基址寄存器,在操作系统对GDT初始化完成后,可以用LGDT指令将GDT基地址加载至GDTR

IDT:中断描述符表,保护模式下所有中断服务程序的入口地址,类似于实模式下的中断向量表。16位中断机制用的是中断向量表,中断向量表位置是固定的,起始位置在0x00000处;32位中断机制用的是IDT,位置不固定,由IDTR来锁定其位置。

IDTR:IDT基址寄存器

setup程序自身提供的数据对IDTR和GDTR进行初始化设置。

294f1590324a4fe3bb394d6143584782.jpeg2bc0d8d3f0aa4b2088a4635b3c332d84.png

 此时的IDT是空表,因为已关中断。

创建这两个表是分两步进行的:1)在设计内核代码时,已经把两个表写好,并且把需要的数据也写好。2)将IDTR、GDTR指向表,由lidt和lgdt指令完成。

3.打开A20,实现32位寻址

打开A20:意味着CPU可以进行32位寻址,最大寻址空间为4GB。

20587c63b5d4464eb19ed83b9f3934c5.jpeg4a133a1e748c4999b1bfe658e42a3161.png

 4.为保护模式下执行head.s做准备

为了建立保护模式下的中断机制,setup程序对可编程中断控制器8259A进行重新编程。

8259A:专门为了对8085A和8086/8088进行中断控制而设计的芯片,是可以用程序控制的中断控制器。单个8259A能管理8级向量优先级中断,在不增加其他电路的情况下,最多可以级联成64级的向量优先级中断系统。

 50726b135f394814856651c37c96ab83.jpeg

 如果不对8259A进行重编程,int 0x00~int 0x1F中断将被覆盖。

 setup程序通过下面代码的前两行将CPU工作方式设为保护模式。将CR0寄存器第0位(PE)置1,置1时CPU工作在保护模式下,置0时为实模式。

ad6456ea917845fbae6f49353f1a89f6.png

 f776b8bfae0e4b31a5ac4481466effa6.jpeg4589d7fd610b490280777142693eb5c7.jpeg

从setup程序跳转到head程序:jumpi 0,8        //0是段内偏移;8是保护模式下的段选择符,用于选择描述符表和描述符表项以及所要求的特权级。必须把8看作二进制的1000,4bit的每一位都有明确的意义。 这里1000的最后两位00表示内核特权级,用户特权级是11;第三位0表示GDT,LDT是1;1000的1表示所选表的1项来确定代码段的段基址和段限长等信息。

b0e44279703e4ea3a483e0e27132f27a.jpeg

 cd1e850e48c74e1a95155c20c70ba36f.jpeg

 5.head.s开始执行

 bootsect(加载bootsect到0x07C00,然后复制到0x90000)--->setup(加载setup到0x90200)--->head(先将head.s汇编成目标代码,将用C语言编写的内核程序编译成目标代码,然后链接成system模块)--->main

9294b47eed2749788cd2666886efb3e8.jpeg

 head程序在前,内核程序在后。system加载到内存后,setup将system模块复制到0x00000位置,实际上head程序就在0x00000这个位置。

head程序除了做一些调用main的准备工作之外,还用程序自身代码在程序所在的内存空间创建了内核分页机制,即在0x000000的位置创建了页目录表、页表、缓冲区、GDT、IDT,并将head程序已经执行过的代码所占内存空间覆盖。

_pg_dir:标识内核分页机制完成后的内核起始位置,也就是物理内存的起始位置0x000000.。

ac8cc1a514f143029307f4a308d9877e.jpeg

 在实模式下,CS本身就是代码段基址。在保护模式下,CS本身是代码段选择符。

将DS、ES、FS、GS从实模式转变到保护模式。执行完毕后,DS、ES、FS、GS的值都成为0x10。0x10:00010000,最后两位00表示内核特权级,倒数第三位0表示选择GDT,第4、5位10是GDT的2项,也就是第三项。

 c48e3040820d4385b5743424e10e35cc.jpeg

SS也要转变为栈段选择符,栈顶指针也成为32位的csp。

0x10将SS设置为与前面4个段选择符的值相同。这样SS与前面讲过的4个段选择符相同,段基址都是指向0x000000,段限长都是8MB,特权级都是内核特权级。栈顶增长方向是从高地址向低地址的。

a20ae0d9eadf4091ba45239ec6d8dc9c.jpeg

 18a548d0e9d1496db572865bffc98729.jpeg中断描述符

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值