Lab 3: Page tables In this lab you will explore page tables and modify them to simplify the functions ,9 bit 一级索引找到二级页表,9 bit 二级索引找到三级页表,9 bit 三级索引找到内存页,最低 12 bit 为页内偏移(即一个页 4096 bytes)。 // 递归释放进程独享的页表,释放页表本身所占用的空间,但**不释放页表指向的物理页** kvm_free_kernelpgtbl(p->kernelpgtbl); p->kernelpgtbl // 将 src 页表的一部分页映射关系拷贝到 dst 页表中。 // 只拷贝页表项,不拷贝实际的物理页内存。 == usertests: copyinstr3: OK == Test usertests: sbrkmuch == usertests: sbrkmuch: OK == Test
在操作系统与计组学习中,我们会学习到页表这个概念,可以说,如今计算机的函数内存调用有很大一部分都离不开页表的调用,本文旨在详解页表的概念应用以及操作系统中的三级页表,三级页表对于节省空间起了至关重要的作用 三级页表 所谓三级页表,就是将原来的虚拟地址的页码27位分为三级,每一级9位: 而原来的页表工作流程也变为下图: 通过虚拟地址转换时,首先通过前9位页码找到第一层页目录,第一层页目录中包含了中间页表的物理地址 : 物理地址(56位) = 底层页表PPN(44位) + 虚拟地址offset(12位) 在三级页表的基础上,假设只使用了几个页面,那么中间层页表只需要加载0号页表即可,底层页表只需要加载要使用的几个页表项即可 ,中间层页表省了511个页面,底层页表省下了511*512个页面 简单理解,其实单级页表就是用长宽高之积来描述长方体,而三级页表就是用长、宽、高三个坐标来描述长方体,这样做的目的就是大大节省了加载页表所需要的空间 至此,有关于页表与三级页表的介绍就到这里了,页表的存在对于内核区与用户区加载代码起了至关重要的作用,真正理解页表的转换机制有助于我们对操作系统的虚拟内存有更深刻的认识
我是cloud3,前段时间有虚拟机出现内存问题,今天借着这个话题给大家介绍一下内存虚拟化,也就是MMU虚拟化。 ,X86上叫CR3 EPT:扩展页表 ptr:这里用来描述指向某个页表的寄存器 一.内存虚拟化要解决的问题 内存虚拟化实际实现就是MMU虚拟化,要实现GVA -> GPA -> HVA -> HPA 二.影子页表 (Shadow page table) 影子页表我用一句话来描述就是:VMM把Guest和Host中的页表合并成一个页表,称为影子页表,来实现GVA->HPA映射。 3, HVA->HPA,这一过程就是我们已知的使用物理MMU完成VMM进程的虚拟内存到物理内存的转换。 4, 把GVA -> HPA,这一路的映射关系记录到页表中,这个页表就是影子页表。 我是cloud3。
一、配置内核 首先配置内核,使其支持导出内核页表到debugfs下面: Kernel hacking ---> ---> [*] Export kernel pagetable layout to 而为了快速方便找到对应物理页而将所有的页帧结构体映射到此区域,后续只需使用virt_to_page, phys_to_page等宏实现虚拟地址,物理地址到对应页结构体的快速查询。 地址空间port属性说明 第一列 当前页表的映射范围地址 第二列 代表此映射范围大小 PMD PUD PTE 当标识为PMD PUD表示当前映射为block映射,如当前页表为4K,则pud的block映射一次性可映射 当标识为PTE表示为页表映射即PAGE_SIZE大小4K。 USR AP标记,用于标识当前范围是否在用户空间还是内核空间可读可写或者仅读。 x表述当前范围特权级别模式可执行,就是内核的可执行代码段,在内核中这段一般指向内核的text*段 SHD 表示可共享属性,在arm64上表述为多核之间可共享其页表可见 AF 访问标志,当首次映射页表时,
前面已经分析了内核页表的准备工作以及内核低端内存页表的建立,接着回到init_mem_mapping()中,低端内存页表建立后紧随着还有一个函数early_ioremap_page_table_range_init ,是从页表缓冲空间中申请还是通过memblock算法申请页表内存。 ,创建页表并使其指向被创建的页表。 为了避免前期可能对固定映射区已经分配了页表项,基于临时内核映射区间要求页表连续性的保证,所以在此重新申请连续的页表空间将原页表内容拷贝至此。 至此,内核页表建立完毕。
,如果只使用了一个页表,一个表项的大小为4byte,32位系统有4GB的物理空间(一个进程看到是4GB大小的虚拟空间),每一个表项对应着物理空间的第xxx页(4KB大小的页),那么应该有4GB/4KB= 如果是二级页表,规则就会改变,让二级页表对应到物理内存上的4KB大小的页,一级页表此时变成映射为物理地址的4MB(这样子是无法定位到具体的页(4KB)的,所以二级页表再去找),这样先找到一级页表,一级页表再和二级页表进行结合 ,二级页表相当于一级页表4MB分成了1024个(1KB个)4KB,找完后二级页表充当了offset的角色,此时定位到具体的4KB的页面,再用一级页表的offset一结合定位到具体物理地址。 这样一个进程浪费掉的空间是一级页表占用的:(4GB/4MB)*4byte=4KB,二级页表浪费掉的是1kb(1个一级页表占用这么多)*1kb(此时有1kb(4GB/4MB)个一级页表)=4MB,加起来是 4MB+4KB,比光用一级页表要多4KB,但是2级页表是可以不存在的,比如此时程序只用了%20的页,那么4MB就需要乘以%20,这样一下子就比只有一级页表时少了。
我们可以通过ring3的段寄存器. 当作GDT表的下标.进行查表. 查询GDT表. 段选择子结构: 首先先拆分选择子. 1B = 0000000000011 011 查询出来下表为3,那么去GDT表的第三项进行查找. 首先我们的CR3寄存器保存了表的首地址. 这里有一个页目录表,还有页表的关键词. 页目录表: 也称为PDE,而页表称之为PTE. 有没有发现,我们的Ring3程序.不过是那个内存区域也好.都是可以读的. 而我们Ring3下的修改内存分页保护属性,其实就是将这个页表的这个RW位进行置位. 而我们的虚拟地址当作下表进行查表. 设页目录表第一项为 003f0111, 此时页表为 取前20位,加上3个0. 003f0 + 000 = 003f0000 设页表为 00201456, 此时取出前20位加上虚拟地址的后边
通俗解释进程-科学家做蛋糕 科学家做蛋糕 然后女儿被蜜蜂蛰了 进程表–在内核 内存管理 经典 老式 管理方法: 基址寄存器(程序开始的地方) + 界限寄存器(程序长度) 空闲内存管理 每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从0开始 将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面”。 操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。 各个页面不必连续存放,可以放到不相邻的各个页框中。 重要的数据结构——页表 为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。 注:页表通常存在PCB中 一个进程对应一张页表 进程的每个页面对应一个页表项 每个页表项由“页号”和“块号”组成 页表记录进程页面和实际存放的内存块之间的映射关系
32bit虚拟地址的高12bit(bit[31:20])作为访问一级页表的索引值,找到相应的表项,每个表项指向一个二级页表。 我们从ARM linux内核建立具体内存区间的页表映射过程中来看页表映射是如何实现的。 首先通过init_mm结构体得到页表的基地址,然后通过addr右移PGDIR_SHIFT得到pgd的索引值,最后在一级页表中找到页表项pgd指针。 ,注意ARM Linux中实现了两份页表,硬件页表的地址r0+2048。 在x86的页表中有3个标志位是ARM32硬件页面表没有提供的。
操作系统多级页表与快表--12 为了提高内存空间利用率,页应该小,但是页小了页表就大了... 页表会很大,页表放置就成了问题... 第一种尝试,只存放用到的页 第二种尝试:多级页表,即页目录表(章)+页表(节) 多级页表提高了空间效率,但在时间上? TLB得以发挥作用的原因 为什么TLB条目数可以在64-1024之间? ---- 页表会很大,页表放置就成了问题… 页面尺寸通常为4K,而地址是32位的,所以32地址能够表示2的20次方个页面。 用书的章目录和节目录来类比思考… ---- 第二种尝试:多级页表,即页目录表(章)+页表(节) 对于书本而言,普通图书的目录结构通过是由章加小节构成的,如下: 假设我们需要去看看链表的相关知识点, 为了保证页表项连续,并且还要减少页表对内存的浪费,就必须采用多级页表的形式,但是多级页表时间上的不足,应该由什么来弥补呢?
本篇文章谈一下 C 语言中的指针数组和 CPU 的页表的类比。 0x01:C 语言中的几个简单概念 以前学习 C 语言的时候,有一些概念好像很绕,但是仔细想想,与其说是绕,不如说是语文的理解能力有限。 一张图里面就有很多名词,比如 CR3、PML4、PML4E、PDPTE、PDE、PTE。这些词在初学的时候就不太好记,理解还有点麻烦,但是实质它们之间的关系其实又不复杂。 CR3 是 CPU 的一个控制寄存器,指向了顶级页表的基址,它其实就是一个数组指针。 至于 PML4、PDPT、PD、PT 就是数组,前面三个就是指针数组,依次指向它们的下级,它们是有层级关系的。 页表的整个层级只有 4 层吧,就是 4 级页表,主流的 Linux 好像也是四级页表吧。不过,新版的 Linux 中要更新到 5 级了~! 比如 C 语言的指针、汇编的寻址、上面提到的页表之类的。
两级页表 单级页表的问题 某计算机系统按字节寻址,支持32位的逻辑地址,采用分页存储管理,页面大小为4KB,页表项长度为4B。 相应的,一个进程的页表中,最多会有220 = 1M = 1,048,576个页表项,所以一个页表最大需要220*4B=2^{22}B,共需要2^{22}/2^{12}=2^{10}个页框存储该页表。 根据页号查询页表的方法:K号页对应的页表项存放位置=页表始址+K*4要在所有的页表项都连续存放的基础上才能用这种方法找到页表项 需要专门给进程分配2^{10}=1024个连续的页框来存放它的页表 同时根据局部性原理可知 因此没有必要让整个页表都常驻内存。 问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。 个页表项,因此每1K个连续的页表项为一组,每组刚好占一个内存块,再讲各组离散地放到各个内存块中) 另外,要为离散分配的页表再建立一张页表,称为页目录表,或称外层页表,或称顶层页表 问题二:没有必要让整个页表常驻内存
1.概述 armv8 mmu页表结构比较复杂,总体说来可以将MMU分为以下几个部分: (1)虚拟地址(VA)为48位,而一般只使用到39位(512G内核,512G用户) (2)可以配置成3级页表(64K 页)或者4级页表(4K页) 最高的地址位是48为的地址,用4级页表进行管理。 2.虚拟地址格式 按照虚拟地址格式可以分为以下几种: 4K时页表的映射 ? 64K时页表的映射 ? 3.页表映射过程 如果要理解ARM64的映射过程,需要搞清楚的是 目前基于ARMv8-A架构的处理器最大可支持到48根地址线,也就是寻址2^48的虚拟地址空间。 由于需要进行4K页表的映射,所以需要3个512字节的数组用来存放表项。 ?
上一篇文章我们创建了 Xamarin 应用程序,当我们创建完应用程序后 VS 2019 帮我们生成了一个名为 MainPage 的 xaml 文件,这个文件在当前项目中是仅有的页面,我们称这个应用程序称为 单页应用程序
,而是使用L1,L2,L3页表这种术语。 Domain:Domain域,指明所属的域,Linux中只使用了3个域。 bit31:bit10:指向二级页表基地址。 二级页表的表项 bit0:禁止执行标志。 39~47:L0索引 30~38:L1索引 21~29:L2索引 12~20:L3 索引 假设页表基地址为TTBRx,访问页表基地址就能访问到L0页表的基地址,可以使用L0索引的值作为offset去访问 最后通过L3的页表项可以得到物理地址的bit12 ~ 47位,这个时候再将虚拟地址的页索引位对应到物理地址的0~11就是完整的物理地址。 (3) 首先检查映射类型的prot_l1字段是否为0。prot_l1表示第一级页表(Level 1 Page Table)的保护位。如果prot_l1为0,表示无法使用页面进行映射。
而在Linux中存储虚拟地址到物理地址转化的关系的表称为页表。 目前最新的linux内核已经支持了5级页表。下图是一个4级页表的转化关系图。 ? Table Entry) 页表 如果是5级页表的话,会在PGD和PUD之间增加一个level叫P4D。 LINUX目前是支持5级页表,当然也可以通过config(CONFIG_PAGE_LEVELS)去配置的,目前手上的模拟板使用的是三级页表,如果使用三级页表的话,PUD等于PMD。 前期条件是目前配置的是3级页表。 目前此地址是线性地址,转化关系比较简单。 4K(CONFIG_ARM64_PAGE_SHIFT=12、CONFIG_ARM64_4K_PAGES=y),页表转化是3级(CONFIG_PAGE_LEVELS=3),所以我们需要详细描述出39位是如何划分的
1将这一定义应用于J-STD-001H的第8章(也就是题目中提及的“3页纸”),我们可以了解为了符合新的要求都需要做些什么。 通常这种缺陷体现为图3所示的组件出现与漏电有关的失效,这个组件已通过了ROSE测试。 图3中的组件对电镀通孔(PTH)连接器引脚使用了手工焊接操作,这一流程是整个工艺流程中唯一会留下大量离子残留物的操作,但只要用整个表面区域的平均数值,就不会检测到存在问题。 第3条注释是说在高温和湿度条件下,使用正常的操作电源对带电产品进行电气测试。在我看来,这是确定离子清洁度对现场操作影响的最重要测试。 WP-019B用28页篇幅来解释J-STD-001H中的3页内容,有力说明了清洁度的重要性。
开始编码之前,请阅读xv6手册的第3章和相关文件: *kernel/memlayout.h*,它捕获了内存的布局。 *kernel/vm.c*,其中包含大多数虚拟内存(VM)代码。 修改struct proc来为每一个进程维护一个内核页表,修改调度程序使得切换进程时也切换内核页表。 对于这个步骤,每个进程的内核页表都应当与现有的的全局内核页表完全一致。 如下,历遍整个内核页表,然后将所有有效的页表项清空为零。如果这个页表项不在最后一层的页表上,需要继续进行递归。 内核启动后,在XV6中该地址是0xC000000,即PLIC寄存器的地址;请参见kernel/vm.c中的kvminit()、kernel/memlayout.h和文中的图3-4。 不要忘记在userinit的内核页表中包含第一个进程的用户页表 用户地址的PTE在进程的内核页表中需要什么权限?
对页目录进行操作 重新梳理一下思路:如果对一个普通物理页(下文简称为:普通页)里的一个地址处的数据进行操作,需要经过3次查表操作: 从页表的某个表项中,找到的那个物理地址,就是最后要操作的普通物理页 我们就来构造一个线性地址 addr,让它经过3次查表操作之后,能够指向页目录的物理地址。 一级查表:构造线性地址的前 10 位,来确定页表的物理地址 一级查表:查找的对象是页目录。 很容易就能得到addr的前10位应该是:0x3FF(二进制:1111_1111_11)。 于是,就得到了中间10位的结果:0x3FF(二进制:11_1111_1111)。 于是,可以构造出线性地址的中间10位是:11_1111_1111(0x3FF)。 由于这个表项中存储的地址是 0x0800_0000,指向的正是页表自己,只不过马上它就被当作普通物理页被使用。