操作系统基础:03 操作系统启动

操作系统启动过程深度剖析

一、课程导入与boot set回顾

好,我们开始学习第三讲。在这一讲中,我们将全面且深入地讲解操作系统启动的全过程。本讲承接上一堂课,上堂课我们介绍了操作系统启动的首个部分 ——boot set。现在我们先来简单回顾一下 boot set 的主要工作,同时换个思路思考它在启动流程中所承担的关键任务。

计算机启动时,操作系统最初存储在硬盘上,而计算机的工作依赖于取指执行,这就要求代码必须处于内存中。所以,操作系统启动的首要任务就是将其从磁盘载入内存。如果不把操作系统载入内存,计算机就无法读取和执行其中的指令,操作系统也就无法正常运行。这就如同厨师没有菜谱就无法做菜一样,因此计算机一上电,就应将磁盘上的操作系统读取到内存里并妥善放置。

这一关键任务起始于 BIOS。BIOS 会将存储着 boot set.s 程序的引导扇区读入内存。boot set.s 程序在被读入内存后,会读取并加载 setup 等后续部分的操作系统代码。它先读入 set up,接着在屏幕上显示 “loading system” 的 logo,之后调用 13 号中断,把操作系统后面的 system 部分也读入内存。至此,操作系统成功进入内存,boot set 完成使命,接下来执行权交给 set up。

二、set up的关键作用与操作

(一)set up在启动流程中的重要地位

上一课结束时,boot set将执行权交给了set up。接下来的核心内容就是set up,它在操作系统启动过程中起着承上启下的关键作用。set up是一段汇编程序,采用汇编语言编写是因为其能够严格控制程序执行内容,不像c语言需编译后才能确定执行结果,且有时c语言的执行过程并不完全受人为直接控制。目前操作系统启动过程仍需精细控制,尚未完成,set up的工作至关重要。
在这里插入图片描述

(二)获取物理内存大小

我们采用上一课的方法,挑选部分关键代码详细解读。其中,int 0x15指令尤为重要。它的作用是获取物理内存的大小,具体操作是使用该指令作为参数,通过int 0x15(15号bios中断)获取内存大小,获取的值存放在ax中,紧接着ax被赋给一个偏移量为2的内存地址处(当前所有段都指向9000,实际是把9000左移四位再加2,即90002这个地址处存储扩展内存的大小)。这里涉及间接寻址。

计算机在英特尔刚推出pc机时,仅有一兆内存,后来人们把一兆之后的内存称作扩展内存。如今,内存规格多样,常见的有1g、4g等。在开机时,获取并保存系统内存大小至关重要。因为操作系统负责管理计算机硬件,内存是其中极为重要的部分,管理内存的关键第一步便是清楚知晓内存的大小。这就好比家庭分地,只有清楚自家土地面积,才能合理耕种,否则可能种到别人家地里。而且不同机器的内存大小可能不同,所以操作系统必须明确内存容量,才能有效管理。

(三)移动操作系统代码

set up还进行了一个移动操作。先是将si和di清零,然后进行移动,移动长度由cx决定。从代码可知,ax先置零后赋值,ds的值为9000。根据之前所学,ds、si和es、di协同工作,把从9000开始的一段内容,也就是整个操作系统代码,全部移动到零地址处。

此时,从零地址开始就是操作系统的内容。如果操作系统原来存放在7c00处,这样移动会不会影响正在执行的set up呢?答案是肯定的。所以之前要从7c00处上移一段空间,目的是给操作系统代码留出存放空间。之后,操作系统核心代码就固定在零地址处,而诸如word、ppt、qq等应用程序则存放在操作系统之上。

三、set up与保护模式

(一)进入保护模式的必要性

set up完成上述工作后,即将退出执行阶段,但操作系统的执行不能中断,所以set up最后做了一件至关重要的事——进入保护模式。在16位模式下,cs左移四位加ip最多只能产生20位地址,对应一兆的内存访问空间,这样的计算机性能有限。如今计算机需要访问4g的空间,传统的16位寻址方式已无法满足需求,所以必须切换到32位模式,即保护模式。

(二)保护模式的切换原理

在这里插入图片描述

16位模式和32位模式的本质区别在于cpu内部的解释程序不同。以前按照cs左移四位加ip解释地址,现在则需通过另一条电路实现。有一个特殊的寄存器cr0,其最后一位为0代表16位模式(实模式),为1则是保护模式。通过两条指令,将1赋给ax(ax最后一位是一),再把ax的值赋给cr0,cr0的最后一位变为1,cpu就会切换到另一条解释执行指令的电路。
在这里插入图片描述

这条新电路涉及一个重要概念——gdt(全局描述符表,global described table或describing table )。这是计算机硬件(英特尔结构)辅助实现的,目的是生成32位地址,提高计算机执行速度。在保护模式下,cs不再通过左移四位产生地址,而是成为选择子(selector)。以前cs存放地址,现在存放查表的下标(索引),真正的段地址存放在表项中。例如cs等于8,就是去选择gdt表中的一个表项,从该表项取出地址,再与ip偏移相加,产生32位地址。所以,要使这套机制正常工作,必须有gdt表,而set up的重要任务之一就是初始化gdt表。

(三)gdt表的初始化及作用

在这里插入图片描述

gdt表的每个表项通常由4个16位的word组成,所以每个表项是64位。寻址以字节为单位,之前提到的8对应的就是这样一个表项。通过几条指令的配合,就能初始化gdt表。
在这里插入图片描述

接下来,我们用 cs 等于 8 去查全局描述符表(GDT),需要明确查出来的结果,因为它决定程序的跳转执行位置。

GDT 表项内容由硬件规定。从结构看,表项共八个字节,可分段理解。像第 4 位关联段基址 31…24 等信息,第 0 位关联段基址 15…0 等信息 。从具体 gdt 定义的一些数据项,如 .word 0,0,0,0 等可知,有两个 GDT 表项值为 0x0000 ,一个用于只读(存代码),一个用于读写(存数据)。

从 GDT 表取出基值为 0 ,结合 jmpi 0,8 指令(8 是 GDT 索引 ),因 cs 取出基值为 0 ,加上指令中偏移量 ip 也为 0 ,得到地址为 0 地址。

此前 system 模块已挪到零地址,所以程序接下来跳到零地址执行,至此 set up 工作完成。

有了gdt表,再结合一条指令,即可启动32位模式。此时,工作方式发生变化,cs要到gdt表中查找地址,再与偏移相加。由于表的基址和偏移都是32位,ip也变为32位寄存器,两个32位相加仍是32位,这样就能访问4g的空间。

此外,在保护模式下,中断处理也发生变化,int 0、int 10、int 20等中断要到gdt表项中查找中断处理函数的入口地址,然后跳转执行。

四、system模块与make file

(一)system模块的结构与执行顺序

system模块由多个文件编译而成,其第一个段代码位于零地址处。为确保执行的是system的第一个模块,操作系统的编译结构有严格规定:磁盘主引导记录(MBR)的引导程序部分必须是boot set,否则BIOS无法正确读取后续操作系统相关内容;boot set从第二个扇区往后读,读取的是set up,这就要求操作系统第二个部分必须是set up 。所以,我们必须严格控制操作系统的编译结构,这和编写普通应用程序不同。编写应用程序时,使用集成开发环境,通过快捷键(如ctrl f9)就能自动编译,无需操心代码顺序。但编写操作系统时,一切都要自己控制,如果编译结果不符合要求,系统就无法正常工作。

(二)make file的作用与原理

在这里插入图片描述

为了控制操作系统的编译结构,我们需要编写make file。make file是一种树状结构,用于将一堆源码生成符合要求的操作系统镜像(image)。image依赖于多个部分产生,其中boot set依赖于boot set.s,set up依赖于set.app.s,同时还依赖于system和build等。当这些依赖项都准备好后,就会执行相应的操作,最终生成image。将image写入磁盘的零扇区,在开机引导时,操作系统就能顺利读入并启动,最终产生桌面,用户即可使用操作系统。

五、从汇编到c函数的跳转及main函数的工作

(一)head.s 函数的工作

head.s是system中的第一个模块,它的汇编语言与之前的boot set和set up不同,因为进入了保护模式,需要使用32位汇编代码。
在这里插入图片描述

head.s 作为 system 的第一个文件,发挥着重要作用。它又一次对中断描述符表(IDT)和全局描述符表(GDT)进行了初始化。此前 set up 建立临时 GDT 表,是为了执行 jmpi 0,8 这种跳转指令 ,实现特定位置的跳转。而如今,操作系统即将真正开始运作,就需要再次借助这些表,重新进行相关设置。

此外,head.s 还涉及诸多其他设置细节,例如开启 20 号地址线,开启后便能够访问 4G 内存。
在这里插入图片描述

在汇编语言方面,head.s 采用的汇编方式与之前大不相同。此前像 boot setset up 运用的是 16 位汇编,比如指令形式可能是 ex ax (目标操作数为 ax )。但从现在起,进入保护模式后,要访问 32 位,汇编方式转变为 eax 这种 32 位汇编形式 。操作数的角色也发生反转,之前的源操作数变为目标操作数,反之亦然。

实际上,操作系统在汇编使用上颇为复杂,一共涵盖三种汇编方式。起先是 16 位汇编,用于 boot setset up ;然后是 32 位汇编,像 head.s 以及后续部分程序的汇编代码都采用这种方式 ;还有一种是内嵌汇编,在 .c 文件中,当有些指令需要严格按照特定方式执行时,就会用到。

关于这三种汇编的语法,不会在此处详尽展开。后续用到时会简要讲解,若大家感兴趣,也可自行查阅相关资料。我们应先把握课程核心主线,在实际应用场景中再深入探究这些汇编语法。

(二)从汇编到c函数的跳转实现

在这里插入图片描述

head.s完成一系列设置后,要跳转到main.c执行。从汇编跳到c函数,原理和c函数调用c函数类似,主要依靠栈来实现。在c语言中,当一个函数调用另一个函数时,会先将参数从后往前压栈,然后把被调用函数的返回地址压栈,最后jump到被调用函数执行。被调用函数执行完后,遇到右括号,会执行ret指令,从栈中弹出返回地址,跳转回原来的位置继续执行。head.s跳到main.c也是类似的过程,通过压栈传递参数和返回地址,最终实现跳转。

(三)main函数的初始化工作

在这里插入图片描述

进入main函数后,会进行一系列初始化工作,如初始化内存、中断、键盘、鼠标、显示器、硬盘等。这里我们以内存初始化为例进行说明。

在这里插入图片描述

main函数中的mem init负责内存初始化工作,其目的是记录内存的使用情况。通过初始化一个数组(mem_map数组,也可称为mem_map表格)来实现,数组元素为零表示该内存区域未使用。通过循环操作,每次将当前值右移12位(相当于除以2的12次方,即4k),然后将这4k大小的区域置零,以4k为一片进行处理。这与内存管理中的分页概念相关,在后续内存管理部分会详细讲解。这样,内存初始化完成后,就形成了一个表示内存使用情况的表格。

六、操作系统启动过程总结

我们回顾本次和上次课的内容,讲解了boot set、set up、head和main等部分的工作,其中包括main函数中的mem init操作。到目前为止,操作系统的初始化和启动基本完成。操作系统启动主要做了两件事:一是将操作系统从磁盘读入内存,这样操作系统才能进行取指执行,发挥其功能;二是完成初始化工作,因为操作系统要管理计算机硬件设备,就需要针对每个硬件构建相应的数据结构并初始化,set up、head.s、main init、main等部分相互配合,获取硬件参数,初始化关键数据结构,为操作系统管理硬件做好准备

理解操作系统启动过程对后续学习非常重要。在后续学习中,比如内存管理等内容时,我们会经常回顾初始化时建立的各种数据结构,这些知识将发挥重要作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值