32位Linux系统虚拟地址映射

不羁岁月 提交于 2020-02-02 12:37:02

IA32体系即Intel32位体系架构,也被称为i386、X86-32或X86。在Intel公司1985年推出的80386微处理器中首先使用。用以取代之前的X86-16位架构,包括8086、80186、80286芯片。谈到这儿,就不得不说说X86架构的发展历史。
Intel 8086是由Intel于1978年所设计的16位微处理器芯片,是x86架构的鼻祖。8086是16位CPU,数据总线16条,地址总线20条,能保存的地址的大小是2^20=1M。
8086增加了4个段寄存器用来保存各内存段的起始地址,这4个段寄存器分别为CS(代码段寄存器)、DS(数据段寄存器)、SS(堆栈段寄存器)、ES(扩展段寄存器)。
由于地址总线共20条,段地址有20位,但是段寄存器只有16位,不能保存20位的地址。

因此,将内存的大小划分为16的倍数(此时还没有操作系统,直接操作的是物理内存)。每块内存起始地址的后四位都为0,段寄存器只保存地址的高16位。正如前面所讲,8086时,地址总线最多只能保存1M的地址空间。

此时,物理内存=段基址+逻辑地址/偏移量。
此时若要取数据,就需要找到物理内存,方法为从DS寄存器中取值,左移4位,就得到了真正的起始地址(DS<<4),再加上变量在该内存段上的偏移量(IP寄存器保存了当前数据在内存段上的偏移量),就得到了数据的物理内存。
DS<<4 + IP = 数据的地址。
DS<<4 称为段基址,称IP寄存器保存的数为逻辑地址、偏移量或者偏移地址。8086时,操作系统不存在,无任何权限控制信息,通过驱动接口可以任意更改段寄存器的值,无任何保护措施。称这种模式为实模式。实模式下,物理地址=DS<<4 + IP。

在实模式下,最大访问的物理内存是1M。CPU通电后,强制进入实模式,CPU只能访问1M的物理内存。通过Linux内核源码也可以看到,Linux内核image都是从0X10000开始加载的,虚拟地址是从0Xc0100000开始加载,因为前1M保存的是驱动代码、显卡的缓存等等。

实模式下访问内存是极其不安全的,对于CPU的寄存器没有任何有效性的保护。其实,在访问一块内存时,不仅仅需要知道内存的起始地址,还需要知道内存段的大小、内存的访问权限(可读、可写或者可执行)。这么多信息不可能全都放在段寄存器中,即使80386已经是32位CPU了,CS、DS、SS段寄存器还是16位的(同一体系,必须兼容)。为了存储内存段的其他信息,从80386开始,增加了GDTR和LDTR两个寄存器,GDTR中保存的是GDT的地址,但是LDTR中保存的是LDT的地址在GDT的索引。GDT是全局的段描述符表,LDT是局部的段描述符表,这两个段描述符表也是80386新增的。可以把他们理解为一个结构体数组,每个元素(段描述符表项)占8字节。后面会专门介绍这个段描述符表项。有了段描述符表保存内存段信息后,段寄存器就空出来了,转而保存段描述符表项的索引。其结构如下:
在这里插入图片描述
寻址时,从寄存器中取出低地址第3位的数字,判断内存段的信息保存在GDT还是LDT中,然后以寄存器的高13位数字为索引,在GDT表中找到段描述符表项。段描述符表项结构如下:

在这里插入图片描述

该结构是一个段描述符表项,占8字节,共64位。其中,有32位保存内存段的起始地址,20位保存内存段的长度,可以保存的内存段的长度为2^20=1M。如果G位为0,长度的单位是byte,内存段的总长度是1M;如果G位为1,长度的单位是页面(4K),内存段的总长度是1M*4K=4G。80386开始有了操作系统,将虚拟地址映射为物理地址是由MMU(Memory Management Unit)完成的,这种模式称为保护模式。
保护模式下,内存分段的地址映射寻址过程是:从寄存器中取高13位,以此为索引,在GDT中找到相应的段描述符表项,从段描述符中得到内存段的地址,再将内存段的长度和偏移量做比较,如果偏移量>内存段的长度,则说明数据可能被修改,结束该进程的运行;如果偏移量合法,段基址加上偏移量就是线性地址。因此,保护模式下,内存分段的地址映射的寻址方式为:GDT[DS>>3].BaseAddr + IP = 线性地址。
得到线性地址后,需要检查内是否开启内存分页机制。若没有开启内存分页机制,线性地址就是物理地址。若开启了内存分页机制,此时的线性地址是虚拟地址,要通过多级页表映射才能得到物理地址。
怎样判断内核是否开启内存分页机制呢?不得不提到4个寄存器了,它们分别是CR0、CR2、CR3、CR4寄存器。CR0的最高位PG位若为0,未开启分页机制;若为1,开启分页机制。CR2保存发生缺页异常时的虚拟地址。CR3保存当前进程的页目录的起始地址。CR4的PAE位若为0,未开启物理地址扩展;若为1,开启了物理地址扩展。通过CR0寄存器的PG位的值就可以判断内核是否开启分页机制。
32位系统需要二级页面映射,36位系统需要三级页面映射,64位系统需要四级页面映射。下面就以32位系统的二级页面映射为例,具体说一下内存分页机制下,如何进行物理内存寻址。
上面说到过,开启内存分页机制后,通过内存分段地址映射的方式得到的线性地址是虚拟地址,还需要对虚拟地址进行内存分页地址映射才能得到物理地址。虚拟地址共32位,其中高10位表示的是页目录的下标,次高10位表示页表的下标,低12位表示物理页面上的偏移量。具体划分如下图示:

在这里插入图片描述

高10位可以表示210=1024个索引,次高10位也可以表示210=1024个索引,低12位可以表示2^12=4K的偏移(物理页面,1页面=4K,设计并非偶然)。因此,页目录(Page Directory)和页表(Page Table)可以看作含有1024个元素的数组。每个元素占4字节,所以页目录、页表的大小为4K,物理页面的大小也是4K,页表和物理页面的起始地址都是4K的倍数,后16位都为0,所以页目录只保存页表地址的高20位,页表只要保存物理页面地址的高20位。剩下的12位保存的内容稍后会谈到。
这会儿,内存分页的地址映射过程即将浮出水面了。从CR3寄存器中拿到当前进程页目录的地址,根据高10位保存的页目录下标定位到页目录中的Entry项,Entry项的高20位保存的是页表地址的高20位,将后16位补上0后,就是页表的完整地址。再根据次高10位,找到页表的Entry项,Entry项的高20位保存的是物理页面的高20位,将后16位补上0后,就是物理页面的完整地址。最后,物理页面的地址+低12位的偏移量,就得到了物理地址。
还有一个小尾巴没有解决。Entry的高20位保存了地址的高20位,那低16位保存的是什么呢?存权限!有没有分配、能不能访问等等信息。其中最重要也是最有用的一位是Page Table的Entry项的最低位,present位。若present位=0,表示页表项所对应的物理页面在交换分区中;若present位=1,表示页表项所对应的物理页面就在物理内存中。根据LRU最近最久未使用算法,将物理内存中最近最久未使用的页面交换到交换分区中。程序刚开始运行时,会访问数据、指令等等,发出的第一个逻辑地址就要发生地址映射,但刚开始执行的时候没有分配任何物理内存,发生缺页异常,操作系统分配物理页面后,重新进行映射。由于程序运行有“局部性”原理,页面一经分配,其他大多数时候访问的都是这个页面。所以,一般只有第一次映射时会发生缺页异常,其他情况下发生缺页异常的概率非常非常小。
到这儿,内存的段页式管理就解释完毕了。下面再附一张图,帮助大家更好的理解这个过程。
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!