今天写了一篇自我觉得很通俗易懂的文章,里面介绍了一些操作系统底层玩家也有些要头晕的概念,有一些还是非常有意思的,发出来给大家看看
理解intel引领的32位寻址模式,需要分清三个概念
物理地址
线性地址
虚拟地址
在一些实模式的介绍中,把实模式访问的地址称为线性地址,之后在一些对于虚拟内存的介绍中,发现也会把进程的4GB内存空间称为线性地址空间,这是什么?为什么每一个内存地址的名字都想和线性地址扯上关系?
我们遵守intel的规则,就用线性地址来称呼CPU运行时代码所接触到的内存地址们。
在实模式中,线性地址是不需要经过 保护模型中有的 访问段(segment)、映射回页面(paging)两个步骤的,当CPU运行到一个访问内存地址的指令时,这个指令它直接访问的就是自己记录的内存地址所等于的物理地址。
在保护模式中,指令中记载的线性地址在访问时要经过,先通过段寄存器中的段选择子从GDT或者LDT中得到,现在执行或处理的段的基地址,加上去之后,再根据线性地址的值,分别取前10bit、中间10bit作为PDT和PTE表中的 索引,得到各自的偏移,最后加上线性地址的结尾12bits,得到处理后才出现的物理地址,也就是此次内存访问要接触的物理内存的地址。
因为保护模式下的进程所看到的那个4GB内存空间,也是被叫做 虚拟空间的,意思是当代码执行时候,它处理的内存地址们是在一个独立的空间中存在的,是一个从物理内存上层虚拟出来的空间,只是访问最终要转向物理内 存的,所以访问这个虚拟空间中的内存地址,也被叫做 虚拟地址了。为了避免头晕,我们索性认为这是只接触虚拟空间的程序员的夜郎自大,我们放弃掉虚拟地址这种称呼。
但是,实际情况中实模式中线性地址可不是单纯的和物理地址一一映射的,试想,16bit的一个线性地址,其可以访问到的内存也不过64KBs,而比尔盖茨在DOS时代放的话都已经是说640KBs内存够全世界各种需求用,而不是 64KB够用,尤其到了95年爆大的2MB内存都已经出现了,所以很明显,如果只能一一映射的话,整个DOS系统是只能用到64KB硬件内存的,这是不可接受的,于是出现了万恶的段选择子的缘由,段寄存器被设计出来,以让代码 在执行时候,访问一个线性地址,需要先让线性地址加上段寄存器中的偏移值,得到的再是实际物理地址,这样相当于最大可以访问两倍宽度的内存地址了,实模式的线性地址直接访问物理地址也变得名不副实,其实要说是 段寄存器+线性地址直接访问物理地址。
接着生活的巨轮就滚动开了,你不能假设横扫千军要在64KB的内存中运行,那是不可行的,事实上横扫千军需要2000MB的内存,而且运行横扫千军的同时你还需要运行一大堆QQ之类的比横扫千军更占内存的软件,这是在1998 年,所以说在这个气氛产生的32bits保护模式是处理了这个需求的,内存地址的范围只有4GB,横扫千军2GB,QQ 3GB,所以操作系统就不能运行了。唔,操作系统是不能不运行的,所以就出现一种可以更有效利用内存地址的 设计,保护模式加入了一种叫做 页面映射(page)的访问内存的步骤,可以理解作,它会假设要访问的一个线性地址先是不存在的,再去试着找有没有受理这种线性地址的物理地址,如果说有物理地址和要访问的线性地址有映 射关系的话,这样就把对线性地址的访问转换到对此物理地址的访问上
物理地址定义为,处理器可以在它的地址总线上面产生出来的地址,地址总线是一个有指定宽度的线,想像下你接主板时的那种宽的一排小细线们,别操心去数了,只用当他们就是32根的,所以说,物理地址就是用这么宽的 线,CPU可以给物理内存反应过去要访问的地址,就是物理地址。
当你是一个程序员的时候,我想你对于物理内存的概念中,会包括一点是底层对于物理内存所做的管理,以及它给你提供的简便的虚拟内存的概念和完全透明的操作方式。但现实是很不幸的,在物理内存上来说,是不能单纯 笼统认为操作系统就会管理好物理内存,和处理完其到使用的虚拟内存的映射,事实上,在操作系统之上,物理内存的地址还是由BIOS分配和管理的,一段内存的地址如何分配,也是由BIOS决定的。这意味着,可能一片物理 内存地址所访问到的内存是指向你的内存条上的空间,或者,你的显卡也带有自己的内存,因而这一片物理内存地址也可能被分配指向到显卡上带来的那块内存使用。
对我们而言值得庆幸的是,除非你真的是要和BIOS打交道的被关在640KB空间里的正装风衣先生,不然我们的确不需要和BIOS分配内存片的方式打交道。不用细究BIOS和操作系统之类的概念,可以简单的认为实际的内存片如何 和物理地址映射,是由BIOS管理的,而BIOS提供的物理内存空间,如何被组织和处理(通常是如何映射到虚拟内存上),则是由我们熟悉的操作系统完成的。
那现在,我们也可以明确的知道实模式和保护模式是什么样的定义了,实模式它是一种不需要操作系统管理的内存管理方式,直接把物理内存地址用给应用程序对物理内存的访问上面。保护模式,则是包含了可被设置指定访 问时候的属相的段选择子,以及可以指定物理内存和应用程序访问的内存之间的对应关系的,一种操作系统的内存管理方式。
上面的就是需要知道的和内存相关的全部东西了,不相关的东西,包括了那些 flat mode, 还有什么ARM CPU的特别的内存管理单元实现的内存管理方式,等等等。对于想对它们感兴趣一下的朋友,我要说一个段子,对,你用 iphone在厕所里面打手枪音质、画面都很棒,你换了ipad后画面更棒了,但是去你*的,我们在卧室的鼠标和液晶屏的已经非常开心了,谁关心你拿着什么去厕所里,哪怕是笔记本呢,也是和我不相关的。
我没想假设你知道上面那个PTE和PDE是什么意思,所以在这儿再重复下,PDE是组织成一个表的形式的,一个虚拟地址,会先访问进PDE的表,在其中找到此虚拟地址所对应的PDE表中的一项,然后从这一项,定位到下层的由PTE组成的表中。再根据虚拟地址得到PTE表中其所对应的项,从得到的PTE项中,可以取出一个物理地址的值,最后让这个值加上虚拟地址的最后一部分,就得到了此虚拟地址对应的物理地址。
相当长,可是不乱,一层层的查找下来的,现在看一段我摘抄的自己的笔记
IA32e是当前的 intel CPU使用的主要架构,它既向下兼容 32位的设计,也还是 intel 实现64位的方式,简而言之,现在的intel 64并没有象 16bits->32bits时虚拟内存空间那样大刀阔斧的新设计,而是一种扩展的 32 bits方式 。按intel的意思,了解IA32e的设计,在当前是了解现有内存管理方式的 最简单有效、最值得涉猎和熟悉的架构
再摘录一段,在根据虚拟地址访问到物理地址时,ia32e的第一步是,
1.根据 线性地址47-39 bits,来 得到了 PML4 表中对应的项中的一个新物理地址,其指向的是 page-directory-pointer table(上面有提到的PDE表),每一个 page-directory-pointer 包含的是一个索引512 GB的物理内存范围,(注意,此512GB的内存范围是不必须相连的,通俗说,它是指512GB的虚拟内存空间).访问到在此范围内的内存的线性地址, 它们的线性地址的47-39 bits(2^ 39== 512GB)会是相同的,因此 paging时都会选定此page-directory-pointer,
抛去看不到的,留下的东西只说了一句话,IA32e比32bit下多了一个叫做 PML4的表,多了一层表,一个虚拟地址可以访问到的空间就乘以了数倍。
但是等等,虚拟地址的位数是增加了的,那本来就会增大可以访问的空间啊!似乎每个人都被ia32e给哄了。
多层映射表是非常有意义的,如果要单纯的实现一个虚拟地址和物理地址之间的对应关系的表,只有单层的话,按照4MB内存需要4KB的映射表的关系,如果使用到的虚拟内存打到了512GB,即便这片虚拟内存重你只是分散的使用了几点位置,在只有一层的映射表里面也一样要占用0.5GB作为这个映射表,而如果用ia32e的方式处理,使用了多少虚拟内存,所需要的表则只在完成对应地址的索引路线上有值,大大节约了映射表对内存的占用。
再提一句,事实上现在支持的内存理论上只到2^52上,不信数数你的i7 CPU的总线有多少根 : ) 而且常用的windows操作系统允许使用的最大内存,其实也只到512 GB内存的。
也许有朋友要问,intel是如此的坑人,那AMD如何呢?我们可以翻开AMD的CPU手册,操作系统编程里的paging一章,里面关于paging过程的描述的开始是,
1.从PML4(E)中,得到PDE表的地址
下面的内容可能你就不感兴趣了,所以这儿也算文章的结束。
为什么前面要关心下到物理内存和BIOS的关系,原因就是写这篇文章的原始理由,我遇到了一个很古怪的确定虚拟内存和物理内存地址之间映射关系的方法,它在win32下,只是简单的把 VirtualAddr & 0x1FFFF000,就得到了 虚拟地址所对应的物理地址,只除非是有意为之,不然物理内存和虚拟内存之间怎么可能会有这种确定性的大片的对应关系?
在大量的google再加上自我怀疑之后,我走到了现在的一步,我的猜测是,
windows系统在load时确实是把ntoskrnl.exe和hal.dll载入到低位置的地址的,也便是在低位的物理地址,如果说在进入保护模式时所做的处理中,是直接把这段物理内存映射到0x80000000加上它自身的虚拟内存位置,那确 实可以解释的通。尤其是,这种转换方式只确定在访问ntoskrnl.exe和hal.dll模块中的地址时可行。
这个解释相当的牵强,在确定的找或者查到了相关的信息后,我会再更新的。