第零章 解惑
1、操作系统是什么?
类比一
- 古代部落害怕手下的人滥用武器伤害他人,所以制造武器是要向部落申请。而人们只有申请的资格,申请结果有部落决定。
- 设计操作系统的人害怕使用者滥用硬件(例如把操作系统所占据的内存恶意覆盖,计算机不就瘫痪了),提供给用户一些系统调用。当用户需要某个资源时,直接调用,于是操作系统就会把资源获取后交给用户进程。
- 总结:操作系统作为中间人,给用户间接提供资源的同时保护操作系统
类比二
- 社会组织提供邮局、电话使人们不需要走到远在他乡的亲朋好友身旁,就可以和他们说话;小情侣领证,社会组织方便预测未来人口趋势,做宏观调控。
- Linux系统中的内存管理,记录哪些是active,哪些是“脏页”,更好地管理内存。
- 总结:操作系统帮用户实现自身需要,帮助用户管理计算机资源。
2、写操作系统,哪些需要自己动手?
- 操作系统是一个软件,所以它的逻辑需要作用在硬件上才能体现出来,没有对应的硬件,操作系统一样什么都不会
- 作为使用者,需要了解硬件、硬件提供了哪些软件方面的接口、如何通过计算机指令控制硬件(可以参考硬件手册)。
3、软件如何访问硬件?
为每个硬件私人定制?
硬件是各种各样的,而且发展速度很快。如果操作系统腰围每个硬件量身定制一个驱动方法,OS开发者也许都活不过一个星期?!!对不起,赶紧 呸! 呸!! 呸!!!
正解:运用接口
- 接口分类:依据输入输出是串行还是并行分为串行接口和并行接口
- 串行硬件通过串行接口与CPU通信
- 访问外部硬件的两个方式
- 某个外设内存映射到一定范围的地址空间中,CPU 通过地址总线访问该内存区域是会落到外设的内存中,就像访问主板物理内存一样。
- 显卡:显卡是显示器的适配器,CPU 只和显卡通信,不直接和显示器交互。显卡上有片内存叫显存,它被映射到主机物理内存的低端
1MB
的0x8000
~0xBFFFF
(放到现在而言不太确切)。CPU 访问这片内存就是访问显存,往这片内存上写字节就是往屏幕上打印内容。
- 显卡:显卡是显示器的适配器,CPU 只和显卡通信,不直接和显示器交互。显卡上有片内存叫显存,它被映射到主机物理内存的低端
- 外设通过 IO 接口与 CPU 通信。而 CPU 不知道有这些设备的存在,只知道自己操作 IO 接口
- 某个外设内存映射到一定范围的地址空间中,CPU 通过地址总线访问该内存区域是会落到外设的内存中,就像访问主板物理内存一样。
4、应用程序是什么,怎么和操作系统配合?
- 应用程序是软件。对于 CPU 而言,操作系统和应用程序一样,CPU 只知道去 cs:ip 寄存器指向的内存存取指令并执行。
- 操作系统是人想出来的帮助自己管理计算机的软件。
- 如何配合:应用程序写好代码需要操作系统提供功能实现完整的程序,而这个功能即为系统函数的调用。所以,应用程序是通过系统调用来和操作系统配合完成某项功能的。
- 用户态和内核态:对于 CPU 而言的,指 CPU 运行在用户态(特权3级)还是内核态(特权0级)。
- 用户进程陷入内核态:由于内部或外部的中段发生,当前进程被暂时终止执行,其上下文被内核当地中断程序保存起来,开始执行一段内核的代码。
- 陷入内核:
- 程序执行时编译器怎么知道系统调用接口是什么:下载编译器时要选择版本,而每个版本在设计时就和系统绑定好了,各操作系统都有自己的系统调用号,而编译器厂商在代码中已经把宿主系统的系统调用号写死了。
5、内存访问为什么要分段?
- 分段是内存访问机制,是给 CPU 用的,所以只有 CPU 才关注段。
- 分段是历史遗留问题,从 CPU 8086 开始的。那时计算机只有物理地址,编译器将代码编译成机器指令,若要在 CPU 上运行无误,程序中的指令地址也必须是绝对物理地址,所以程序就必须放在固定的地方,两个编译出来同样地址的用户程序没法同时运行。先辈就想出让 CPU 采用“段基址 + 偏移地址”的方式来访问任意内存,这样使得程序可以重定位了。
- 重定位:简单来说就是将程序中的指令的地址改写成另一个地址,改写到的地址内容和原地址一样。
- 程序需要用到哪块内存,只要加载合适的段到段基址寄存器(cs、ds、es等)中,再给出相对于基地址的偏移地址即可,CPU 会加二者相加,结果送上地址总线。而基地址可以是任意的。
- 结论:加载用户程序时,只要将整个段的内容复制到新的位置,将段寄存器中的地址改为改成该地址,程序就可以准确无误的执行。
- 补充:
- 那时寄存器是16位的,故一个段最多可以访问 64KB ,而那时再小的内存也有 1MB ,所以改变段地址就可以用寄存器访问任意内存位置了。所以程序分段也是为了将大内存分为可以访问的小段 。(突发奇想:类似于运动记录的带有滑轨的摄影机)
- 16位寄存器访问20位地址空间(1M):段地址乘以16,加上段内偏移地址
6、代码为何中分的代码段、数据段,它们和内存访问机制中的段是一样的吗?
代码的分段
-
平坦模型:硬件段寄存器指向的内存为最大的 4GB
- 操作系统将整个 4GB 的内存放的同一个段里,不需要来回切换段寄存器,代码中不需要分段。
-
多段模型
- 程序员人为划分在代码中定义多个段
-
一般的高级语言不允许程序员自己将代码分成各种各样的段,这是因为其所用的编译器是针对某个操作系统编写的,该操作系统采用的是平坦模型,所以该编译器要编译出适合此操作系统加载运行的程序。由于处理器支持了具有分页机制的虚拟内存,操作系统也采用了分页模型,因此编译器会将程序按内容划分成代码段和数据段,如编译器gcc会把C语言写出的程序划分成代码段数据段、栈段、.bss 段、堆等部分。这会由操作系统将编译器编译出来的用户程序中的各个段分配到不同的物理内存上。对于目前咱们用高级语言编码来说,我们之所以不用关心如何将程序分段,正是由于编译器按平坦模型编译,而程序所依赖的操作系统又采用了虚拟内存管理,即处理器的分页机制。像汇编这种低级语言允许程序员为自己的程序分段,能够灵活地编排布局,这就属于人为将程序分成段了,也就是采用多段模型编程。
-
分段意义讨论:
-
指令存放在代码段,数据存放在数据段。但指令只需要在逻辑上是连续的就可以了。
-
将数据和代码分开好处:
-
可以赋予它们不同的属性(例如数据是可写的,代码不可写只可读)
-
提高 CPU 内部缓存的命中率
c a c h e 的命中率 = N c / ( N c + N m ) cache的命中率 = Nc / (Nc + Nm) cache的命中率=Nc/(Nc+Nm)
-
- 节省内存(代码共享)
-
-
数据段和代码段的属性是谁添加的,又是谁保护的?
- 编译器负责挑选出数据具备的属性,从而根据属性将程序片段分类,比如,划分出了只读属性的代码段和可写属性的数据段。再补充一下,编译器并没有让段具备某种属性,对于代码段,编译器所做的只是将代码归类到一起而已,也就是将程序中的有关代码的多个section合并成一个大的segment(这就是我们所说的代码段),它并没有为代码段添加额外的信息。
- 操作系统通过设置 GDT 全局描述符表来构建段描述符,在段描述符中指定 段的位置、大小及属性(包括S字段和TYPE 字段)。也就是说,操作系统认为代码应该是只读的,所以给用来指向代码段的那个段描述符设置了只读的属性,这才是真正给段添加属性的地方。
- CPU 中的段寄存器提前被操作系统赋予相应的选择子(后面章节会讲什么是选择子,暂时将其理解为相当于段基址),从而确定了指向的段。在执行指令时,会根据该段的属性来判断指令的行为,若有返回则发出异常。
与内存段的关系
- 程序中的段只是逻辑上的划分,无须连续(类似于下图的各种水果)
- 内存分段指的是处理器为访问内存而采用的机制,称之为内存分段机制,通过这个机制分的段是连续的,(类似于下图的盘子)
- 总结:程序分段是软件中人为逻辑划分的内存区域,它本身也是内存,所以处理器在访问该区域时,也会采用内存分段机制,用段寄存器指向该区域的起始地址
7、物理地址、逻辑地址、有效地址、线性地址、虚拟地址的区别
物理地址
- 物理内存真正的地址,不管是虚拟地址、线性地址, CPU 最终都要通过物理地址去访问内存,可以形象地解释为物理地址是内存访问的终点站。
- 实模式下:“段基址 + 段内偏移地址” 经过段部件的处理,直接输出就是物理地址,CPU 可用此直接访问内存。
线性地址
- 保护模式下:“段基址 + 段内偏移地址” 称为线性地址。
- 此时的段基址已经不是真正的地址,而是选择子。选择子的本质是个索引(类似于数组下标),通过这个所有你便能在 GDT 中找到相应的段描述符,描述符中记载段的起始、大小等信息,这样便可以得到真正的段基址。
- 若没有开启分页功能,此线性地址经过上述过程就可以直接访问内存。
虚拟地址
- 开启分页功能,线性地址就多了一个名字——虚拟地址(在分页机制下线性地址和虚拟地址是一回事),虚拟地址通过 CPU 页部件转换成具体的地址。
有效地址(逻辑地址)
- 无论是在实模式还是保护模式下,段内偏移地址又称为有效地址,也称为逻辑地址,这是程序员可见的地址。
- 原因:最终的地址是由段基址和段内偏移地址组合而成的。由于段基址已经有默认的啦,要么是在实模式下的默认段寄存器中,要么是在保护模式下的默认段选择子寄存器指向的段描述符中,所以只要给出段内偏移地址就行了,这个地址虽然只是段内偏移,但加上默认的段基址,依然足够有效。
8、段重叠
- 依然假设在实模式下(并不是说在保护模式下就不存在段重叠,只是这样就会少解释了相关数据结构,如段描述符,不过这不重要,原理是一样的),一个段最大为64KB,其大小由段内偏移地址寻址范围决定,也就是2的16次方。其起始位置由段基地址决定。CPU的内存寻址方式是:给我一个段基址,个相对于该段起始位置的偏移地址,我就能访问到相应内存。它并不要求一个内存地址只隶属于某一个段。欲访问内存OxC03,段基址可以选择0xC00,0xC01,0xC02,0xC03,只不过是段内偏移量要根据段基地址来调整罢了。用这种“段基地址:段内偏移”的组合,0xC00:3和0xC02:1是等价的,它们都访问到同一个物理内存块。但段的大小决定于段内偏移地址寻址范围,假设段A的段基址是从0xC00开始,段B的段基址是从0xC02开始,在16位宽度的寻址范围内,这两个段都能访问到0xC05这块内存。用段A去访问,其偏移为5,用段B去访问,其偏移量为3。这样一来,用段B和段A在地址0xC02之后,一直到段B偏移地址为0xfffe的部分,像是重叠在一起了,这就是段重叠了
9、什么是平坦模型?
平坦模型是相对于多段模型来说的,所以说平坦模型指的就是一个段。比如在实模式下,访问超过64KB的内存,需要重新指定不同的段基址,通过这种迂回变通的方式才能达到目的。在保护模式下,由于其是32位的,寻址范围便能够达到4GB,段内偏移地址也是地址,所以也是32位。可见,在32位环境下用一个段就能够访问到硬件所支持的所有内存。也就是说,段的大小可以是地址总线能够到达的范围。既然平坦模型是相对于多段模型来说的,为什么不称为单段模型,而称为平坦呢,我估计很多读者已经明白了,用多个小段再加上不断换段基址的方式访问内存确实够麻烦的,可能换着换着就晕了,别忘记了,这种多段模型为了访问到1MB地址空间,还需要额外打开A20地址线呢,这种访存方式本身就是种补救措施,相当于给硬件打了个补丁,既然是补丁,访问内存的过程必然是不顺畅的。相对于那么麻烦的多段模型,平坦模型不需要额外打开A20地址线,不需要来回切换段基址就可以在地址空间内任意翱翔。如果把内存段比喻成小格子的话,平坦模型下的内存访问,没有众多小格子成为羁绊,可谓一路“平坦”。
10、cs、ds 这类 steg 段寄存器,位宽是多少?
段寄存器(Segment reg)
- CS——代码段寄存器(Code Segment Register),代码段的段基址
- DS——数据段寄存器(Data Segment Register),数据段的段基址
- ES——附加段寄存器 (Extra Segment Register),附加数据段的段基址,称为”附加“是因为此段寄存器用途不像其他sreg 那样固定,可以额外做他用
- FS——附加段寄存器(Extra Segment Register),附加数据段的段基址
- GS——附加段寄存器(Extra Segment Register),附加数据段的段基址
- SS——堆栈段寄存器(Stack Segment Register),堆栈段的段值
段寄存器的位宽
- 每种模式下,段寄存器中值的意义是不同的,但不管其为何值,在段寄存器中所表达的都是指向的段在哪里。
- 在实模式下,CS、DS、ES、SS 中的值为段基址,是具体的物理地址,内存单元的逻辑地址仍为“段基值:段内偏移量”的形式。
- 在保护模式下,装入段寄存器的不再是段地址,而是“段选择子”(Selector),当然,选择子也是数值,其依然为16位宽度。
- 可见,在32位 CPU 中,sreg 无论是工作在16位的实模式,还是32位的保护模式,用的段寄存器都是同一组,并且在32位下的段选择子是16位宽度,排除了段寄存器在32位环境下是32位宽的可能,综上所述,sreg 都是16位宽。
11、局部变量和函数参数为什么要放在栈中?
局部变量放在栈中
- 局部和全局变量简介:
- 局部变量,顾名思义其作用域属于局部,并不是像 static那样属于全局性的。
- 全局的变量,意味着谁都可以随时随地访问,所以其放在数据段中。
- 局部变量只是自己在用,放在数据段中纯属浪费空间,没有必要,故将其放在自己的栈中,随时可以清理,真正体现了局部的意义
堆和栈名的 ”多变“
堆是堆,而堆栈就是栈,和堆没关系,只是都这么叫。栈和堆栈都是指的栈,在C程序的内存布局中,由于堆和栈的地址空间是接壤的,栈从高地址往低地址发展,堆是从低地址往高地址发展,堆和栈早晚会碰头,它们各自的大小取决于实际的使用情况,界限并不明朗,所以这可能是堆栈常放在一直称呼的原因吧。
函数参数放在栈中
- 函数参数是局部的,只有这个函数用这个参数,无须将其放在数据段
- 函数是在程序执行过程中调用的,属于动态的调用,编译时无法预测会何时调用及被调用的次数,函数的参数及返回值都需要内存来存储,需要的存储空间也是不确定的(万一是很大的递归,有点难办 >__< )
12、汇编语言比C语言快
一定程度上是谬论!!!
无论什么语言,程序最终都是给CPU运行的。而CPU不知道什么是汇编语言、C语言,甚至Java、PHP、Python等,不管什么语言,编译器最终翻译出来的都是机器指令。所以在这一点来说,汇编语言编译器编译出来的机器指令和C编译器编译出来的机器指令无异。
为什么汇编会更快?
- 分析
- 汇编语言:生成的指令数更少,从而“显得”执行得快。汇编语言本身就是机器指令的符号化,汇编中的符号和机器指令一一对应。用汇编语言写程序就相当于直接在写机器指令,汇编语言编译器并不会添加额外的语句,因此汇编语言写的程序会更直接,CPU不会因多执行一些无关的指令而浪费时间
- C语言:C编译器在背后做了大量的工作,不仅如此,出于通用性、易用性或者其他方面的考虑,C编译器往往会在背后加入额外的C语言代码来支撑,因此实际的C代码量就变得很大。另外在编译阶段,C代码会率先被编译成汇编代码,然后再由汇编器将汇编代码翻译成机器指令,由于**C代码已经变得冗余了,编译出的汇编代码自然也会冗余,其机器指令也会多很多 **
- 总结:高级语言如C语言为了通用性等,需要兼顾的东西比较多,往往还加入了一些额外的代码,因此编译出来的汇编代码比较多,很多部分都是一些周边功能,并不是直接起作用的,不如用汇编语言直接写功能相关的部分效果来得更直接,C语言被编译成机器指令后,生成的机器指令当然也包括这些额外的部分,相当于多执行了一些“看似没用”的指令,因此会比直接用汇编语言慢。
13、编译型程序和解释型程序的区别
解释型语言
- 也称脚本语言,本身是文本文件,是脚本解释器的输入。
- 执行时本质上是脚本解释器时时分析脚本,动态根据关键字和语法来做出相应的行为,脚本出现错误,先前的代码也会被执行。
编译型语言
- 编译器编译出来的程序,运行时就是一个进程。
- 由操作系统直接调用:操作系统直接加载到内存后,操作系统将 CS :IP 寄存器指向这个程序的入口,使它直接上 CPU 运行。
14、BIOS中断、DOS中断、Linux中断区别
中断和异常
无论在实模式,还是保护模式,在任何情况下都会有来自外部和内部的事件发生
- 异常(Exception):事件来自于 CPU 内部,例如 CPU 计算时发现分母零,就会抛出异常
- 中断:事件来自于 CPU 外部,即有外部设备发出并通知 CPU ,此时这个事件就被称为中断
中断向量表
- 引入:BOIS 和 DOS 都是存在于实模式下的程序,它们建立的中断调用都是建立在中断向量表(Interrupt Vector Table)中的。通过软中断指令 int 中断号来调用的。
- 简介:
- 中断向量表中每个中断向量大小都是4字节。这4字节描述有一个中断处理例程(程序)的段基址和段内偏移地址
- 因为中断向量表的长度为1024字节,故该表最多可以容纳256个中断向量处理程序
- 计算机启动之初,中断向量表中的中断例程是由 BOIS 建立的,它从物理地址 0x0000 处初始化并在中断向量表中添加各种处理例程
BOIS中断
- 主要功能:提供硬件访问方法,但不通过 BOIS 也可以访问硬件(要不BOIS中断处理程序如何操作硬件)
操作硬件:通过 in/out 指令来读写外设端口
- BOIS 添加中断处理例程的原因
- 给自己用,便于频繁执行类似的某段代码
- 给后来的程序用,入加载器和 boot loader
- BOIS 设置中断处理程序也要调用别人的函数例程
BIOS也是软件,也要有求于别人。首先硬件厂商为了让自己生产的产品易用,肯定事先写好了一组调用接口,必然是越简单越好,直接给接口函数传一个参数,硬件就能返回一个输出,如果不易用的话,厂商肯定倒闭了。
那这些硬件自己的接口代码在哪里呢?
每个外设,包括显卡、键盘、各种控制器等,都有自己的内存(主板也有自己的内存,BIOS就存放在里面),不过这种内存都是只读存储器ROM。硬件自己的功能调用例程及初始化代码就存放在这ROM中。根据规范,第1个内存单元的内容是0x55,第2个存储单元是OxAA,第3个存储单位是该rom 中以512字节为单位的代码长度。从第4个存储单元起就是实际代码了,直到第3个存储单元所示的长度为止。
引出问题:CPU 如何访问到外设的 ROM?
- 内存映射:通过地址总线将外设自己的内存映射到某个内存区域(并不是映射到主板上插的内存条中)。
- 端口操作:外设都有自己的控制器,控制器上有寄存器,这些寄存器就是所谓的端口,通过in/out指令读写端口来访问硬件的内存。
从内存的物理地址0xA0000开始到0xFFFFF 这部分内存中,一部分是专门用来做映射的,如果硬件存在,硬件自己的ROM会被映射到这片内存中的某处,至于如何映射过去的,咱们暂时先不要深入了,这是硬件完成的工作。
从内存的物理地址0xA0000开始到0xFFFFF 这部分内存中,一部分是专门用来做映射的,如果硬件存在,硬件自己的ROM会被映射到这片内存中的某处,至于如何映射过去的,咱们暂时先不要深入了,这是硬件完成的工作。
如图0-11所示,BIOS 在运行期间会扫描OxCO000到OxE0000之间的内存,若在某个区域发现前两个字节是Ox55和0xAA时,这意味着该区域对应的rom中有代码存在,再对该区域做累加和检查,若结果与第3个字节的值相符,说明代码无误,就从第4个字节进入。这时开始执行了硬件自带的例程以初始化硬件自身,最后,BIOS填写中断向量表中相关项,使它们指向硬件自带的例程。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOwSaWVq-1680362989872)(images\0/image-20230327201001633.png)]
中断向量表中第OH~1FH项是BIOS 中断。
DOS 中断
- 0x20 ~ 0x27是 DOS 中断,与BOIS不冲突
- 可以调用 BOIS 中断
- DOS 中断只占 0x21 这个中断号,那么DOS中断调用中那么多功能是如何实现的?是通过先往 ah寄存器中写好子功能号,再执行int Ox21.这时在中断向量表中第0x21个表项,即物理地址Ox21*4处中的中断处理程序开始根据寄存器ah中的值来调用相应的子功能。
Linux 中断
Linux的系统调用和DOS中断调用类似,不过 Linux是通过int 0x80 指令进入一个中断程序后再根据eax寄存器的值来调用不同的子功能函数的。如果在实模式下执行 int 指令,会自动去访问中断向量表。如果在保护模式下执行int指令,则会自动访问中断描述符表。
15、Section 和 Segment 的区别
Section
- 称为节,是指在汇编源码中经由关键字 section 或 segment 修饰、逻辑划分的指令或数据区域
- 汇编器会将这两个关键字修饰的区域在目标文件中编译成节,“节” 最初诞生于目标文件
Segment
- 称为段,是链接器根据目标文件中属性相同的多个 section 合并后的 section 集合,这个集合称为 segment,即段
- 链接器比把目标文件链接成可执行文件,所以段最终诞生于可执行文件中,平时所说的可执行程序内存空间中的代码段和数据段就是指 segment
16、魔数
- 神奇(带有“生机勃勃的气“的数字? maybe”)数字:被用来为重要的数据定义标签,用独特的数字唯一地标识该数据
- 应用场景:
- elf文件头
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
这个 Magic 后面的一长串就是魔数,elf 解析器(通常是程序加载器)用它来校验文件类型是否是 elf
17、操作系统如何识别文件系统
-
一个硬盘上可以有很多分区,每个分区的格式又可以不同。就拿Linux 来说,既能识别ext3,又能识别ext4。
-
文件系统也有自己的魔数。各分区都有超级块,一般位于本分区的第2个扇区,比如若各分区的扇区以0开始索引,其第1个扇区便是超级块的起始扇区。超级块里面记录了此分区的信息,其中就有文件系统的魔数,一种文件系统对应一个魔数,比对此值便可知道是否为文件系统类型。
18、指令集、体系结构、微架构、编程语言
指令集
- 具体的一套指令编码,一种 CPU 只能识别一种指令集,所以很多 CPU 都以其支持的指令集来称呼
- 例如:CISC(x86)、RISC(ARM、MIPS、Power、C6000都是 RISC 指令体系中的)
- ARM:主要用在手机中,作为手机的处理器
- Power:IBM 用在服务器上处理器
- C6000:数字信号处理器,广泛用于视频处理
- MIPS:严格遵守 RISC 思想,业界公认的优秀处理器,我国的龙芯用的就是 mips 指令集
微架构:指令集的物理实现方式
编程语言:由编译器按照 CPU 的指令集翻译成该 CPU 的指令
体系结构:CPU 指令体系结构
19、MBR、EBR、DBR、OBR
首先,这几个概念是围绕计算机系统的控制权交接展开的
MBR
-
计算机在接电后运行的是输入输出基本系统 BOIS ,但因为 BIOS 所在的空间有限,代码量较小,功能受限,BIOS 在完成一些简单的检测和初始化工作后,处理器把使用权交给 MBR
-
MBR 是主引导记录(Master 或 Main Boot Record),它存在 0 盘 0 道 1 扇区(MBR 引导扇区),这里是用 CHS 方式表示 MBR 扇区的地址,即地址以 1 开始(LBA 以 0 开始)
-
MBR 扇区的内容
- 446 字节的引导程序及参数
- 64 字节的分区表
- 2 字节结束标记 0x55 和 0xaa
-
BIOS 知道 MBR 在 0 盘 0 道 1 扇区,因此它会将 MBR 引导程序加载到物理地址 0x7c00,然后跳过去执行,这样BIOS 就把处理器使用权移交给MBR了
-
BIOS 把控制权交给 MBR,由 MBR 从众多可能的接力选手中挑出合适的人选并交出系统控制权,这个过程就是由“主引导程序”去找“次引导程序”,这么说的意思是“次引导程序”不止一个。
为什么BIOS不直接把控制权交给“次引导程序” ?
BIOS受限于其主板上的存储空间,代码量有限,本身的工作还做不过来呢,因此心有余而力不足。
-
MBR 中 64 字节大小的分区表:存储着分区信息,有4 个分区,每个分区表项占16字节,这4个分区就是“次引导程序”的候选,MBR 引导程序开始遍历这4个分区,并找到合适的分区交接系统控制权
通常情况下这个“次引导程序”就是操作系统提供的加载器,因此MBR引导程序的任务就是把控制权交给操作系统加载器,由该加载器完成操作系统的自举,最终使控制权交付给操作系统内核。但是各分区都有可能存在操作系统,MBR也不知道操作系统在哪里,它甚至不知道分区上的二进制01串是指令,还是普通数据,好吧,它根本分不清楚上面的是什么,谈何权利交接呢。
为了让MBR 知道哪里有操作系统,我们在分区时,如果想在某个分区中安装操作系统,就用分区上具将该分区设置为活动分区,设置活动分区的本质就是把分区表中该分区对应的分区表项中的活动标记为0x80。MBR知道“活动分区”意味着该分区中存在操作系统,这也是约定好的。活动分区标记位于分区表项中最开始的1字节(有关分区内容,后面介绍分区的章节中会细说),其值要么为0x80,要么为0,其他值都是非法的。Ox80表示此分区上有引导程序,0表示没引导程序,该分区不可引导。MBR在分析分区表时通过辨识“活动分区”的标记0x80开始找活动分区,如果找到了,就将CPU使用权交给此分区上的引导程序,此引导程序通常是内核加载器,下面就直接以它为例。
OBR 和 DBR
- 内核加载器、操作系统引导程序
- OBR 继承 DBR
总结
- EBR与MBR结构相同,但位置和数量都不同,整个硬盘只有1个MBR,其位于整个硬盘最开始的扇区——0道0道1扇区。而EBR可有无数个,具体位置取决于扩展分区的分配情况,总之是位于各子扩展分区最开始的扇区。
- OBR其实就是DBR,指的都是操作系统引导程序,位于各分区(主分区或逻辑分区)最开始的扇区,访扇区称为操作系统引导扇区,即OBR引导扇区。OBR 的数量与分区数有关,等于主分区数加逻辑分区数之和,友情提示:一个子扩展分区中只包含1个逻辑分区。
- MBR 和EBR是分区工具创建维护的,不属于操作系统管理的范围,因此操作系统不可以往里面写东西,注意这里所说的是“不可以”,其实操作系统是有能力读写任何地址的,只是如果这样做的话会破坏“系统控制权接力赛”所使用的数据,下次开机后就无法启动了。OBR 是各分区(主分区或逻辑分区)最开始的扇区,因此属于操作系统管理。
- DBR、OBR、MBR、EBR 都包含引导程序,因此它们都称为引导扇区,只要该扇区中存在可执行的程序,该扇区就是可引导扇区。若该扇区位于整个硬盘最开始的扇区并且以 0x55 和 0xaa 结束,BIOS 就认为该扇区中存在MBR,该扇区就是 MBR 引导扇区。若该扇区位于各分区最开始的扇区,并且以Ox55和 0xaa 结束,MRR就认为该扇区中有操作系统引导程序 OBR,该扇区就是 OBR 引导扇区。