1. 虚拟地址到物理地址(内存虚拟化)
假设一个堆的基地址为34KB,虚拟地址为4KB,其大小为2KB
当 程序不分段时 ,找到堆中虚拟地值的物理地址很简单,物理地址 = 基地址 + 虚拟地址
当 程序分段时 ,找到堆中物理地址会复杂一些,物理地址 = 基地址 + (虚拟地址 - 该段的开头的虚拟地址)
举个例子,堆中有一个虚拟地址为4200,那么如果想得到其物理地址,需 34KB + 4200 - 4 KB = 34920
你可能好奇为什么要这么做,我们来简单解释一下:
首先我们先明确,之所以使用虚拟地址是想让程序以为自己独占内存,也就是说程序所占内存是从0 - xxx。虚拟地址是多少,就表示其在第多少个内存空间
当不分段时:整个程序的内存空间连续(无论是程序以为的内存空间还是物理内存都是连续的),所以虚拟地址即表明了其是第几个内存空间。显然 物理地址 = 基地址 + 虚拟地址
当分短时:整个程序的内存空间不再连续,每一段都有自己独特的基地址,但是虚拟地址还是相对于之前只有一个基地址时的值,那么此时虚拟地址就无法直接表示其在第几个内存空间了(因为程序以为的连续内存空间映射成的物理内存并不连续)。所以,我们需要虚拟地址相对于每个段自己的基地址的值,要完成这个操作只需要将虚拟地址 - 段开头的虚拟地址。因此 物理地址 = 基地址 + 虚拟地址 - 段开头的虚拟地址
2. 逻辑地址转换物理地址公式
物理地址是明确的、最终用在总线上的编号。那么逻辑地址转物理地址怎么转?我为大家介绍逻辑地址转物理地址的解决 方法 。希望大家喜欢。
逻辑地址转换物理地址公式参考如下
1. 物理地址和逻辑地址
物理地址:加载到内存地址寄存器中的地址,内存单元的真正地址。在前端总线上传输的内存地址都是物理内存地址,编号从0开始一直到可用物理内存的最高端。这些数字被北桥(Nortbridge chip)映射到实际的内存条上。物理地址是明确的、最终用在总线上的编号,不必转换,不必分页,也没有特权级检查(no translation, no paging, no privilege checks)。
逻辑地址:CPU所生成的地址。逻辑地址是内部和编程使用的、并不唯一。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址(偏移地址),不和绝对物理地址相干。
(具体步骤 共三步)
1.确定虚拟地址(物理地址)的有效位
例如:假设页面大小1KB,共32页。(页面:逻辑地址 页框:物理地址)
由32(KB)=32×1024(B) 即等于32×1024 字节
二进制用多少位能有效表示这么多字节呢——答是:15位 因为32×1024=2^5×2^10=2^15
2.再次确定逻辑地址页面位数 你应该知道:逻辑地址=页号+页面
还是以上假设,那么页面大小为1KB=1024字节 同样的方法计算出表示位数:10位
如果给你逻辑地址:0000 1111 1000 0000
那么由:011+11100000000(相当于 页号+页面(10位))推得出页号011=3
3.根据页号找出对应的页框号
由 物理地址=页框号×页块大小(页块大小是等于页面大小的)+页内位移(即页面逻辑地址)
根据上面 物理地址=页框号×1024B + 1110000000 ( 这里的相加是指位置上而言)
例如:110+110=110110(即高地址+低地址)
提问:在一分页存储管理系统中,逻辑地址长度为16位,页面大小为4096B,现有一逻辑地址为2F6AH,且第0、1、2页依次存放在物理块5、10、11中,问相应的物理地址为多少?
答:4096B=2^12B
16位寻址一共2^16B
分页存储。共分的页:2^16/2^12=2^4=16 共分16页。
第0页的地址范围 0 - FFFH
第1页的地址范围 1000H - 1FFFH
第2页得地址范围 2000H - 2FFFH
.....
第11页 B000H - BFFFH
第15页 F000H - FFFFH
2F6AH=10 1111 0110 1010 在2页的范围对应物理块11
所以物理地址为:
2F6AH - 2000H + B000H = F6AH + B000H= BF6AH
3. 科普 VT、EPT
硬件虚拟化,到底什么意思,虚拟化什么东西。VT 其中一个功能虚拟化内存。虚拟化内存什么意思,比如你的 PC 物理电脑实际上只有 8G 物理内存,但是用了虚拟化内存这个功能后,可以让你的操作系统(这里就用WINDOWS来代替操作系统)使用超过 8G 的物理内存。可以把你的物理内存隐藏起来让别人访问不到,包括操作系统也访问不到,当然也包括杀毒软件访问不到或者访问到的是你伪造的内存。 进一步讲,可以让你一台PC物理机同时运行多个操作系统,就像VMWARE一样。多个操作系统之间的物理内存是隔离的。
其实简单点讲就是,1 号操作系统访问物理内存0X12345678 和 2 号操作系统访问物理内存 0X1245678 里面的内容可以不一样。 这是怎么做到的? 其实原理挺简单的。在 WINDOWS 里面为什么多个进程访问同一个地址,但是里面的内容可以做到不一样,两个进程都执行 mov eax,0x40001000,然而取出来的地址会不一样。
为什么能做到这点,其实执行这条汇编指令的时候,0x40001000这个地址并不是真正的物理地址,那么真正的物理地址是什么呢,真正的物理地址是需要经过MMU(是个硬件)转换过的。怎么转换的呢,http://bbs.pediy.com/showthread.php?t=203391&highlight=物理+理地+地址------大家可以参考这篇文章。
其实 VT EPT 功能就是把物理内存虚拟化了。就是把经过虚拟地址转换过后的物理地址,还需要再经常 EPT 机制再转换一次,从而实现,物理地址的虚拟化。
下面说下这是怎么做到的。INTEL 是怎么设计的。虚拟地址转换成物理地址,我们都知道是经过 CR3 所指向一块内存(可是理解成数组),多大 4096 字节。(为了便于新手理解其他页面大小这里暂不考虑)。其实 EPT 机制里面也有个类似 CR3 的寄存器,就是这个功能,开始转换地址的首地址 EPTP。这个 EPTP 大家可以简单理解成 CR3 寄存器。这个虚拟地址到物理地址的转换是在后台,硬件偷偷在后台的完成的,我们完全感觉不到,当然我们的虚拟机的物理地址转换成真正的物理地址也是在硬件层次,后台偷偷完成的。我们完全感觉不到。但是怎么完成的。其实它是通过查表完成的。关键就在于这个表,我们把这个表初始化好了,构建好了,CPU 就可以自动后台查找这个表运行转换功能了。
先看下这个表要怎么构建,CPU 会怎么去用这个表,EPT 扩展页表的功能。我们目前要做的就是把这个 EPT 给构建起来,啥功能都没有,什么意思呢,就是物理地址 0X87654321经过这个表转换之后还是变成 0x87654321(当然想变成什么地址你可以自己构建)。这里我都是以 64 位系统为例为讲的。
看下 INTEL 是怎么设计,怎么查找这个表的,首先硬件会从 EPTP 指向的一块内存开始,查找,这块内存你可以自己申请好 4096 字节,把这个起始地址,在 VT 初始化的时候赋值给 VT 的某个字段。因为是 64 位,所以是 512 项,一项占八个字节,相当于 ULONG64 EPTP[512-1] 里面有 512 项,每一项都是一个地址,这个地址指向另外一块内存,当然这块内存也是 4096 字节,里面也是 512 项的 64 位地址,就这样总共下去有四层,数组里面的每一项指向另外一块数组的首地址。
反回来再说说,到底是取这个数组的哪一项呢,INTEL 是这么设计的,把 64 位的物理地址分成 5 段,0-11位算一段,12-20位算一段,21-29位算一段,30-38位算一段,39-47位算一段。我们这里先无视0-12位,其他四个段都是9位组成,四个段,刚好对应四层表,就是说,每一段独立取出来,当作这个数组的页表的序列。看 INTEL 手册吧,看书的时候,感觉书上写的不清楚,论到自己写的时候,才感觉,要把这个意思表达出来还真不容易,
先把上面 0x87654321 转换成二进制 ,再把这段二进制分成五段,怎么分,从低位12,9,9,9,9多余的位暂且无视,感觉用视频讲解会好很多,这里我试着尽量用文字表达清楚。
000000000 000000010 000111011 001010100 001100100001 分成这样五段,然后再把这五段二进制分别独立转换成 十六 进制0 0x2 0x3B 0x54 0x321 我们先看前面四段,0 2 3B 54 先看这个零代表什么意思,零就代表上面我们申请的内存页的第一项,就是EPTP 首地址里面的第一项的内容。取出里面的内容,然后看图
这个地址 fffffa80`01878000就是我申请的 4096 字节大小的内存的首地址,你会发现只有第一项有内容,其余的零,因为我们实际物理内存并没有那么大,所有只需要填下第一项就行了。第一项里面的值也是一个地址,这个地址也是一个页 4096 字节大小的首地址,继续看图
图里可以看出也是有 512 项(注意这里存放的都是物理地址,然后无视最后位的数字 7,看作零,7 代表什么意思我等下再说),每一项又指向一块 4096 大小的内存的首地址,可以看出我只初始化了 8 项,这 8 项代表 8G,8G是怎么算出来的,其实这里的每一项代表一个G的物理内存。
先了解虚拟地址怎么转换成物理地址的就可以看懂我发的图了。
4. 操作系统中逻辑地址转物理地址是什么
1、确定虚拟地址(物理地址)的有效位。
2、再次确定逻辑地址页面位数你应该知道:逻辑地址=页号+页面。
3、由物理地址=页框号×页块大小(页块大小是等于页面大小的)+页内位移(即页面逻辑地址)
4、根据上面物理地址=页框号×1024B+1110000000。
5、若在一分页存储管理系统中,某作业的页表如下所示。已知页面大小为1024字节,试将逻辑地址1011,2148,4000,5012转化为相应的物理地址。
分析页式存储管理的地址结构是一维的,即逻辑地址(或物理地址)只用一个数值即可表示。若给定逻辑地址A,页面的大小为L,则页号p和页内地址d可按照下式求得:
p=int[A/L]d=AmodL
其中,int是取整函数(取数值的整数部分),mod是取余函数(取数值的余数部分)。
5. 分页,虚拟地址是怎么转换成物理地址的
虚拟地址(即图中的逻辑地址)的高位表示页号,由计算机硬件将页号取出,且和页表寄存器中的页表始址一起送加法器,就可以得到该页对应的页表项的地址,根据此地址到内存读出对应的块号,最后将块号和页内地址拼接得到对应的物理地址。
6. PGD 功能解析
VPN :virtual page number.
PPN :physical page number.
PTE :page-table entries.
ASID :address space identifier.
PMA :Physical Memory Attributes
PMP :Physical Memory Protection
PGD :Page Global Directory
PUD :Page Upper Directory
PMD :Page Middle Directory
PT :Page Table
TVM :Trap Virtual Memory
4KB 的内存页大小可能不是最佳的选择,8KB 或者 16KB 说不定是更好的选择,但是这是过去在特定场景下做出的权衡。我们在这篇文章中不要过于纠结于 4KB 这个数字,应该更重视决定这个结果的几个因素,这样当我们在遇到类似场景时才可以从这些方面考虑当下最佳的选择,我们在这篇文章中会介绍以下两个影响内存页大小的因素,它们分别是:
每个进程能够看到的都是独立的虚拟内存空间,虚拟内存空间只是逻辑上的概念,进程仍然需要访问虚拟内存对应的物理内存,从虚拟内存到物理内存的转换就需要使用每个进程持有页表。
在如上图所示的四层页表结构中,操作系统会使用最低的 12 位作为页面的偏移量,剩下的 36 位会分四组分别表示当前层级在上一层中的索引,所有的虚拟地址都可以用上述的多层页表查找到对应的物理地址 4 。
因为操作系统的虚拟地址空间大小都是一定的,整片虚拟地址空间被均匀分成了 N 个大小相同的内存页,所以内存页的大小最终会决定每个进程中页表项的层级结构和具体数量,虚拟页的大小越小,单个进程中的页表项和虚拟页也就越多。
因为目前的虚拟页大小为 4096 字节,所以虚拟地址末尾的 12 位可以表示虚拟页中的地址,如果虚拟页的大小降到了 512 字节,那么原本的四层页表结构或者五层页表结构会变成五层或者六层,这不仅会增加内存访问的额外开销,还会增加每个进程中页表项占用的内存大小。
PGD中包含若干PUD的地址,PUD中包含若干PMD的地址,PMD中又包含若干PT的地址。每一个页表项指向一个页框,页框就是真正的物理内存页。
PGD: Page Global Directory
mm_init() ---> fork.c 文件 ,源码如下:
mm_init() 函数调用 mm_alloc_pgd() 函数与底层物理内存产生关系, mm_alloc_pgd() ---> fork.c 文件
pgd_alloc() ---> paglloc.h 这个函数为当前 pgd 分配一个 page ,并且将当前的 page 的首地址返回,并且将内
核GPG拷贝的当前进程的结构体中。函数中调用了 __get_free_page() ,获取一个空间的物理页保存当前进程信息, __get_free_page() 就是Kernel常用的 __get_free_pages() ,这样子上层进程创建就与底层物理内存产生直接的关系,以上几个函数源码如下:
init_mm() ---> init_mm.c 结构体记录了当前 root table 的所有信息, swapper_pg_dir 是存放PGD 全局信息的全局变量,源码如下在 init_mm.c 文件中,源码如下:
这样一来,每个进程的页面目录就分成了两部分,第一部分为“用户空间”,用来映射其整个进程空间 (0x0000 0000-0xBFFF FFFF) 即3G字节的虚拟地址;第二部分为“系统空间”,用来映射 (0xC000 0000-0xFFFF FFFF)1G 字节的虚拟地址。可以看出 Linux 系统中每个进程的页面目录的第二部分是相同的,所以从进程的角度来看,每个进程有 4G 字节的虚拟空间,较低的 2G 字节是自己的用户空间,最高的 2G 字节则为与所有进程以及内核共享的系统空间。每个进程有它自己的 PGD( Page Global Directory) ,它是一个物理页,并包含一个 pgd_t 数组。
An Sv32 virtual address is partitioned into a virtual page number (VPN) and page offset, as shown in
Figure 4.15.
satp寄存器的组成:
虚拟地址转换为物理地址转换过程如下:
每一个应用程序都有自己的Page Global Directory(PGD),其保存物理地址的页帧,在<asm/page.h>中定义了pgd_t 结构体数组,不同的架构有不同的PGD加载方式。
A virtual address va is translated into a physical address pa as follows:
当虚拟地址没有映射物理地址,最典型就是用户态 Malloc 一段虚拟地址后, Linux 并没有为这段虚拟地址分配物理地址,而是当用写这段虚拟地址时, Linux Kernel 发生 PageFault 才会为这段虚拟地址映射物理内存,大概的过程就是这样,但是其中 Linux Kernel 产生缺页异常到映射物理的过程则是非常复杂的一个过程,其中涉及到很重要的一个函数就是缺页中断服务函数,在 RISC-V 中叫 do_page_fault() 在 arch/risv-v/mm/fault.c 文件中定义了该函数。
do_page_fault() 函数实现如下:
7. 现代CPU如何自动把虚拟地址转换成物理地址的硬件电路
虚拟内存是一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。
每个字节都有一个唯一的地址,就是虚拟地址。通常,虚拟地址由页号和偏移量组成,页号就是抽象的虚拟页的编号,偏移量用于计算实际的物理地址。
虚拟地址和物理地址的关系。进程虽然使用虚拟地址,但是用数据时还是要到实际的物理地址去取数据。这就存在一个虚拟地址到物理地址的转化运算,这是由CPU芯片上一个叫做内存管理单元(MMU)的专用硬件来实现的。
通常,物理地址=页号*页大小+页内偏移量。虚拟寻址CPU通过虚拟地址来访问主存,访问内存使用的物理地址,MMU通过将虚拟地址进行翻译,转化为物理地址,然后再用这个物理地址去访问内存数据。
8. 通过虚拟地址计算物理地址 求过程
你打的太多了,有点乱,只说下地址转换问题:
1.虚拟地址:虚拟地址是以"段寄存器:偏移地址"形式存在的,例如--0542:24521360
2.线性地址:它是由分段部件把虚拟地址转化而来的.
3.物理地址:即真实存在的地址,由处理器的地址引脚寻找到的地址.
虚拟地址---->线性地址:
段寄存器是一个16位的寄存器,其中第0和1位控制着将要访问段的特权级,第2位说明是在gdt还是ldt寻找地址.高13位作为一个索引值,总共8192个索引.假设段寄存器-0000
0000
0000
1011(000b),那么我们可以知道rpl=3(特权级为3);ti=0,从gdt中选择段描述符;index=1,即将要索引的段描述符在gdt中的顺序号为1,由于一个段描述符占8个字节,所以其索引到的地址为"gdt的高32位+1*8".这也就是为什么gdt48位,留最低的16位作为限长的原因(8192*8=64k).
找到了段描述符,然后就是从段描述符中找出该段的位置了.段描述符是个8字节的内存空间,由于结构复杂,无法构图,省略段描述符的结构.我们只要知道在里面规定了该段的基址,限长,还有属性等等.找出基址后,再加上虚拟地址的偏址,就形成了32位的线性地址.由于偏址是32位的,所以该段独享4g的虚拟地址空间.
线性地址----->物理地址
该部分是由分页部件通过3级查找完成的.此时,我们把线性地址分为3段:0-11位(c)字节索引,12-21位(b)页表索引,22-31位(a)页目录索引.我们把页表描述符和页描述符通称为页表项,页表项占4个字节,总共占4kb大小.先以cr3为基址,以(a*4)为索引值,寻址页目录描述符.然后再以页目录地址的高20位地址为基址,以(b*4)为索引值,寻址页描述符.再以页描述符的高20位地址为基址,以c为偏移地址,相加得到物理地址.
从上可以看到页的大小是4kb,即一项任务cpu只调用该任务所占内存空间的4kb大小.有利于减少内存占用.
以上大体就是这样的,其中分页部件的转换相当复杂,不是三言两语就能说明白的.还有pentium之后,分页部件又采用了4mb的页面,线性地址采用2级寻址.才开启pae功能后,又形成了4级寻址.然后再结合后面的内存保护,i/o保护,任务保护及特权级的变换,形成了保护模式的大部分内容.
太复杂了,我也不是十分会.写的有些乱,但愿你能明白些.
9. 关于内存管理和地址转换的小小小小小总结
因为在ipad上画图比较好操作,这篇笔记就直接上传手写版了。把线性地址到物理地址部分的转换理了一下,以后有补充会做更新。
四级页表的作用主要就是地址映射,将逻辑地址映射到物理地址。
ARM MMU的地址转换过程实际上更加复杂,通过两级页表实现,转换方式有两大类共四种情况,具体的可以看这篇博客 https://blog.csdn.net/sinat_41104353/article/details/82778822
已知系统使用IA-32分页,现知道一个虚拟地址0x10036270,需要将该虚拟地址转换为物理地址。若已知CR3寄存器中的值为0x7401000,转化的过程如下:
1. 虚拟地址为0x10036270(00010000 00000011 01100010 01110000)
22-31bit为PDI值(00 0100 0000),12-21bit为PTI值(00 0011 0110 ),0-11bit为地址偏移(010 0111 0000)
2.页目录项PDE的地址=PDI×4+PDB(CR3)=0x40×4+0x740100=0x7401100
3.知道PDE物理地址后即可知道该物理地址中存储的值,比如假设该物理地址存储的值为0x28cf9067。PTE的值由PDE值的12-31bit及虚拟地址的12-21bit构成(0-11bit根据12bit填充为0),可得到PTE的物理地址=0x28cf9058
4.假设该物理地址中的值为0x182a7071,物理地址的值由PTE值的12-31bit及偏移地址构成。
最终得到物理地址=0x28cf9000+0x270=0x28cf9270。
以上为IA-32分页虚拟地址转物理地址的过程。
关于虚拟地址到物理地址的转换
由于在内存中存储的一般是虚拟地址,而在物理内存中地址定位的一定是物理地址,因此计算虚拟地址(线性地址)到物理地址的映射关系是内存分析的关键。
虚拟地址到物理地址的映射计算需要使用到一个基本规则: 在同一个虚拟地址页面上的内容,也在同一个物理页面。
比如,在物理内存管理中,页的大小一般为4KB、2MB、4MB,都大于或等于0x1000(4KB)。根据上述的规则,虚拟地址0xffdff000-0xffdfffff就应该映射到同一个物理页面上。而计算系统的页目录基地址是计算内存映射的关键,如果在0xffdff000-0xffdfffff中找到指向系统页目录基地址的指针就会解决地址映射的问题。
在上部分的笔记中能看到,CR3寄存器是非常重要的一个寄存器,它记录的是页目录基地址(或页目录指针基地址、或PLM4基地址),如果能得到CR3寄存器的内容,那么就有可能得到现成的页目录基地址。
这里以《内存取证原理与实践》的例子,先大概描述一下 利用CR3的虚拟地址找到其物理地址的方法 。
以64位win7操作系统为例,_KPRCB 的结构成员ProcessorState是一个_KPRROCESSOR_STATE结构,起始地址为0xfffff80045eff80+0x40,在0x0处是SpecialRegister成员,偏移0x010处就是CR3寄存器,它的虚拟地址为0xfffff80045eff80+0x40+0x10。
而根据上述提到的基本规则我们可以知道,它和0xfffff800045efe00在同一个页面中,那么所以它的物理地址= 0xFFFFF800045EFE00的物理地址+0x180(这两个地址的差值)+0x40+0x10。
关于页的分页方式和页的大小则由以下过程确定:
1. 根据CR3寄存器的内容找到它指向的物理地址。
2. 判断该地址处的第一个字节,如果不是0x01则跳转至第三步,否则表明其使用了PAE模式,从这个地址开始的8byte是页目录指针。根据待转换的虚拟地址的第31~30 bit选择页目录指针。例如,如果待转换的地址是0x8054c2b8(10000000 01010100 11000010 10111000),则页目录指针表的第三项(二进制10)为指向页目录的指针,根据这个指针可找到页目录基地址。
根据页目录基地址和虚拟地址的第21~20bit确定待转换虚拟地址对应的页目录项。例如,如果待转换的地址是ox8054c2b8,则第21~29bit是000000010(0x02),则从页目录基地址加上8×2开始的8个字节就是所找的页目录项。
3. 判断该地址处的第一个字节最高位,如果是“1”,则表明使用的大页模式;如果是“0”,则表明它指向页表。