在操作系统与计组学习中,我们会学习到页表这个概念,可以说,如今计算机的函数内存调用有很大一部分都离不开页表的调用,本文旨在详解页表的概念应用以及操作系统中的三级页表,三级页表对于节省空间起了至关重要的作用 虚拟地址的页码有27位,每个页表条目为8字节,那么占用内存为: 2^27 * 8 bit = 1GB,虽然看似不多,但是如果用户程序只使用了几个页表条目,但是还是要占用1GB的内存还是很恐怖的,因此科学家们想到了使用三级页表来代替单级页表 : 物理地址(56位) = 底层页表PPN(44位) + 虚拟地址offset(12位) 在三级页表的基础上,假设只使用了几个页面,那么中间层页表只需要加载0号页表即可,底层页表只需要加载要使用的几个页表项即可 ,中间层页表省了511个页面,底层页表省下了511*512个页面 简单理解,其实单级页表就是用长宽高之积来描述长方体,而三级页表就是用长、宽、高三个坐标来描述长方体,这样做的目的就是大大节省了加载页表所需要的空间 至此,有关于页表与三级页表的介绍就到这里了,页表的存在对于内核区与用户区加载代码起了至关重要的作用,真正理解页表的转换机制有助于我们对操作系统的虚拟内存有更深刻的认识
二.影子页表 (Shadow page table) 影子页表我用一句话来描述就是:VMM把Guest和Host中的页表合并成一个页表,称为影子页表,来实现GVA->HPA映射。 2, GPA -> HVA,这一过程由VMM软件实现的,这个很容易理解,就是通用的malloc。 4, 把GVA -> HPA,这一路的映射关系记录到页表中,这个页表就是影子页表。 虚拟机页表和影子页表通过一个哈希表建立关联(当然也有其他的关联方式),客户机操作系统把当前进程的页表基址载入PDBR时而VMM将会截获这一特权指令,将进程的影子页表基址载入客户机PDBR,使客户机在恢复运行时 硬件层面引入EPTP寄存器,来指向EPT页表基地址。Guest运行时,Guest页表被载入PDBR,而 EPT 页表被载入专门的EPT 页表指针寄存器 EPTP。
一、配置内核 首先配置内核,使其支持导出内核页表到debugfs下面: Kernel hacking ---> ---> [*] Export kernel pagetable layout to 地址空间port属性说明 第一列 当前页表的映射范围地址 第二列 代表此映射范围大小 PMD PUD PTE 当标识为PMD PUD表示当前映射为block映射,如当前页表为4K,则pud的block映射一次性可映射 1G范围,pmd的block映射可一次性映射2M范围。 当标识为PTE表示为页表映射即PAGE_SIZE大小4K。 USR AP标记,用于标识当前范围是否在用户空间还是内核空间可读可写或者仅读。 x表述当前范围特权级别模式可执行,就是内核的可执行代码段,在内核中这段一般指向内核的text*段 SHD 表示可共享属性,在arm64上表述为多核之间可共享其页表可见 AF 访问标志,当首次映射页表时,
,如果只使用了一个页表,一个表项的大小为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,这样一下子就比只有一级页表时少了。
内核知识第八讲,PDE,PTE,页目录表,页表的内存管理 一丶查看GDT表. 首先我们的CR3寄存器保存了表的首地址. 这里有一个页目录表,还有页表的关键词. 页目录表: 也称为PDE,而页表称之为PTE. CPU会通过虚拟地址,当作下表.去页目录表中查询.然后查到的结果再去页表中查询.这样就查到对应的物理地址了. PDE表的大小: 页目录表,存储在一个4K字节的物理页中,其中每一项是4个字节.保存了页表的地址. 而最大是1M个页. PTE表的大小. PTE的大小也和PDE一样的. 但是通过两个表查询.可以映射4G内存.而上面的设计方法不行. 首先前边20位保存了页表或者物理地址的基地址. 比如我们的页目录表. 查到了第5项.那么从中取出千20位来,加上000就等于页表了.
通俗解释进程-科学家做蛋糕 科学家做蛋糕 然后女儿被蜜蜂蛰了 进程表–在内核 内存管理 经典 老式 管理方法: 基址寄存器(程序开始的地方) + 界限寄存器(程序长度) 空闲内存管理 每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从0开始 将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面”。 操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。 各个页面不必连续存放,可以放到不相邻的各个页框中。 重要的数据结构——页表 为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表。 注:页表通常存在PCB中 一个进程对应一张页表 进程的每个页面对应一个页表项 每个页表项由“页号”和“块号”组成 页表记录进程页面和实际存放的内存块之间的映射关系
32bit虚拟地址的高12bit(bit[31:20])作为访问一级页表的索引值,找到相应的表项,每个表项指向一个二级页表。 我们从ARM linux内核建立具体内存区间的页表映射过程中来看页表映射是如何实现的。 首先通过init_mm结构体得到页表的基地址,然后通过addr右移PGDIR_SHIFT得到pgd的索引值,最后在一级页表中找到页表项pgd指针。 所以pgd_offset()在查找pgd表项时,是按照pgd[2]长度来进行计算的,因此查找相应的pgd表项时,其中pgd[0]指向第一份PTE页表,pgd[1]指向第二份PTE页表; pte_offset_kernel ,注意ARM Linux中实现了两份页表,硬件页表的地址r0+2048。
---- 为了提高内存空间利用率,页应该小,但是页小了页表就大了… 对于32的操作系统来说,用于寻址的位数是32位,那么最多可以表示2的32次方大小的地址,也就是4G。 4G大小能够存放多少页呢 ? ---- 页表会很大,页表放置就成了问题… 页面尺寸通常为4K,而地址是32位的,所以32地址能够表示2的20次方个页面。 对于下面给出的例子而言: 假设某个程序的页表如下,虚拟页号为2的一栏为空,是因为当前程序没有使用到该虚拟页,那么很自然我们就想到了删除掉这一行记录。 如果页表中表项是连续的,没用到的虚拟页号也会保留的话,那么定位到具体的表项只需要额外一次访存即可 32位地址空间+ 4K页面+页号必须连续 ===> 2^20个页表项 ==>大页表占用内存,造成浪费 对于逻辑地址(虚拟地址)而言,它的前10位表示的是页目录号,即最多可以表示2^10 个页目录,随后是10位的页号,表示一个页目录下最多存放2^10个页表项。
本篇文章谈一下 C 语言中的指针数组和 CPU 的页表的类比。 0x01:C 语言中的几个简单概念 以前学习 C 语言的时候,有一些概念好像很绕,但是仔细想想,与其说是绕,不如说是语文的理解能力有限。 上图的分页是按照 4K 进行分配的页的结构,还有按照 2M 进行分配的页的结构,还有按照 1G 进行分配的页的结构。这些名词、概念……虽然上面的这些概念很吓人,但是实质就是多级的数组指针数组。 0x03:指针数组和页表的类比 CPU 的分页机制其实和 C 语言的指针数组几乎就是同一个东西,其实就是同一个东西。除了像是指针数组外,如果用过在 CE 下找基址的话,其实我感觉也和这个很类似。 页表的整个层级只有 4 层吧,就是 4 级页表,主流的 Linux 好像也是四级页表吧。不过,新版的 Linux 中要更新到 5 级了~! 比如 C 语言的指针、汇编的寻址、上面提到的页表之类的。
4KB = 2^{12}B,因此页内地址要用12位表示,剩余20位表示页号。因此,该系统中用户进程最多有220页。 相应的,一个进程的页表中,最多会有220 = 1M = 1,048,576个页表项,所以一个页表最大需要220*4B=2^{22}B,共需要2^{22}/2^{12}=2^{10}个页框存储该页表。 根据页号查询页表的方法:K号页对应的页表项存放位置=页表始址+K*4要在所有的页表项都连续存放的基础上才能用这种方法找到页表项 需要专门给进程分配2^{10}=1024个连续的页框来存放它的页表 同时根据局部性原理可知 因此没有必要让整个页表都常驻内存。 问题一:页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框。 个页表项,因此每1K个连续的页表项为一组,每组刚好占一个内存块,再讲各组离散地放到各个内存块中) 另外,要为离散分配的页表再建立一张页表,称为页目录表,或称外层页表,或称顶层页表 问题二:没有必要让整个页表常驻内存
Innodb数据页简介(2) 01 概念介绍 3月17号的文章里面,我们提到了innodb的数据页结构,我们知道,页是内存和磁盘交互的基本单位,它的大小一般是16KB,可以被分为如下几个部分: 如果Free Space中的数据页被分配完了,则去申请新的数据页。 为了方便理解,我们现在创建一个表进行演示: CREATE TABLE test( -> c1 INT, -> c2 INT, -> c3 VARCHAR( 'a'), (2,3,'bb'), (3,4,'ccc'), (4,5,'dddd'); 我们可以把上面的数据页结构简单表示如下: ? 当我们对数据记录中id=2的一条记录进行删除时,实际上,在数据记录链表里面发生的变化如下: ?
前面的前奏已经分析介绍了建立内核页表相关变量的设置准备,接下来转入正题分析内核页表的建立。 建立内核页表的关键函数init_mem_mapping(): 【file:/arch/x86/mm/init.c】 void __init init_mem_mapping(void) { unsigned goto repeat; } return last_map_addr; } kernel_physical_mapping_init()是建立内核页表的一个关键函数, 最终建立内核页表的同时,完成内存映射。 至此,内核低端内存页表建立完毕。
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的虚拟地址空间。 如果此时访问这2M虚拟地址,则直接1:1访问到物理地址上去了。 当然,如果想将这个地址映射的更加具体,也就是将这2M的空间,映射成4K,那就需要第三级的页表项来映射。 现在我们用树莓派3B来举例。
,9 bit 一级索引找到二级页表,9 bit 二级索引找到三级页表,9 bit 三级索引找到内存页,最低 12 bit 为页内偏移(即一个页 4096 bytes)。 ,还会把页表内所有的叶节点对应的物理页也释放掉。 // 递归释放进程独享的页表,释放页表本身所占用的空间,但**不释放页表指向的物理页** kvm_free_kernelpgtbl(p->kernelpgtbl); p->kernelpgtbl 要实现这样的效果,我们需要在每一处内核对用户页表进行修改的时候,将同样的修改也同步应用在进程的内核页表上,使得两个页表的程序段(0 到 PLIC 段)地址空间的映射同步。 // 将 src 页表的一部分页映射关系拷贝到 dst 页表中。 // 只拷贝页表项,不拷贝实际的物理页内存。
,而是使用L1,L2,L3页表这种术语。 中间的8个bit位叫做L2索引,在Linux内核中叫做PT,页表。最低的12位叫做页索引。 在ARM处理器中,TTBRx寄存器存放着页表基地址,我们这里的一级页表有4096个页表项。 二级页表通常是动态分配的,可以通过虚拟地址的中间8bit位L2索引访问二级页表,在L2索引中存放着最终物理地址的高20bit位,然后和虚拟地址的低12bit位就组成了最终的物理地址。 0~11 :页索引 bit 63 :页表基地址选择位,ARMV8架构中有2两个页表基地址,一个用于用户空间,一个用户内核空间。 L0的页表项包含了下一级L1页表的基地址,同样的,可以使用L1索引的值作为offset去访问L2页表。以此类推。
而在Linux中存储虚拟地址到物理地址转化的关系的表称为页表。 目前最新的linux内核已经支持了5级页表。下图是一个4级页表的转化关系图。 ? Table Entry) 页表 如果是5级页表的话,会在PGD和PUD之间增加一个level叫P4D。 LINUX目前是支持5级页表,当然也可以通过config(CONFIG_PAGE_LEVELS)去配置的,目前手上的模拟板使用的是三级页表,如果使用三级页表的话,PUD等于PMD。 前期条件是目前配置的是3级页表。 目前此地址是线性地址,转化关系比较简单。 PMD_SHIFT ==> 页中级目录索引的偏移 PAGE_SHIFT ==> 页表内的偏移 当前模拟板是只有三级页表,则就没有P4D和PUD,这样的话PGD=PMD了。
在上面的示例中,顶级页表页具有条目0和255的映射。 条目0的下一级只映射了索引0,该索引0的下一级映射了条目0、1和2。 您的代码可能会发出与上面显示的不同的物理地址。条目数和虚拟地址应相同。 修改struct proc来为每一个进程维护一个内核页表,修改调度程序使得切换进程时也切换内核页表。 对于这个步骤,每个进程的内核页表都应当与现有的的全局内核页表完全一致。 不要忘记在userinit的内核页表中包含第一个进程的用户页表 用户地址的PTE在进程的内核页表中需要什么权限? //vm.c void // 复制进程想用户态页表到内核态页表中 u2kvmcopy(pagetable_t pagetable, pagetable_t kernelpt, uint64 oldsz, (kernelpt, i, 1)) == 0) panic("u2kvmcopy: pte walk failed"); //获取用户态页表中虚地址对应物理地址 uint64
遍历news数据库表里status==1的数据,然后遍历出来(列表页) 点击哪一个列表页中的某一个列表,通过$news->id可以获取到是哪一个 然后detail接收id数据,在遍历详情页即可.
并且页目录和页表“们”自身,都占用一个物理页的空间,所以它们都有自己的物理地址。 二级查表:构造线性地址的中间 10 位,来确定“普通页”的物理地址 二级查表:查找的对象是页表,也就是一级查表得到的那个“页表”。 虽然一级查表的结果是页目录自己,但是处理器不管这些,它会把这个表当做页表来使用。 现在,来考虑线性地址addr的中间10位,它决定了页表中的索引号。 对页表进行寻址 既然已经弄明白了操作系统是如何操作页目录的,那么对页表的操作就不是什么大问题了。 二级查表 利用这个页表的最后一个表项(index = 1023),预先填写一个地址(0x08000),让它指向这个页表自己的开始物理地址。