第2章 存储管理

2.1 Linux内存管理的基本框架

2.2 地址映射的全过程

  Linux会在不同的cpu上运行,相应的80386也不仅仅只会跑Linux系统,所以系统和cpu之间的配合并不是完美的。

2.2.1 逻辑地址到线性地址

  逻辑地址到线性地址即段映射阶段。假设整个系统的映射机制都已经建立好,cpu正在执行call 08048368去执行另一段代码。

  在段映射阶段首先要根据段寄存器里的值去选择一个合适的段描述符。cpu根据所执行指令的不同去选择不同的段寄存器,由于这里的call指令是去执行别的代码段的内容,所以采用CS寄存器里的值作为段选择符。

  那么此时此刻CS里的值是多少?CS的值是在什么时候被设置成多少,又会在什么时候被修改? 内核在新建一个进程的时候需要将该进程所需要使用的段寄存器全部设置好,代码如下:

  411到414是用来设置ds es ss cs寄存器值的地方,后面的宏定义的常量是被设置成的值。可以看出两个很有意思的地方:1、除了cs寄存器外,其余的几个寄存器都是一样的值 2、把宏常量设置成寄存器的值,所以所以进程的cs ds es ss内容是一样的。Intel工程师分出来的代码段、数据段、堆栈段到了Linux这里只剩下了代码段和数据段。

  整个Linux内核里就四个段选择符,如下所示,并且从TI=0可以看出全都放在了GDT表里了,而且在Linux里基本上不使用LDT。

  回到程序,现在段映射需要做的是去GDT表里把index=4的段描述符拿出来,GDT表的定义和里面的段描述符如下所示。

  

  

  总的来说这四个段的基址都是0x0,长度都是0xffffff ,并且都在内存里,也就是是说内核不会把整个段全部置换到硬盘上。他们的不同之处主要体现的访问的权限(DPL)和该段存储的是数据还是代码。虽然看到现在LInux的段式映射徒有其表,但是在这个地方他还是完成了权限的检查。比如段描述符要求的DPL为0,但是CS寄存器里的DPL为3,那么这次访问会被禁止。但其实这个检查在后面的页映射还会再做一次。

  内核在新建一个进程的时候,为其分配CS寄存器的值(虽然是固定的),CS寄存器的一部分用于定位段描述符,另一部分用于规定该进程的权限,即可访问的存储区域是属于内核还是用户。从这个角度来看进程是被内核安全的管理的,一个进程在创建的时候就规定好了其权限。

  

2.2.2 线性地址到物理地址

  经过段式映射,一个虚拟地址被映射成线性地址,而且这个线性地址和虚拟地址一样,因为基址都是0。但最起码从法理的角度现在他是线性地址了,整个过程就是在欺骗80386。

  Linux里线性地址到物理地址的映射需要经过三个步骤,这一点是和80386不一样,80386只有两个步骤。这个是LInux虚拟的映射模型,其实和80386原理都是一样的,只不过多了一个步骤。为了解决这个矛盾,在LInux编译的时候回根据CPU的型号选择相应的编译版本,在80386上运行的时候回选择32位地址的两层映射。其实从这里很好的看出来的虚拟模型和实际模型的区别,虚拟模型是3层的但是受限于硬件只能改成两层的。

   下面这个文件定义了如何去使用32位的线性地址,也就定义了如何从线性地址映射到物理地址。第一个PGDIR_SHFIT定义了PGD索引的起始位置,bit22到bit31一共10位,索引是10位的,所以一个PGD表里可以有的PGD的个数是1024个。接下来的PMD_SHIFT也是22,从bit22到bit22就是一位也没有,也就是说PMD只有一个,这样以来三层映射到了386这里变成了两层,其实从结果来看就是跳过了PMD这一层,但在LInux源码的角度来说是三层映射。

  整个地址的映射是由MMU来管理的,所以虽然说是CPU来完成了整个地址转换过程,但是其实本质上是MMU来完成的,在80836上LInux的页式管理如下。

  在一个进程上处理机的时候,内核会使用特权指令设置好PGD的BASE,存在寄存器CR3里,这个PGD的地址是进程独享的,也就是每一个进程的PGD的地址不一样,这一点和段式映射都使用同一个GDT不一样。然后MMU用线性地址的第一个位段即高10位在PGD里找到相应的项,然后MMU用线性地址中的PT段作为下表在PGD表项里找到相应的PTE,在PTE里存的是就是物理地址的BASE,然后线性地址剩下的部分用于和物理地址的BASE组合得到真正的物理地址。

  整个映射过程CPU要访问三次内存:取PGD,取PT,取PTE,所以虚存的高效实现要依赖于缓存。

2.3 几个重要的数据结构和函数

  在整个逻辑地址到物理地址的映射过程中主要是由MMU和几个寄存器的来完成,LInux需要做的是在一个进程上处理机的时候设置好相应的寄存器的值和在内存中存放合理的数据,比如设置gdtr的值和GDT,准备好PGD、PT。本节介绍的是这个LInux在管理内存映射的时候需要用几个函数和数据结构。

  在页式映射阶段使用的PGD PMD PT定义如下,我们当然使用上面的那部分结构体的定义。

  页式映射的最后一站是PTE到物理地址的映射,PTE的定义包含了两个部分,32位的PTE指针高20位是作为指针来使用,低12位用来表示页面的状态信息和访问权限,具体来说是9位用来表示权限和标志位。这些权限标志位表示该地址是否在内存中、其权限大小、是否可以使用缓存等等、

  

  内核里有一个全局变量指向一个page数组,每一个page代表一个物理页面,那么整个page数组就代表整个物理页面。所以PTE的高20是这个数组的索引,拿着这个索引就可以在数组里定位到想要的物理页面。这个索引定位的过程是从软件的角度来说的,从硬件的角度来说,MMU需要把20位的地址和剩下的访问权限结合在一起然后去物理内存上定位并检验权限,事实上高20位补上12个零就是就是物理页的起始地址。如果MMU的工作过程正常结束,那么通过索引下标访问page数组的过程就正常结束,反之就会抛出相应的异常。

  MMU在进行在拿着高20位进行映射前会先检查其权限,在检查权限的过程中会首先检查P标志位,即_PAGE_PRESENT,该位为1的时候表示对应的物理页在内存中,后面才会继续。这是一种物理页定位失败的情况,除此之外还有另一种失败的情况。在拿着32为的线性地址一步步定位PTE地址的时候,发现PTE整个内容为0,则说明该物理内存和虚拟内存的映射还没有建立成功。

  

原文地址:https://www.cnblogs.com/AshOfTime/p/10367042.html