linux页表(Linux页表映射)
其实linux页表的问题并不复杂,但是又很多的朋友都不太了解Linux页表映射,因此呢,今天小编就来为大家分享linux页表的一些知识,希望可以帮助到大家,下面我们一起来看看这个问题的分析吧!
linux为什么主要采用分页机制来实现虚拟存储管理
1分页机制
在虚拟内存中,页表是个映射表的概念,即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address).
很显然,这个页表是需要常驻内存的东西,以应对频繁的查询映射需要(实际上,现代支持VM的处理器都有一个叫TLB的硬件级页表缓存部件,本文不讨论)。
1.1为什么使用多级页表来完成映射
但是为什么要使用多级页表来完成映射呢?
用来将虚拟地址映射到物理地址的数据结构称为页表,实现两个地址空间的关联最容易的方式是使用数组,对虚拟地址空间中的每一页,都分配一个数组项.该数组指向与之关联的页帧,但这会引发一个问题,例如, IA-32体系结构使用4KB大小的页,在虚拟地址空间为4GB的前提下,则需要包含100万项的页表.这个问题在64位体系结构下,情况会更加糟糕.而每个进程都需要自身的页表,这回导致系统中大量的所有内存都用来保存页表.
设想一个典型的32位的X86系统,它的虚拟内存用户空间(user space)大小为3G,并且典型的一个页表项(page table entry, pte)大小为4 bytes,每一个页(page)大小为4k bytes。那么这3G空间一共有(3G/4k=)786432个页面,每个页面需要一个pte来保存映射信息,这样一共需要786432个pte!
如何存储这些信息呢?一个直观的做法是用数组来存储,这样每个页能存储(4k/4=)1K个,这样一共需要(786432/1k=)768个连续的物理页面(phsical page)。而且,这只是一个进程,如果要存放所有N个进程,这个数目还要乘上N!这是个巨大的数目,哪怕内存能提供这样数量的空间,要找到连续768个连续的物理页面在系统运行一段时间后碎片化的情况下,也是不现实的。
为减少页表的大小并容许忽略不需要的区域,计算机体系结构的涉及会将虚拟地址分成多个部分.同时虚拟地址空间的大部分们区域都没有使用,因而页没有关联到页帧,那么就可以使用功能相同但内存用量少的多的模型:多级页表
但是新的问题来了,到底采用几级页表合适呢?
1.2 32位系统中2级页表
从80386开始, intel处理器的分页单元是4KB的页, 32位的地址空间被分为3部分
单元
描述
页目录表Directory最高10位
页中间表Table中间10位
页内偏移最低12位
即页表被划分为页目录表Directory和页中间表Tabl两个部分
此种情况下,线性地址的转换分为两步完成.
第一步,基于两级转换表(页目录表和页中间表),最终查找到地址所在的页帧
第二步,基于偏移,在所在的页帧中查找到对应偏移的物理地址
使用这种二级页表可以有效的减少每个进程页表所需的RAM的数量.如果使用简单的一级页表,那将需要高达220个页表,假设每项4B,则共需要占用220?4B=4MB的RAM来表示每个进程的页表.当然我们并不需要映射所有的线性地址空间(32位机器上线性地址空间为4GB),内核通常只为进程实际使用的那些虚拟内存区请求页表来减少内存使用量.
1.3 64位系统中的分页
正常来说,对于32位的系统两级页表已经足够了,但是对于64位系统的计算机,这远远不够.
首先假设一个大小为4KB的标准页.因为1KB覆盖210个地址的范围, 4KB覆盖212个地址,所以offset字段需要12位.
这样线性地址空间就剩下64-12=52位分配给页中间表Table和页目录表Directory.如果我们现在决定仅仅使用64位中的48位来寻址(这个限制其实已经足够了, 2^48=256TB,即可达到256TB的寻址空间).剩下的48-12=36位被分配给Table和Directory字段.即使我们现在决定位两个字段各预留18位,那么每个进程的页目录和页表都包含218个项,即超过256000个项.
基于这个原因,所有64位处理器的硬件分页系统都使用了额外的分页级别.使用的级别取决于处理器的类型
平台名称
页大小
寻址所使用的位数
分页级别数
线性地址分级
alpha 8KB 43 3 10+ 10+ 10+ 13
ia64 4KB 39 3 9+ 9+ 9+ 12
ppc64 4KB 41 3 10+ 10+ 9+ 12
sh64 4KB 41 3 10+ 10+ 9+ 12
x86_64 4KB 48 4 9+ 9+ 9+ 9+ 12
显示进程页表——海光X86和国产Linux的一个问题
为了解决在Hygon的C86系统上使用国产Linux进行PCI BAR的mmap时,用户态访问BAR返回无效数据的问题,我需要确认用户进程确实进行了mmap操作。问题的核心在于验证mmap给用户进程的虚拟地址VADDR的页表项是否真正指向了PCI设备的BAR空间。在用户态下,查看页表的方法有限,因此借助crash工具进行实时调试成为一种可行的解决方案。
在使用crash工具显示的VADDR-> PADDR的页表项时,我注意到在Hygon系统中,bit47被设置为1,而通常这应作为保留位,这可能与系统设置有关。此外,我编写了一个内核模块来显示指定进程的页表,以期从源代码层面深入了解问题。
为了进行测试,我选择在Intel系统的KVM环境中运行Linux虚拟机,并在其中创建了一个名为pcibar.c的用户态进程,用于对虚拟机中的PCI设备的BAR空间进行mmap操作。同时,我开发了名为procvmapt.ko的内核模块,用于显示进程pcibar的页表,以便于与系统输出进行对比验证。
首先,我展示了PCI BAR空间的范围,然后pcibar.c通过/dev/mem映射了0xfea00000开始的16KB空间。接着,我对比了内核模块的输出和系统输出,验证了页表的正确性。内核模块的输出揭示了一些有趣的现象,例如bit47的设置和其他细节,这些信息有助于深入理解系统行为。
通过对比输出,我们可以看到页表项中PTE(页表表项)中的物理地址部分(physical address)正确地指向了PCI设备的BAR地址范围。这表明页表完整地建立了,没有问题,符合预期。
问题的最终解决方案是在升级了操作系统后得以实现,但问题的根本原因仍然未知,厂家对于操作系统的更新细节也没有详细说明。这使得我们无法完全理解为何之前会出现数据返回全“1”的情况。希望厂家在未来有时间对这个问题进行更深入的探讨。
在与同事的讨论中,他提到其内核团队认为BAR的映射属于MMIO(内存映射I/O)范畴,每次访问都需要通过page fault handler处理。然而,我认为这种观点在当前场景下是错误的,至少对于mmap操作而言。在虚拟化环境下,映射的处理方式可能有所不同,这需要进一步的讨论。
从页表项的测试结果来看,mmap操作成功构建了用户页表,MMIO地址被正确地设置在PTE中。CPU在后续访问时,会直接发出对应的物理地址给内部地址逻辑,最后分发到PCI root complex和PCI拓扑,而不需要每次都通过page fault异常处理,否则效率将会大大降低。
通过在pcibar.c中进行循环读操作,我验证了映射后的BAR访问并不需要经过page fault异常处理。实际测试结果显示,只有几十次(例如67次)page fault,远远低于理论预期的1000次,这证明了映射后的BAR访问效率非常高,不需要每次访问都通过异常处理。
一文看懂Linux 页表、大页与透明大页
我们通常所说的内存容量,指的是物理内存,只有内核才可以直接访问物理内存,进程并不可以。
Linux内核为每个进程提供独立的虚拟地址空间,这个地址空间是连续的,使得进程能够方便地访问内存,实际上访问的是虚拟内存。
虚拟地址空间分为内核空间和用户空间两部分,不同字长的处理器,地址空间的范围也不同。比如最常见的32位和64位系统:
既然每个进程都有一个这么大的地址空间,所有进程的虚拟内存加起来自然比实际的物理内存大得多。所以,并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。内存映射,其实是将虚拟内存地址映射到物理内存地址。
为了完成内存映射,内核为每个进程维护了一张页表,记录虚拟地址与物理地址的映射关系。
页的大小只有4 KB,导致的一个问题是,当物理内存很大时,页表会变得非常大,占用大量物理内存。
假如一个进程,访问的物理内存有1GB,即262144个内存页,在32位系统中,页表需要262144*4/1024/1024=1MB,而在64位系统下,页表占用的空间增加1倍,即2MB。
对于Linux系统中运行的Oracle数据库,假如数据库的SGA大小12GB,如果一个Oracle Process访问到了所有的SGA内存,其页表大小会是24MB,如果有300个左右的会话,那么这300个连接的页表会达到7200MB,只不过并不是每个进程都会访问到SGA中所有的内存。
页表大小可以通过/proc/meminfo的PageTables部分查看。
为了解决页表项过多的问题,Linux提供了两种机制,也就是多级页表和大页(HugePage),后面我们以大页为重点。
大页顾名思义,就是比较大的页,通常是2MB。由于页变大了,需要的页表项也就小了,占用物理内存也减少了。
严重问题可能包括:
大页的分配方法包括设置memlock,设定oracle用户可以锁定内存的大小,这个参数在/etc/security/limits.conf文件,单位是KB。开启大页时,这个参数很重要,如果设置过小,可能导致大页无法被用到,白白浪费内存。
对于11g,由于HugePage只能用于共享内存,不能用于PGA,所以不能使用AMM,只能分别设置SGA和PGA。SGA同样只能是AUTO方式管理,需要将SGA_TARGET_SIZE设为大于0的合适值。
大页是惰性分配的,用到才会分配。随着数据库的使用,可以在/proc/meminfo中查看HugePages_Free是否已经减少。如果已经减少,表明已经使用到HugePage Memory。
在一些Linux系统中,transparent hugepage被默认开启,它允许大页做动态的分配,而不是系统启动后就分配好,根据Oracle MOS DOC:1557478.1,transparent hugepage导致了很多的问题,建议将其关闭。
如果这个文件不存在,则检查
如果2个文件都不存在,说明系统内核中移除了THP,例如OEL 7。