linux 内存映射机制

来源:互联网 发布:顶易软件多少钱 编辑:程序博客网 时间:2024/05/17 20:34

前言

linux一直想研究,可是太复杂了,并且工作也不太需要,就一直是块心病,像花一段时间好好看看,内存映射机制貌似很恶心人,我也是搞的恶心死了,慢慢记录下看的书的进度

硬件寻址

linux是基于页式管理机制管理内存的,意思是吧真个空间分成一页一页,每一页4k,我们知道真个32为cpu最大可以寻址4g,也就是有1m个页,我们暂时来说下linux的内存整体管理机制。
linux内存管理分为三层管理
1k个pgd
每个pgd对应几个(个数不确定,对于32为一个)pmd
每个pmd对应几个PT(32为、位为1k个(貌似是的))PT

其实这里为了搜寻方便,如果直接设置1m个表,那么如果左后一次寻找需要地址自加1m次很麻烦。这样感觉可以节省时间。

这里我暂时不太懂,

关于映射。
每次我们有一个虚拟地址,我们系统如何通过虚拟地址找到逻辑地址呢,我们知道我们的虚拟地址也是32位,

  1. 首先我们取虚拟地址的高十位,然后通过这个十位的数字,找到数组中的pgd,这个放在那里我也不知道哦,系统开启时候专门分了一个页来记录这个数组,总之就是通过前十位找到一个对应的PGD。
  2. PGD中高20位,保存一个页面的高20位,然后我们通过高20位补12个零,我们可以得到一个页面表数组的第一位(存放在一个4k’的页面中),这里我们可以把它当做一个数组,里面保存了4k/4=1k’个页面。
  3. 当我们找到一个用于存放页面表的页面。我们通过虚拟地址的中间十位取出。数组的偏移地址就是中间十位的大小*4,最后我们得到我们的需要找的页面。
  4. 最后我们可以通过虚拟地址的低12为找到在页面内的偏移地址,算出逻辑地址,让后这里基本映射完毕

这里我们需要搞懂我们为了完成映射我们需要一个pgd数组,一共一k大小,占用个页面,每个pgd对应一个页面,里面是一个数组保存着k个页面,所以我们为了映射就需要4K*1K+1k的内存来完成映射,

是不是感觉好麻烦啊,需要找这么久,可是这里有一个优点,告诉缓存,在读入一个页面后,不用去读内存,直接通过高速缓存来完成,速度也是很快的。(还是没实地址快,或者段地址快,)

具体寻址方式

我们本能反应是32为地址总线,可以直接访问4g的空间,可是为了设计出保护模式和与之前芯片通用,搞出了很奇怪的设计方法。
第一、cpu根据传输过来的指令和地址(这个地址是软件强行定义的一个地址,跟cpu访存的地址无关,并且不一定是32位),首先确定cpu的四个段寄存器的内容(ps仅仅改变需要改变的那个),现在我们在我们一个特定的寄存器中放置了一个特定的数据,这个数据前十三位是cpu自己计算出来的,后三位有特定用途。前十三位的计算是根据另外一个数据结构计算的。
第二、我们通过那个段寄存器,仅仅是为了查找一个用来保存访问位置的权限信息的数据。可是如果面对整个计算机的全部内存都设置一个特有的权限,就显得过于复杂(这是不可能的,因为我们确定了一个数据的权限,使用一个数据来保存这个权限,在使用一个数据来保存这个权限的权限…….).所以只能保存连续的一段权限,这样又有一个问题,我们如果使用寄存器来保存权限信息,分的段太小,寄存器不够用,如果分的大。管理又没有意义,。我们可以用一个寄存器。来保持一个指针,就好了。
如果我们确定好每个段的长度为a,我们只用使用4g/a个数组,数组总保存每个特定段寄存器中保存的基地址,与原来的虚拟地址相结合就可以得到实地址,当然这里我们知道我们段寄存器只有13位,所以,每个段最少也该是2^19,即512k。但是这里有个问题,很不灵活。这又回到了段式内存管理方法,页式内存管理更加方便,所以需要改正,
只要我们可以设置每个段的长度。我们就可以通过cpu来控制权限的访问,这样直接交由程序控制更加方便,那么我们如何控制呢,当然是修改刚才通过指针得到那个权限数组。我们可以直接在这个特殊数组中设置一个用以控制段长度的片段,如果我们在我们那个特定的数组(段描述表)设置段长度,我们就可以随意控制内存中的权限,是不是感觉很爽
道理明白了,我们还是来研究i386具体是如何实现寻址的,
在段寄存器中,前十三位没问题,后三位是干啥的呢,我们如果我们想保存一些私有东西和公共的东西最好分开,那么我们为啥不设置成两个表,这样我们就有一位是用来判断使用哪个表(也就是两个表的指针)这两个指针是GDTR(global descriptor table register)和LDTR(local descriptor table register)。后两位是判断权限的,一共四个权限,
最关键的问题出现了,我们假定我们已经我们通过软件设置好了段描述表,那么我们可以想到这个表的内容。这里我不细说80386的段描述项的具体内容,我们仅仅是需要知道这货需要保存什么,第一段地址。需要32位,因为我们想设置出任意段长,会产生任意基地址,还有段长,最好32位(这里现实是残酷的,其实不影响)。权限信息,等等等,这里暂时还是不要研究这些复杂的特性了。

总之这里是全部80386的寻址方式。

寻址貌似比较简单(我看了好久)。但是如何让每个进程可以的划分这么多的空间的,比如对于我们代码段要连续,如果我们没有一个好的解决办法。计算机的内存的碎片化会很严重(这里一个空闲page哪里一个空闲page,)如果我们每个进程最小需要两个内存,那么我们中间隔着的一个page就浪费了,为了解决这个问题。我发现linux给出了一个很神奇的解决办法,
就是每个进程都有自己的映射表,如果我们每个映射表都是每个进程独自拥有,我们的进程都可以从虚拟机地址为零的地方创建进程的开始,虚拟地址是0,但是映射最终是通过pgd找到pt最终找到pte,我们如果设置的地址是0,计算机的mmu只会从lgtr中找到pdg的第一元素,通过第一个元素,作为指针,找到数组pt,在找到第一元素,这就是我们的0号page,如果我们都按照正常的地址映射,这里当然会有问题,因为只有一个0号page,如果我们控制每一个线程,把特定的page设置成0好page,比如我们第一进程设物理地址为0的0号page作为第一个进程的0号虚拟地址,第二进程设置物理地址为2的那个page作为0号page就好了。最终解决了问题。
上面的假设谁没有意义的,因为没必要一定要设置成号地址,这里仅仅是给一个概念,在虚拟地址的下,对于用户空间,虚拟地址的连续性都是有程序员控制的。

刚才这些理论都建立在不一个条件下,我们知道有那些page是空的,那些page是已经使用的,所以我们必须有一个进程管理所有的内存—-这就是内核进程。在内核状态,我门有一个全局的变量,mem_map,这是一个数组,与物理地址大小相对应,也就是说,我们的虚拟地址的高20位(不是pte中的高20位)为下标,我们就可以找到我们需要的page。

这里还有一个问题,是pte的低12位,这里我仅仅介绍一个最低位,这个代表是够存在内存中,这种标志位很有意思,不再内存中(我们可以把它移到交换区),这里是不是多了一位。对于寻址空间是不是忽然打了一倍,由原来的20+12+1,变成了8g的寻址能力。这是理论上的,因为内存中的(物理地址的低1g为内核固有的,并且不映射)所以变成的空间是6+1,最大空间。
加入我们虚拟地址已经设置好映射关系,我们呢就可以直接通过pte找到page了,这里有个宏

#define PAGE_SHIFT  12

//这个完全可以通过pte找到page,

#define pte_page(x) (mem_map+((unsigned long)(((x).pte_low >> PAGE_SHIFT))))

//这个是通过虚拟地址找到page,

#define virt_to_page(kaddr) (mem_map + (__pa(kaddr) >> PAGE_SHIFT))

两个功能基本类似。只是如果设置少pte。

这里少了page的介绍,不过这里仅仅介绍一些内存映射的问题,以后在慢慢介绍真个寻址机制,与内存分配。

后记

终于搞定这些乌七八糟的映射关系,好烦啊。以后继续。争取先搞定内存映射,以后慢慢啃这东西。

0 0
原创粉丝点击