目录分段存储的缺点管理物理内存映射表一个线性地址寻址过程终于开始介绍分页机制了。作为Linuxer,大名鼎鼎的分页机制一定要吃透!我会尽我所能,努力正确分解我理解的分页机制,希望对你有所帮助!一共三篇文章:本文主要介绍单映射表;下一篇介绍二级映射(页目录和页表);上一篇介绍了映射表本身的操作。分段存储的缺点在前面的文章中,我们已经多次描述了段描述符的结构,包括段的起始地址、边界和各种段属性。经过分段处理单元的权限检查和计算,起始地址加上偏移量就是一个线性地址,如下图所示:在x86系统中,分段机制是固有的,必须通过这个得到一个线性地址关联。地址。因此,在Linux系统中,为了“不用”分段机制,但又无法绕过它,就不得不定义一个“扁平化”的分段模型。当不启用分页机制时,分段单元输出的线性地址等于物理地址。这里有一个重要的问题:从段的起始地址到段空间的最后地址,这是一个连续的空间!这样的话,在每个用户程序中,物理内存中包含的所有段上面对应的空间也必须是连续的,如下图所示:因为每个程序的代码和数据长度是不确定的,不同的,根据这个映射方法,将物理内存分成各种离散的、大小不同的块。运行一段时间后,部分程序会退出,它们占用的物理内存空间可以收回。然而,这些物理记忆以许多碎片的形式存在。如果这时操作系统要分配一个稍微大一点的连续空间,虽然空闲物理内存空间总量是足够的,但不是连续的,会造成物理内存的极大浪费!我应该怎么办?目前的需求是:操作系统提供给用户的段空间必须是连续的,但最好不要有连续的物理内存。软件领域有一句经典的话:没有什么是加个抽象层解决不了的!在内存管理上,这个新增加的层就是虚拟内存:将物理内存划分为一个固定的单元(4KB,称为物理内存页),然后将连续的虚拟内存映射到若干个不连续的物理内存页。图中绿色的映射表用于将虚拟内存映射到物理内存。物理内存的管理映射表的细节将在下一个主题中讨论。我们先来看看操作系统对物理内存的状态管理。现在的PC上,内存往往配置8G/16G/32G,看似充裕又好用。但是在N年前,买U盘都是看MB的,更别说内存了。所以,在那个年代,面对MB级的物理内存,操作系统可以将其虚拟成4GB的内存空间供用户程序使用,也是非常厉害的!言归正传,在这篇文章中,我们会比较奢侈,假设可用的物理内存有1GB的空间。当系统开机时,BIOS会检查系统的各种硬件资源,并告诉操作系统,其中包括1GB的物理内存。按照一个4KB的物理页的大小来划分,1GB的空间就是262144(1GB/4K)个物理页。操作系统需要管理这些页面,即维护它们的状态:哪些页面正在被使用,哪些页面是空闲的。最简单直观的方法是用一个连续的内存空间来描述每个物理页的状态,每个位对应一个物理页:bit=1:表示使用该物理页;bit=0:表示物理页Pages空闲;262144页需要262144位,或32768字节。那么,对于1GB的物理内存,如下图所示:通过map结构,操作系统知道当前:哪些物理页正在被使用,哪些物理页是空闲的。每个物理页为4KB,所以地址的后12位全为0;map结构本身也需要存储在物理内存中,所以32768字节,一共需要存储8个物理页(32768/4*1024=8)。映射表在32位系统中,虚拟内存的最大空间是4GB,这是每个用户程序拥有的虚拟内存空间。实际上,操作系统会将虚拟内存的高地址部分作为操作系统使用,低地址部分留给用户程序使用;在Linux系统中,高地址1GB空间供操作系统使用;在Windows系统中,高地址2GB空间被操作系统使用,但可以调整;但是,实际的物理内存只有1GB(假设值),那么操作系统会用自己的操作方法让用户程序认为所有4GB的内存空间都可用。就像杂耍一样,十碗九盖,谁能玩得不露声色,谁就是高手!计算映射表本身占用的空间:映射表中的每一项都指向一个物理页起始地址。在32位系统中,地址的长度是4个字节,所以映射表中的每一项占用4个字节。由于所有4GB的虚拟内存都需要可用,映射表需要能够表示所有4GB的空间,因此总共需要1,048,576(4GB/4KB)个条目。因此,映射表占用的总空间大小为:1048576*4=4MB。也就是说,映射表本身会占用1024个物理页(4MB/4KB)。正是因为使用映射表需要这么大的物理内存空间,才有了后续的多级分页机制。虚拟内存看似被虚线“分割”成4KB个单元,但实际上并没有分割,虚拟内存还是连续的。带点的单元只表示它与映射表中每一项的映射关系,最终映射到一个相同大小的物理内存页。例如:虚拟内存的0~4KB空间对应映射表的第0项,其中存储的物理地址为0x3FFF_F000(最后一个物理页);虚拟内存的4KB~8KB空间对应映射表的第一项,其中存储的物理地址为0x0000_0000(第0个物理页);虚拟内存的最后4KB空间对应映射表的最后一个表项,其中存储的物理地址为0x0000_1000(第一个物理页);也就是说:虚拟内存和映射表之间存在并行的一一对应关系;映射表中的物理地址是与物理内存的随机映射关系,它指向可用的地方(物理页)。上面是用映射表分配物理内存,以4KB为页,然后和虚拟内存匹配,打包成连续的虚拟内存,供用户使用。虽然最终使用的物理内存是离散的,但是虚拟内存对应的线性地址是连续的。当处理器访问数据和获取指令时,它使用线性地址。只要是连续的,它最终就能通过映射表找到实际的物理地址。为了有更感性的认识,我们来看一个稍微具体一点的例子。一个线性地址的寻址过程我们假设用户程序中有一个代码段,那么在这个程序的LDT(LocalDescriptorTable)中,段描述的结构如下:假设如下:virtual内存(32位系统):4GB,实际物理内存为1GB;代码段起始地址位于3GB,即0xC000_0000;代码段长度为1MB;我们的目标是找到线性地址0xC000_2020对应的物理地址。根据描述符的结构,段基地址为0xC000_0000,极限为0x00100。在段描述符中,其他字段暂时忽略。该限制总共有20位。假设粒度是4KB,那么1MB的长度除以4KB,结果就是0x00100。代码段的起始地址(线性地址)为0xC000_0000,位于虚拟内存的高端四分之一附近,映射表中对应的表项也位于高端四分之一。映射表中的每一项都指向一个4KB的物理页,所以一个长度为1MB的代码段需要256个项。也就是说映射表中有256个表项,指向256个物理页:对于我们要找的线性地址0xC000_2020,先拆解成两部分:高20位0xC0002:是映射表的索引;低12位0x020:是物理页中的偏移地址;索引值0xC002对应下图中从3GB开始的第二个表项:上图中,代码段起始地址为0xC000_0000,对应映射表中索引为0xC0000这个表项,起始地址此条目中记录的物理内存页的大小为0x1000_0000(距起始地址256MB)。代码段长度为1MB,一共需要256个入口,所以最后一个入口的索引应该是0xC00FF。那么对于我们要找的线性地址0xC000_2020,对应的表项索引号是0xC0002,这个表项记录的物理内存页的起始地址是0x2000_0000(距离起始地址512MB)。找到物理内存的起始地址,加上偏移量0x020,那么最终的物理地址就是:0x2000_0020。以上就是通过映射表从线性地址到物理地址的页面转换过程。使用二级页表的翻译机制原理相同。无非就是将高20位的索引拆分(10位+10位),用两个表进行转换。这个问题将在下一篇文章中详细讨论。结束本文介绍:通过一个映射表,将连续的虚拟内存映射到离散的物理页,极大地利用了物理内存。当操作系统需要给用户程序分配一大块连续的内存空间时,映射表中的表项可以指向多个不连续的物理页,反正用户程序不能触及这一层(用户程序只与虚拟内存交互)。这样就大大提高了物理内存的使用效率。再加上换出换入机制(使用硬盘作为物理内存),用户程序认为有无穷无尽的物理内存。同时,我们也讨论了这种单一映射表的缺点,即映射表本身占用了4MB的物理内存空间。为了解决这个问题,先驱们推出了多级映射表(页目录表和页表),我们下篇见!本文转载自微信公众号“IOT物联网小镇”,可通过以下二维码关注。转载本文请联系物联小镇公众号。
