前面我们介绍了内存管理的三级架构:内存节点-内存区-页。今天我们就介绍由页引入的页表机制。 linux采用了一种同时适用于32位和64位系统的普通分页模型。对于像32位arm系统来说两级页表已经足够,但64位系统需要更多数量的分页级别。2.6.10版本以前,linux采用三级分页的模型,从2.6.11版本开始采用4级分页模型。4级页表如下: 页全局目录(pgd) 页上级目录(pud) 页中间目录(pmd) 页表(pte):页内偏移 ![]() 图 四级页表映射图 说明:控制寄存器cr3中存放了页目录PGD的物理地址,通过cr3寄存器可以找到页目录,不同的进程有不同的页目录地址。进程切换时,操作系统负责把页目录地址装入CR3寄存器。备注:这是在32位pc机上的情况,arm没有cr3寄存器,放在了另外的地方,后面会介绍。 我们来解释下四级页表的映射过程。 一个线性地址被划分成了五部分,通过某种方式得到pgd的地址,也就是顶层页目录的地址,顶层页目录顺序存放着许多页目录项,以线性地址的第一部分作为索引找到对应的顶层页目录项,所谓的页目录项是指下一级页目录的地址,一个页目录项32位。通过顶层页目录项我们就可以找到二级页目录的地址,每一个顶层页目录项都对应着一个二级页目录。同理,以线性地址的第二部分为索引可以得到二级页目录项,依次类推,得到最后一级,我们把它叫做页表项,页表项存放着线性地址对应的物理页的地址,而线性地址的最后一部分,12位,表示页内偏移,通过这个页内偏移,我们就能找到页内的任一字节。 这个地址翻译过程完全是由硬件完成的。 到这里,我们抛出一个问题:为何要采用多级页表,一级页表不行吗?直接20位表示一个页表,12位表示页内偏移,一共可以存放1M个页表项,一个页表项可以表示4K,也就是一共映射了4G空间。 首先我们来计算下各级页表需要占用的空间。 一级页表: 对于4G的空间,4K页大小,那需要2^20 == 1M个页表项(无论用不用都需要,因为并不知道哪个地址是否会被访问),1M * 4Byte == 4M。 也就是说每个进程都需要4M的内存(每个进程最小需要4M的内存用于存放页表项),为啥是每个进程?因为每个进程的虚拟地址映射 二级页表: 对于4G的空间,4K页大小,那么需要一个页目录(1024项*4字节=4K)+ 4K*2^10个页表项=4M+4K的空间。 假设进程只需小于4M的内存空间,那么只需要一个页目录(1024项*4字节=4K)+ 4K*1个页表=4K+4K=8K。 总结下一级页表的两个问题 1.页表在内存分配时必须连续,否则无法查表。 2.每个进程都必须实实在在占用4M的空间用于页表,必须全部分配,进程才可以使用。 二级页表的优势 1.允许页表被分散在内存的各个页面中,不需要连续的4M内存块,只需要连续的4K内存块就可以了,因为只要一个页目录项的所有页表项连续即可; 2.并不需要为不存在的或线性地址空间未使用部分分配二级页表,用到时再分配页表,这也是二级页表能节省内存的原因; 3.可以在虚拟内存中存放二级页表。 ![]() 图 二级页表示意图 ![]() 上面这张图片形象的解释了二级页表相对于1级页表的好处: (1)一级页表中的一个PTE是空的,其对应的二级页表就不存在了,节约了很大部分内存。 (2)只有一级页表需要总是存储在内存中,其二级页表使用时才会加载进内存,除非频繁使用的二级页表。 那么多级页表有没有坏处呢?当然是有的。不然arm干嘛不用3级、4级甚至n级呢,而要用二级页表呢。 使用一级页表时,读取内存中一页内容需要2次访问内存,第一次是访问页表项得到虚拟地址所对应的物理页的地址,第二次是访问要读取的一页数据。但如果是使用二级页表的话,就需要3次访问内存了,第一次访问页目录项得到页表项的地址,第二次访问页表项得到页地址,第三次访问要读取的一页数据。访存次数的增加也就意味着访问数据所花费的总时间增加。 人类是聪明的,针对多级页表这个缺点,又发明了缓存机制。即将最近访问过的或者频繁访问的页的页表项放于缓存之中,下次先去那里看,如果有直接去缓存拿。也就是下面的图: ![]() 图 引入TLB的地址转换示意图 补充介绍TLB CPU的Memory management unit(MMU)cache最近使用的页面映射。我们称之为translation lookaside buffer(TLB)。TLB是一个组相连的cache。当一个虚拟地址需要转换成物理地址时,首先搜索TLB。如果发现了匹配(TLB命中),那么直接返回物理地址并访问。然而,如果没有匹配项(TLB miss),那么就要从页表中查找匹配项,如果存在也要把结果写回 TLB。 CPU访问内存时的硬件操作顺序: 1 CPU内核(图中的ARM)发出VA请求读数据,TLB(translation lookaside buffer)接收到该地址,那为什么是TLB先接收到该地址呢?因为TLB是MMU中的一块高速缓存(也是一种cache,是CPU内核和物理内存之间的cache),它缓存最近查找过的VA对应的页表项,如果TLB里缓存了当前VA的页表项就不必做translation table walk了,否则就去物理内存中读出页表项保存在TLB中,TLB缓存可以减少访问物理内存的次数。 2 页表项中不仅保存着物理页面的基地址,还保存着权限和是否允许cache的标志。MMU首先检查权限位,如果没有访问权限,就引发一个异常给CPU内核。然后检查是否允许cache,如果允许cache就启动cache和CPU内核互操作。 3 如果不允许cache,那直接发出PA从物理内存中读取数据到CPU内核。 4 如果允许cache,则以VA为索引到cache中查找是否缓存了要读取的数据,如果cache中已经缓存了该数据(称为cache hit)则直接返回给CPU内核,如果cache中没有缓存该数据(称为cache miss),则发出PA从物理内存中读取数据并缓存到cache中,同时返回给CPU内核。但是cache并不是只去CPU内核所需要的数据,而是把相邻的数据都去上来缓存,这称为一个cache line。ARM920T的cache line是32个字节,例如CPU内核要读取地址0x30000134~0x3000137的4个字节数据,cache会把地址0x30000120~0x3000137(对齐到32字节地址边界)的32字节都取上来缓存。 牛客网居然考到了多级页表相关的知识点: ![]() 图 牛客相关题目 ---------------------------------------------------------------------------------------------------------------------- 我们尊重原创,也注重分享,文章来源于微信公众号:嵌入式开发者,建议关注公众号查看原文。如若侵权请联系qter@qter.org。 ---------------------------------------------------------------------------------------------------------------------- |