大家好,这里是第五位面壁者。
在正式开始之前要先和大家说两声抱歉,第一句抱歉是因为上次我有说将RWLinux放在优快云上让大家免积分随意下载,但是后来经过小伙伴提醒发现,优快云系统总是自动提高下载积分限制,如果我强制让所有人零积分下载,优快云审核就一直过不去,这一点是我欠考虑了,大家如果需要的话还是发邮件到我的邮箱,地址在这里;第二句抱歉为了我的更新速度,诸位请放心,x86这个系列,我肯定会更完的,但是受限于个人水平和精力,速度肯定没那么快。同样,所有平台同步更新,账号如上。
好了废话不多说,开始我们今天的内容,这一期我们一起学习x86架构的内存管理,这一期可以说是这个系列的基石和精华,往后的中断与异常管理和任务管理都和这一章紧密相连,而搞懂这三章,对大家理解操作系统底层运行原理也大有裨益,把硬件和操作系统底层原理搞清楚了,才能啃得动虚拟机高并发多线程这些高端技能。具体的内容安排,咱们由难到易,先从8086实模式的内存管理讲起,然后再到保护模式,最后是IA-32e 模式,如果由余力的话,咱们再一起看看Linux的内存管理。这里要多说一句,x86最显著的特定就两个字:兼容,所以有时候学习x86就跟考古似的,如果某个机制你在2021年的文档张无法理解,可能找一份1994年的文档再看看就搞明白了,这也是为什么我要先从8086讲起了。
所有的硬件都是为软件服务的,我们不能脱离于软件去讲硬件的原理,所以在讲8086的内存管理之前,我们先来看看8086时代操作系统的运作方式,这样我们可以知道当时的操作系统是怎么使用硬件的,然后我们再反过来看8086是怎么满足操作系统的这些需求的。单任务操作系统,咱们这个年纪能叫出来名字也只有DOS了。DOS,全名是磁盘操作系统,和现代操作系统的概念其实差的特别远,严格的意义上来说,DOS都算不上个操作系统,它并没有向用户完全屏蔽用户信息,在某些情况下,程序员可能只有在充分了解硬件细节的情况下才能写出高性能的DOS软件。从整体框架上来看,DOS为用户提供了三个基本部件,第一个就是命令行CLI,为用户提供了最基本的交互界面;第二个是程序加载器,当你的程序没有在运行的时候,默认肯定是磁盘里的,有了程序加载器,我们只需要在命令行上正确的输入路径和程序文件名,加载器会把程序加载到内存里,然后让程序指针跳转到被加载程序对应的内存地址处开始执行;第三个就是OS Service是,我们搞操作系统,是为了方便人类使用计算机,更准确的说是利用计算机里的CPU提供的算力,这就需要不断的有原始数据从外设送进CPU,或者运算结果不断的从CPU送到外设,比如我们从键盘输入1+1,然后在屏幕上可以看到1+1=2。在8086那个年代,CPU想要和外设读取数据,只能通过IO/OUT指令操作外设对应的IO端口, 因为所有的外设寄存器都映射到了IO地址空间。这对于新人来说可能不好理解,咱们对着图来说。我们可以这样理解,RAM对于CPU来说就跟自家后院儿似的,想拿什么数据直接搬运就行,所以当CPU看到一笔MOV AX,[0x0],它就知道这是一笔内存地址直接寻址,地址为0,于是在地址总线配合S0S1S2就把这笔memory read cycle放到了地址总线上,RAM芯片看到后就明白这是跟我来要数据了,然后就响应,随后把数据从数据总线送回去。IO操作也差不多,过程类似,不过就是S0S1S2会告诉外设芯片,这笔IN AX,60H是和你要数据,外设芯片能解码,也会把数据送回去。这就是x86为什么会有IO地址空间的原因,只是因为8086没有把RAM和外设统一编制而已。等到了后来,x86 chipset可以把内存和外设统一编址,产生了MMIO机制,我们才可以向访问内存一样访问IO设备的寄存器。这就是x86CPU控制硬件的基本原理,然后我们再说会DOS的OS Server,抛开MMIO不谈,因为那个时代MMIO还没出现,如果想在DOS环境里控制硬件,程序员可以自行编写程序利用IO/OUT指令直接操控外设,但是这样子需要程序员对硬件了如指掌,写起来也很繁琐;对于一些基本的硬件,比如键盘屏幕打印机等,BIOS提前进行了初始化,并且在地址为0的位置建立了中断向量表,BIOS会负责建立部分中断向量函数用户进行外设硬件的控制,DOS程序员可以在程序里使用软中断指令INT n直接调用现成的,其中n是中断向量号,比如0号调用就是从键盘读取一个字符。业界管这样的硬件控制方式叫做BIOS功能调用。
此外还有一种方式叫做DOS功能调用,原理上其实和BIOS调用类似,也是借助INT n软中断,只不过响应的中断处理函数是DOS系统负责初始化并将函数地址注册到中断向量表中的。例如上图里的9号调用,先把数据放到DS里,然后往AH里写9,再调用21H,21H的中断处理函数就会产看AH,看到是多少又会再此去中断向量表那里拿到对应的中断处理函数的地址,再跳转过去执行。DOS系统程序远如果想增加系统服务,只需要编好中断处理函数然后注册到中断向量表即可。这个程序里面用了调用了两次21H,是因为你调用完09,09执行完以后,会跳回到当前这个程序,然后程序再调用一次4C结束运行,把系统控制权交还给了DOS系统,这个4C系统调用的作用就是结束当前程序。
了解完了DOS的三大基础部件后,我们现在可以想象DOS的运行过程,先上电,然后BIOS初始化硬件建立中断向量表建立BIOS调用中断处理函数,引导DOS到内存,然后DOS初始化,建立DOS调用中断处理函数再注册到中断向量表,然后建立CLI等待用户输入,用户输入一个程序,程序加载器把程序文件从磁盘加载到内存并跳转过去,程序开始执行,中间进行若干此系统调用,执行完毕再调用一次4C,然后回到DOS,然后等待用户键入下一次程序或者任务。这是一个完全串行的过程,中间的每步都是环环相扣的,上一条完了才能下一条。那在这种单任务的环境下,其实DOS对于8086的要求也不会很高,程序和数据分开放这是起码的吧,毕竟程序不会变,数据一直变,但是分开放的同事还不能耽误随时用;过程调用和返回也是起码的吧,这中间就包含被中断现成的保护和恢复;然后呢,好像也没啥了,接下了我们看看8086也就是x86实模式是怎么实现这两个需求的
我们先来解决第一个问题,程序和数据分开来放的同时还要合起来用。8086给出的回答就是内存的分段机制。之前我们讲过8086的寄存器组,