作为Linux操作系统,如果硬件环境千差万别,就会很难集中精力做出让用户易用的产品。x86架构是一个开放的平台。
计算机的工作模式是什么样的?
还记得咱们攒电脑时买的那堆硬件么?虽然你可以根据经验,把那些复杂的设备和线安装起来,但是你真的了解它们为什么要这么连接么?
现在我就把硬件图和计算机的逻辑图对应起来,带你看看计算机的工作模式。
对于一个计算机来讲,最核心的就是CPU。这是这台计算机的大脑,所有的设备都围绕它展开。
CPU和其他设备连接,要靠一种叫做总线的东西,其实就是主板上密密麻麻的集成电路,这些东西组成了CPU和其他设备的高速通道。
在这些设备中,最重要的是内存。因为单靠CPU是没办法完成计算任务的,很多复杂的计算任务都需要将中间结果保存下来,然后基于中间结果进行进一步的计算。CPU本身没办法保存这么多中间结果,这就要依赖内存了。
当然总线上还有一些其他设备,例如显卡会连接显示器、磁盘控制器会连接硬盘、USB控制器会连接键盘和鼠标等等。
CPU和内存是完成计算任务的核心组件,所以这里我们重点介绍一下CPU和内存是如何配合工作的。CPU包括三个部分:运算单元、数据单元和控制单元
运算单元只管算,例如做加法、做位移等待。但是,它不知道应该算哪些数据,运算结果应该放在哪里。
数据单元包括CPU内部的缓存和寄存器组、空间很小,但是速度飞快,可以暂时存放数据和运算结果。
控制单元控制单元是一个统一的指挥中心,它可以获得下一条指令,然后执行这条指令。这个指令会知道运算单元去除数据单元中的某几个数据,计算出个结果,然后放在数据单元的某个地方。
每个进程都有一个程序放在硬盘上,是二进制的,再里面就是一行行的指令,会操作一些数据。程序一旦运行,比如图中的两个进程A和B,会有独立的内存空间,互相隔离,程序会分别加载到进程A和进程B的内存空间,形成各自的代码段。当然真实的情况肯定比我说的要复杂的多,进程的内存虽然隔离但不连续,除了简单但区分代码段和数据段,还会分的更细。
程序运行但过程中要操作的数据和产生的计算结果,都会放在数据段里面。那CPU怎么执行这些程序,操作这些数据,产生一些结果,并写入回内存呢?
CPU的控制单元里面,有一个指令指针寄存器,它里面存放的是下一条指令在内存中的地址。控制单元会不停地将代码段的指令拿进来,先放入指令寄存器。当前的指令分成两部分,一部分是做什么操作;一部分是操作哪些数据。要执行这条指令,就要把第一部分交给运算单元,第二部分交给数据单元。
数据单元根据数据的地址,从数据段里读取到数据寄存器里,就可以参与运算了。运算单元做完运算,产生的结果会暂存在数据单元的数据寄存器里。最终,会有指令将数据写回内存中的数据段。
到这里,你会发现,CPU和内存来来回回传数据,靠的都是总线。其实总线上主要有两类数据,一个是地址数据,也就是我想那内存中那个位置的数据,这类总线叫地址总线;另一类是真正的数据,这类总线叫数据总线。所以说,总线其实有点像连接CPU和内存这两个设备的高速通道,说总线到底是多少位,就类似说高速公路有几个车道。但是这两种总线的位数意义是不同的。
地址总线的位数,决定了能访问的地址范围到底有多广。例如只有两位,那CPU就只能认00,01,10,11四个位置,超过四个位置,就区分不出来了。位数越多,能够访问的位置就越多,能管理的内存范围也就越广。
而数据总线的位数,决定了一次能拿多少个数据进来。例如只有两位,那CPU一次只能存内存拿两位数。要想拿八位,就要拿四次。位数越多,一次拿的数据就越多,访问速度也就越快。
x86 成为开放平台历史中的重要一笔
那CPU中总线的位数有没有标准呢?如果没有标准,那操作系统作为软件就很难办了,因为软件层没办法实现通用的运算逻辑。这就像很多非标准的元器件一样,你烧你的电路板,我烧我的电路板,谁都不能用彼此的。
好在历史将x86平台推到了开放、统一、兼容的位置。
从8086的原理说起
说完了x86的历史,我们再来看x86中最经典的一款处理器,8086处理器。虽然它已经很老了,但是咱们现在操作系统中的很多特性都和它有关,并且一致保持兼容。
我们把CPU里面的组件放大之后来看:
我们先来看看数据单元,为了暂存数据,8086处理器内部有8个16位的通用寄存器,也就是刚才说的CPU内部的数据单元,分别是AX、BX、CX、DX、SP、BP、SI、DI。这些寄存器主要用于在计算过程中存储数据。这些寄存器比较灵活,其中AX、BX、CX、DX可以当作8位的寄存器来使用H表示高位,L表示地位,这样较长的数据和较短的数据都能很好的保存起来。
接着我们来看控制单元。IP寄存器就是指令指针寄存器,指向代码段中下一条指令的位置。CPU会根据它来不断地将指令从内存的代码段中,加载到CPU的指令队列中,然后交给运算单元去执行。
如果需要切换进程呢?每个进程都分代码段和数据段,为了指向不同进程的地址空间,有四个16位的段寄存器,分别是CS、DS、SS、ES。
其中CS就是代码端寄存器,通过它可以找到代码在内存中的位置;DS是数据段的寄存器,通过它可以找到数据在内存中的位置。
SS是栈寄存器。栈是程序运行中一个特殊的数据结构,数据的存取只能从一端进行,秉承后进先出的原则,push就是入栈,pop就是出栈。
如果运算中需要加载内存中的数据,需要通过DS找到内存中 的数据,加载到通用寄存器中,应该如何加载呢?对于一个段,有一个起始的地址,而段内的具体位置,我们称为偏移量。
在CS和DS中都存放着一个段的起始地址。代码段的偏移量在IP寄存器中,数据段的偏移量会放在通用寄存器中。这时候问题来了,CS和DS都是16位的,也就是说,起始地址都是16位的,IP寄存器和通用寄存器都是16位的,偏移量也是16位的,但是8086的地址总线地址是20位。怎么凑够这20位呢?方法就是“起始地址*16 + 偏移量”,也就是把cs和DS中的值左移四位,变成20位的,加上16位的偏移量,这样就可以得到最终20位的数据地址。
从这个计算方式可以算出,无论真正的内存多么大,对于只有 20 位地址总线的 8086 来讲,能够区分出的地址也就 2^20=1M,超过这个空间就访问不到了。这又是为啥呢?如果你想访问 1M+X 的地方,这个位置已经超过 20 位了,由于地址总线只有 20 位,在总线上超过 20 位的部分根本是发不出去的,所以发出去的还是 X,最后还是会访问 1M 内的 X 的位置。
那一个段最大能有多大呢?因为偏移量只能是 16 位的,所以一个段最大的大小是 2^16=64k。
再来说32位处理器
当然,后来计算机的发展日新月异,内存越来越大,总线也越来越宽。在32位处理器中,有32根地址总线,可以访问2^ 32=4G的内存。使用原来的模式肯定是不行了,但是又不能完全抛弃原来的模式,因为这个架构是开放的。
我们下面来说说,在开放架构的基础上,如何保持兼容呢?
首先,通用寄存器有扩展,可以将8个16位的扩展到8个32位的,但是依然可以保留16位的和8位的使用方式。
其中,指向下一条指令的指针寄存器IP,就会扩展成32位的,同样也兼容16位的。
作为Linux操作系统,如果硬件环境千差万别,就会很难集中精力做出让用户易用的产品。x86架构是一个开放的平台。
计算机的工作模式是什么样的?
还记得咱们攒电脑时买的那堆硬件么?虽然你可以根据经验,把那些复杂的设备和线安装起来,但是你真的了解它们为什么要这么连接么?
现在我就把硬件图和计算机的逻辑图对应起来,带你看看计算机的工作模式。
对于一个计算机来讲,最核心的就是CPU。这是这台计算机的大脑,所有的设备都围绕它展开。
CPU和其他设备连接,要靠一种叫做总线的东西,其实就是主板上密密麻麻的集成电路,这些东西组成了CPU和其他设备的高速通道。
在这些设备中,最重要的是内存。因为单靠CPU是没办法完成计算任务的,很多复杂的计算任务都需要将中间结果保存下来,然后基于中间结果进行进一步的计算。CPU本身没办法保存这么多中间结果,这就要依赖内存了。
当然总线上还有一些其他设备,例如显卡会连接显示器、磁盘控制器会连接硬盘、USB控制器会连接键盘和鼠标等等。
CPU和内存是完成计算任务的核心组件,所以这里我们重点介绍一下CPU和内存是如何配合工作的。CPU包括三个部分:运算单元、数据单元和控制单元
运算单元只管算,例如做加法、做位移等待。但是,它不知道应该算哪些数据,运算结果应该放在哪里。
数据单元包括CPU内部的缓存和寄存器组、空间很小,但是速度飞快,可以暂时存放数据和运算结果。
控制单元控制单元是一个统一的指挥中心,它可以获得下一条指令,然后执行这条指令。这个指令会知道运算单元去除数据单元中的某几个数据,计算出个结果,然后放在数据单元的某个地方。
每个进程都有一个程序放在硬盘上,是二进制的,再里面就是一行行的指令,会操作一些数据。程序一旦运行,比如图中的两个进程A和B,会有独立的内存空间,互相隔离,程序会分别加载到进程A和进程B的内存空间,形成各自的代码段。当然真实的情况肯定比我说的要复杂的多,进程的内存虽然隔离但不连续,除了简单但区分代码段和数据段,还会分的更细。
程序运行但过程中要操作的数据和产生的计算结果,都会放在数据段里面。那CPU怎么执行这些程序,操作这些数据,产生一些结果,并写入回内存呢?
CPU的控制单元里面,有一个指令指针寄存器,它里面存放的是下一条指令在内存中的地址。控制单元会不停地将代码段的指令拿进来,先放入指令寄存器。当前的指令分成两部分,一部分是做什么操作;一部分是操作哪些数据。要执行这条指令,就要把第一部分交给运算单元,第二部分交给数据单元。
数据单元根据数据的地址,从数据段里读取到数据寄存器里,就可以参与运算了。运算单元做完运算,产生的结果会暂存在数据单元的数据寄存器里。最终,会有指令将数据写回内存中的数据段。
到这里,你会发现,CPU和内存来来回回传数据,靠的都是总线。其实总线上主要有两类数据,一个是地址数据,也就是我想那内存中那个位置的数据,这类总线叫地址总线;另一类是真正的数据,这类总线叫数据总线。所以说,总线其实有点像连接CPU和内存这两个设备的高速通道,说总线到底是多少位,就类似说高速公路有几个车道。但是这两种总线的位数意义是不同的。
地址总线的位数,决定了能访问的地址范围到底有多广。例如只有两位,那CPU就只能认00,01,10,11四个位置,超过四个位置,就区分不出来了。位数越多,能够访问的位置就越多,能管理的内存范围也就越广。
而数据总线的位数,决定了一次能拿多少个数据进来。例如只有两位,那CPU一次只能存内存拿两位数。要想拿八位,就要拿四次。位数越多,一次拿的数据就越多,访问速度也就越快。
x86 成为开放平台历史中的重要一笔
那CPU中总线的位数有没有标准呢?如果没有标准,那操作系统作为软件就很难办了,因为软件层没办法实现通用的运算逻辑。这就像很多非标准的元器件一样,你烧你的电路板,我烧我的电路板,谁都不能用彼此的。
好在历史将x86平台推到了开放、统一、兼容的位置。
从8086的原理说起
说完了x86的历史,我们再来看x86中最经典的一款处理器,8086处理器。虽然它已经很老了,但是咱们现在操作系统中的很多特性都和它有关,并且一致保持兼容。
我们把CPU里面的组件放大之后来看:
我们先来看看数据单元,为了暂存数据,8086处理器内部有8个16位的通用寄存器,也就是刚才说的CPU内部的数据单元,分别是AX、BX、CX、DX、SP、BP、SI、DI。这些寄存器主要用于在计算过程中存储数据。这些寄存器比较灵活,其中AX、BX、CX、DX可以当作8位的寄存器来使用H表示高位,L表示地位,这样较长的数据和较短的数据都能很好的保存起来。
接着我们来看控制单元。IP寄存器就是指令指针寄存器,指向代码段中下一条指令的位置。CPU会根据它来不断地将指令从内存的代码段中,加载到CPU的指令队列中,然后交给运算单元去执行。
如果需要切换进程呢?每个进程都分代码段和数据段,为了指向不同进程的地址空间,有四个16位的段寄存器,分别是CS、DS、SS、ES。
其中CS就是代码端寄存器,通过它可以找到代码在内存中的位置;DS是数据段的寄存器,通过它可以找到数据在内存中的位置。
SS是栈寄存器。栈是程序运行中一个特殊的数据结构,数据的存取只能从一端进行,秉承后进先出的原则,push就是入栈,pop就是出栈。
如果运算中需要加载内存中的数据,需要通过DS找到内存中 的数据,加载到通用寄存器中,应该如何加载呢?对于一个段,有一个起始的地址,而段内的具体位置,我们称为偏移量。
在CS和DS中都存放着一个段的起始地址。代码段的偏移量在IP寄存器中,数据段的偏移量会放在通用寄存器中。这时候问题来了,CS和DS都是16位的,也就是说,起始地址都是16位的,IP寄存器和通用寄存器都是16位的,偏移量也是16位的,但是8086的地址总线地址是20位。怎么凑够这20位呢?方法就是“起始地址*16 + 偏移量”,也就是把cs和DS中的值左移四位,变成20位的,加上16位的偏移量,这样就可以得到最终20位的数据地址。
从这个计算方式可以算出,无论真正的内存多么大,对于只有 20 位地址总线的 8086 来讲,能够区分出的地址也就 2^20=1M,超过这个空间就访问不到了。这又是为啥呢?如果你想访问 1M+X 的地方,这个位置已经超过 20 位了,由于地址总线只有 20 位,在总线上超过 20 位的部分根本是发不出去的,所以发出去的还是 X,最后还是会访问 1M 内的 X 的位置。
那一个段最大能有多大呢?因为偏移量只能是 16 位的,所以一个段最大的大小是 2^16=64k。
再来说32位处理器
当然,后来计算机的发展日新月异,内存越来越大,总线也越来越宽。在32位处理器中,有32根地址总线,可以访问2^ 32=4G的内存。使用原来的模式肯定是不行了,但是又不能完全抛弃原来的模式,因为这个架构是开放的。
我们下面来说说,在开放架构的基础上,如何保持兼容呢?
首先,通用寄存器有扩展,可以将8个16位的扩展到8个32位的,但是依然可以保留16位的和8位的使用方式。
其中,指向下一条指令的指针寄存器IP,就会扩展成32位的,同样也兼容16位的。
而改动比较大,有点不兼容的就是段寄存器。
因为原来的模式其实有点不伦不类,因为它没有把16位当成一个段的起始地址,也没有按照8位或者16位的扩展形式,而是根据当时的硬件,弄了一个不上不下的20位的地址。这样每次都要左移四位,也就意味着段的起始地址不能是任何一个地方,只是能整除16的地方。
如果新的段寄存器都改成32位的,明明4G的内存全部都能访问到,还左移不左移四位呢?
那我们索性就重新定义一把吧。CS、SS、DS、ES仍然是16位的,但不再是段段其实地址。段的起始地址放在内存的某个地方。这个地方是一个表格,表格中的一项一项是段描述符这里面才是真正的段的起始地址。而段寄存器里面保存的是这个在表格中的哪一项,称为选择子。
这样,将一个从段寄存器直接拿到的段的起始地址,就变成了先间接地从段寄存器找到表格中的一项,再从表格中的一项拿到段的起始地址。
这样段起始地址就会很灵活了。当然为了快速拿到段起始地址,段寄存器会从内存中拿到CPU的描述符高速缓存器中。
这就不兼容了,好在后面这种模式灵活度非常高,可以保持将来一直兼容下去。前面的模式出新的时候,没想到自己能够成为一个标准,所以设计的就没有这么灵活。
因而到了32位的系统架构下,我们将前一种模式称为实模式,后一种模式称为保护模式。
当系统刚刚启动的时候,CPU是处于实模式的,这个时候和原来的模式是兼容的。也就是说,哪怕你买了32位的CPU,也支持在原来的模式下运行,只不过快了一点。
当需要更多内存的时候,你可以遵循一定的规则,进行一系列的操作,然后切换到保护模式,就能够用到32位CPU更强大的能力。这也就是说,不能无缝兼容,但是通过切换模式兼容,也是可以接受的。