物理地址和线性地址介绍

在硬件工程师和普通用户看来,内存就是插在或固化在主板上的内存条,它们有一定的容量——比如64 MB。但在应用程序员眼中,并不过度关心插在主板上的内存容量,而是他们可以使用的内存空间——他们可以开发一个需要占用1 GB内存的程序,并让其在OS平台上运行,哪怕这台运行主机上只有128 MB的物理内存条。而对于OS开发者而言,则是介于二者之间,他们既需要知道物理内存的细节,也需要提供一套机制,为应用程序员提供另一个内存空间,这个内存空间的大小可以和实际的物理内存大小之间没有任何关系。
   
  我们将主板上的物理内存条所提供的内存空间定义为物理内存空间;将应用程序员看到的内存空间定义为线性空间。物理内存空间大小在不同的主机上可以是不一样的,随着主板上所插的物理内存条的容量不同而不同;但为应用程序员提供的线性空间却是固定的,不会随物理内存的变化而变化,这样才能保证应用程序的可移植性。尽管物理内存的大小可以影响应用程序运行的性能,并且很多情况下对物理内存的大小有一个最低要求,但这些因素只是为了让一个OS可以正常的运行。
   
  线性空间的大小在32-bit平台上为4 GB的固定大小,对于每个进程都是这样(一个应用可以是多进程的,在OS眼中,是以进程为单位的)。也就是说线性空间不是进程共享的,而是进程隔离的,每个进程都有相同大小的4 GB线性空间。一个进程对于某一个内存地址的访问,与其它进程对于同一内存地址的访问绝不冲突。比如,一个进程读取线性空间地址1234ABCDh可以读出整数8,而另外一个进程读取线性空间地址1234ABCDh可以读出整数20,这取决于进程自身的逻辑。
   
  在任意一个时刻,在一个CPU上只有一个进程在运行。所以对于此CPU来讲,在这一时刻,整个系统只存在一个线性空间,这个线性空间是面向此进程的。当进程发生切换的时候,线性空间也随着切换。所以结论就是每个进程都有自己的线性空间,只有此进程运行的时候,其线性空间才被运行它的CPU所知。在其它时刻,其线性空间对于CPU来说,是不可知的。所以尽管每个进程都可以有4 GB的线性空间,但在CPU眼中,只有一个线性空间的存在。线性空间的变化,随着进程切换而变化。
   
  尽管线性空间的大小和物理内存的大小之间没有任何关系,但使用线性空间的应用程序最终还是要运行在物理内存中。应用所给出的任何线性地址最终必须被转化为物理地址,才能够真正的访问物理内存。所以,线性内存空间必须被映射到物理内存空间中,这个映射关系需要通过使用硬件体系结构所规定的数据结构来建立。我们不妨先称其为映射表。一个映射表的内容就是某个线性内存空间和物理内存空间之间的映射关系。OS Kernel一旦告诉某个CPU一个映射表的位置,那么这个CPU需要去访问一个线性空间地址时,就根据这张映射表的内容,将这个线性空间地址转化为物理空间地址,并将此物理地址送到地址线,毕竟地址线只知道物理地址。
   
  所以,我们很容易得出一个结论,如果我们给出不同的映射表,那么CPU将某一线性空间地址转化的物理地址也会不同。所以我们为每一个进程都建立一张映射表,将每个进程的线性空间根据自己的需要映射到物理空间上。既然某一时刻在某一CPU上只能有一个应用在运行,那么当任务发生切换的时候,将映射表也更换为响应的映射表就可以实现每个进程都有自己的线性空间而互不影响。所以,在任意时刻,对于一个CPU来说,也只需要有一张映射表,以实现当前进程的线性空间到物理空间的转化。
  
  --------------------------------------------------------------------------------
  
  
  
  2. OS Kernel Space & Process Space
  
   
  由于OS Kernel在任意时刻都必须存在于内存中,而进程却可以切换,所以在任意时刻,内存中都存在两部分,OS Kernel和用户进程。而在任意时刻,对于一个CPU来说只存在一个线性空间,所以这个线性空间必须被分成两部分,一部分供OS Kernel使用,另一部分供用户进程使用。既然OS Kernel在任何时候都占用线性空间中的一部分,那么对于所有进程的线性空间而言,它们为OS Kernel所留出的线性空间可以是完全相同的,也就是说,它们各自的映射表中,也分为两部分,一部分是进程私有映射部分,对于OS Kernel映射部分的内容则完全相同。
   
  从这个意义上来说,我们可以认为,对于所有的进程而言,它们共享OS Kernel所占用的线性空间部分,而每个进程又各自有自己私有的线性空间部分。假如,我们将任意一个4 GB线性空间分割为1 GB的OS Kernel空间部分和3 GB的进程空间部分,那么所有进程的4 GB线性空间中1 GB的OS Kernel空间是共享的,而剩余的3 GB进程空间部分则是各个进程私有的。Linux就是这么做的,而Windows NT则是让OS Kernel和进程各使用2 GB线性空间。
  
  --------------------------------------------------------------------------------
  
  
  
  3. Segment Mapping & Page Mapping
  
   
  所有的线性空间的内容只有被放置到物理内存中才能够被真正的运行和操作。所以,尽管OS Kernel和进程都被放在线性空间中,但它们最终必须被放置到物理内存中。所以OS Kernel和所有的进程都最终共享物理内存。在现阶段,物理内存远没有线性空间那么大——线性空间是4 GB,而物理内存空间往往只有几百兆,甚至更小。另外即使物理内存有4 GB,但由于每个进程都可以有3 GB线性空间(假如进程私有线性空间是3 GB的话),如果把所有进程的线性空间内容都放在物理内存中,明显是不现实的。所以OS Kernel必须将某些进程暂时用不到的数据或代码放在物理内存之外,将有限的内存提供给当前最需要的进程。另外,由于OS Kernel在任何时候都有可能运行,所以OS Kernel最好被永远放在物理内存中。我们仅仅将进程数据进行换入换出。
   
  从线性空间到物理空间的映射需要映射表,映射表的内容是将某段线性空间映射到相同大小的物理内存空间上。从理论上,我们可以使用两种映射方法:变长映射,和定长映射。变长映射指的是根据不同的需要,将一个一个变长段映射到物理内存上,其格式可以如下(线性空间段起始地址,物理空间段起始地址,段长度)。假如一个进程有3个段:10M的数据段,5M的代码段,和8K的堆栈段,那么就可以在映射表中建立3项内容,每一项针对一个段。这看起来没有问题。但假如现在我们的实际的内存只有32M,其中10M被内核占用,留给进程的物理空间只有22M,那么此进程在运行时,就占据了10M+5M+8K的内存空间。随后当进程发生切换时,假如另一个进程和其有相同的内存要求,那么剩余的22M-(10M+5M+8K)明显就不够用了,这时只能将原进程的某些段换出,并且必须是整段的换出。这就意味着我们必须至少换出一个10M的数据段,而换出的成本很高,因为我们必须将这10M的内容拷贝到磁盘上,磁盘I/O是很慢的。
   
  所以,使用变长的段映射的结果就是一个段要么被全部换入,要么被全部换出。但在现实中,一个程序中并非所有的代码和数据都能够被经常访问,往往被经常访问的只占全部代码数据的一部分,甚至是一小部分。所以更有效的策略是我们最好只换出那些并不经常使用的部分,而保留那些经常被使用的部分。而不是整个段的换入换出。这样可以避免大块的慢速磁盘操作。
   
  这就是定长映射策略,我们将内存空间分割为一个个定长块,每个定长块被称为一个页。映射表的基本格式为(物理空间页起始地址),由于页是定长的,所以不需要指出它的长度,另外,我们不需要在映射表中指定线性地址,我们可以将线性地址作为索引,到映射表中检索出相应的物理地址。当使用页时,其策略为:当换出的时候,我们只将那些不活跃的,也就是不经常使用的页换出,而保留那些活跃的页。在换入的时候,只有被请求访问的页才被换入,没有被请求访问的页将永远不会被换入到物理内存。这就是请求页(Demand Page)算法的核心思想。
   

  这就引出一个页大小的问题:首先我们不可能以字节为单位,这样映射表的大小和线性空间大小相同——假如整个线性空间都被映射的话——我们不可能将全部线性空间用作存放这个映射表。由此,我们也可以得知,页越小,则映射表的容量越大。而我们不能让映射表占用太多的空间。但如果页太大,则面临着和不定长段映射同样的问题,每次换出一个页,都需要大量的磁盘操作。另外,由于为一个进程分配内存的最小单位是页,假如我们的页大小为4 MB,那么即使一个进程只需要使用4 KB的内存,也不得不占用整个4 MB页,这明显是一种很大的浪费。所以我们必须在两者之间进行折衷,一般平台所规定的页大小为1 KB到8 KB,IA-32所规定的页大小为4 KB。(IA-32也支持4 MB页,你可以根据你的OS的用途进行选择,一般都是使用4 KB页)。






第一讲 简述 x86 寻址演变我们知道,操作系统是一组软件的集合。但它和一般软件不同,因为它是充分挖掘硬件潜能的软件,也可以说,操作系统是横跨软件和硬件的桥梁。因此,要想深入解析操作系统内在的运作机制,就必须搞清楚相关的硬件机制——尤其是内存寻址的硬件机制。操作系统的设计者必须在硬件相关的代码与硬件无关的代码之间划出清楚的界限,以便于一个操作系统很容易地移植到不同的平台。Linux 的设计就做到了这点,它把与硬件相关的代码全部放在 arch(architecture 一词的缩写,即体系结构相关)的目录下,在这个目录下,可以找到 Linux 目前版本支持的所有平台,例如,支持的平台有arm、alpha,、i386、m68k、mips 等十多种。在这众多的平台中,大家最熟悉的就是 i386,即Intel80386 体系结构。因此,我们所介绍的内存寻址也是以此为背景。1、 鼻祖图灵曾经有一个叫“阿兰.图灵”的天才(据说他 16 岁开始研究相对论,虽然英年早逝,但才气纵横逻辑学、物理学、数学等多个领域,尤其是数学逻辑上的所作所为奠定了现代计算技术的理论基础 。后来以他名字命名的“图灵奖”被看作计算机学界的最高荣誉)。他设想出了一种简单但运算能力几乎无限发达的理想机器,这不是一个具体的机械设备,而是一个思想模型,可以用来计算能想象得到的所有可计算函数。这个有趣的机器由一个控制器,一个读写头和一条假设两端无限长的带子组成。工作带相当于存储器,被划分成大小相同的格子,每格上可写一个字母,读写头可以在工作带上随意移动,而控制器可以要求读写头读取其下方工作带上的字母。这听起来仅仅是纸上谈兵,但它却是当代冯.诺依曼计算机体系的理论鼻祖。它带来的“数据连续存储和选择读取思想”是目前我们使用的几乎所有机器运行背后的灵魂。计算机体系结构中的核心问题之一就是如何有效地进行内存寻址,因为所有运算的前提都是先要从内存中取得数据,所以内存寻址技术从某种程度上代表了计算机技术2、 4 位和 8 位处理器及寻址在微处理器的历史上,第一款微处理器芯片 4004 是由 Intel 推出的,那是一个 4 位的微处理器。在 4004 之后,Intel 推出了一款 8 位处理器 8080,它有 1 个主累加器(寄存器 A)和 6 个次累加器(寄存器 B,C,D,E,H 和 L),几个次累加器可以配对(如组成 BC, DE或 HL)用来访问 16 位的内存地址,也就是说 8080 可访问到 64K 内的地址空间。3、 实模式-16 位处理器及寻址几年后,Intel 开发出了 16 位的处理器 8086,这个处理器标志着 Intel x86 王朝的开始,这也是内存寻址的第一次飞跃。之所以说这是一次飞跃,是因为 8086 处理器引入了一个重要概念—段。8086 处理器的寻址目标是 1M 大的内存空间,于是它的地址总线扩展到了 20 位。但是,一个问题摆在了 Intel 设计人员面前,虽然地址总线宽度是 20 位的,但是 CPU 中“算术逻辑运算单元(ALU)”的宽度,即数据总线却只有 16 位,也就是可直接加以运算的指针长度是 16 位的。如何填补这个空隙呢?可能的解决方案有多种,例如,可以像一些 8 位CPU 中那样,增设一些 20 位的指令专用于地址运算和操作,但是那样又会造成 CPU 内存结构的不均匀。又例如,当时的 PDP-11 小型机也是 16 位的,但是其内存管理单元(MMU)可以将 16 位的地址映射到 24 位的地址空间。受此启发,Intel 设计了一种在当时看来不失为巧妙的方法,即分段的方法。 在微处理器的历史上,第一款微处理器芯片4004 是由 Intel 推出的,那是一个 4 位的微处理器。在 4004 之后,Intel 推出了一款 8 位处理器 8080,它有 1 个主累加器(寄存器 A)和 6 个次累加器(寄存器 B,C,D,E,H 和 L),几个次累加器可以配对(如组成 BC, De 或 HL)用来访问 16 位的内存地址,也就是说 8080可访问到 64K 内的地址空间。另外,那时还没有段的概念,访问内存都要通过绝对地址,因此程序中的地址必须进行硬编码(给出具体地址),而且也难以重定位,这就不难理解为什么当时的软件大都是些可控性弱,结构简陋,数据处理量小的工控程序了。Intel 开发出了 16 位的处理器 8086,这个处理器标志着 Intel x86 王朝的开始。为了支持分段,Intel 在 8086 CPU 中设置了四个段寄存器:CS、DS、SS 和 ES,分别用于可执行代码段、数据段、堆栈段及其他段。每个段寄存器都是 16 位的,对应于地址总线中的高 16 位。每条“访内”指令中的内部地址也都是 16 位的,但是在送上地址总线之前,CPU 内部自动地把它与某个段寄存器中的内容相加。因为段寄存器中的内容对应于 20 位地址总线中的高 16 位(也就是把段寄存器左移 4 位),所以相加时实际上是内存总线中的高12 位与段寄存器中的 16 位相加,而低 4 位保留不变,这样就形成一个 20 位的实际地址,也就实现了从 16 位内存地址到 20 位实际地址的转换,或者叫“映射”。段式内存管理带来了显而易见的优势,程序的地址不再需要硬编码了,调试错误也更容易定位了,更可贵的是支持更大的内存地址。程序员开始获得了自由。4、 保护模式-24 位及 32 位寻址技术的发展不会就此止步。Intel 的 80286 处理器于 1982 年问世了,它的地址总线位数增加到了 24 位,因此可以访问到 16M 的内存空间。更重要的是从此开始引进了一个全新理念—保护模式。这种模式下内存段的访问受到了限制。访问内存时不能直接从段寄存器中获得段的起始地址了,而需要经过额外转换和检查(从此你不能再随意存取数据段,具体保护和实现我们后面讲述)。为了和过去兼容,80286 内存寻址可以有两种方式,一种是先进的保护模式,另一种是老式的 8086 方式,被成为实模式。系统启动时处理器处于实模式,只能访问 1M 空间,经过处理可进入保护模式,访问空间扩大到 16M,但是要想从保护模式返回到实模式,你只有重新启动机器。还有一个致命的缺陷是 80286 虽然扩大了访问空间,但是每个段的大小还是 64k,程序规模仍受到限制。因此这个先天低能儿注定命不会很久。很快它就被天资卓越的兄弟——80386 代替了。80386 是一个 32 位的 CPU,也就是它的 ALU 数据总线是 32 位的,同时它的地址总线与数据总线宽度一致,也是 32 位,因此,其寻址能力达到 4GB。对于内存来说,似乎是足够了。从理论上说,当数据总线与地址总线宽度一致时,其 CPU 结构应该简洁明了。但是,80386 无法做到这一点。作为 x86 产品系列的一员,80386 必须维持那些段寄存器的存在,还必须支持实模式,同时又要能支持保护模式,这给 Intel 的设计人员带来很大的挑战。Intel 选择了在段寄存器的基础上构筑保护模式,并且保留段寄存器 16 位。在保护模式下,它的段范围不再受限于 64K,可以达到 4G(参见段机制一讲)。这一下真正解放了软件工程师,他们不必再费尽心思去压缩程序规模,软件功能也因此迅速提升。从 8086 的 16 位到 80386 的 32 位处理器,这看起来是处理器位数的变化,但实质上是处理器体系结构的变化,从寻址方式上说,就是从“实模式”到“保护模式”的变化。从 80386 以后,Intel 的 CPU 经历了 80486、Pentium、PentiumII、PentiumIII 等型号,虽然它们在速度上提高了好几个数量级,功能上也有不少改进,但基本上属于同一种系统结构的改进与加强,而无本质的变化,所以我们把 80386 以后的处理器统称为 IA32(32Bit Intel Architecture)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值