首页
社区
课程
招聘
[原创] Windows 7 x64 虚拟内存管理 第一部分笔记
2022-2-25 16:39 24347

[原创] Windows 7 x64 虚拟内存管理 第一部分笔记

2022-2-25 16:39
24347

Intel 架构基础

1 简介

​ 第一部分主要涉及Intel架构的基础知识,不涉及Windows相关的内容。如果你熟悉Intel架构,那么可以跳到第二部分。详细的内容可以参考Intel白皮书。

2 权限级别

​ 处理器拥有一种叫做当前权限级别(CPL)的内部状态,用于决定某些特定的操作是否被执行。

 

ring0~ring3共定义了4个级别,ring0的级别是最高的。

 

Windows只使用了CPL 0CPL 3,因此我们只讨论这两个级别。

 

​ 处理器在这两个级别上的影响如下:

  1. 特定指令只能在CPL 0执行
  2. 特定内存区域配置为只能CPL 0 下被访问
  3. I/O 端口配置为仅在CPL 0下可以被访问

​ 一个执行在CPL 3的程序,如果尝试去执行上面禁止的操作,那么会触发异常。简单的说就是处理器会跳到一个操作系统提供的函数地址里去处理这种情况,最终被禁止的操作不会被执行。比如说程序尝试写被保护的内存位置,那么该内存区域的内容并不会改变。

 

​ Windows操作系统上,应用程序代码和操作系统的一部分执行在CPL 3CPL 0只用于执行操作系统代码,当然也包括第三方操作系统扩展,比如设备驱动。

 

 

​ 我们经常用环来代表级别,因为他很形象的显示了ring 0的保护最强,ring 3的保护最弱,越往内保护越强。处理器在ring 0 就代表他的CPL是0。

2.1 权限级别的转换

2.1.1 权限控制指令

CPL的切换是通过特定指令完成的。比方说syscallCPL 3切到CPL 0sysret相反。

 

syscall跳转的地址只能CPL 0 修改,所以应用程序不能修改操作系统代码的入口点。syscall指令会报错当前的指令指针riprcx寄存器,然后目标代码能将地址保存到内存的其他地方,之后sysret能正常返回。

2.1.2 硬件中断

​ 硬件中断发生后,一个执行ring 3代码的处理器将会跳到其他地址,然后设置CPL为0。中断发生后ripCPLcs寄存器持有)保存在栈上,之后iret指令从栈上加载ripcs后来。iret能从ring 0切到ring 3,但不能从ring 3切到ring 0

2.1.2 处理器异常

​ 处理器异常时,和中断类似,如果当前CPL是3,那么会跳转到一个不同的地址,然后设置CPL为0。异常是处理器自己产生的,由正在执行的指令引起的。例如div执行除0操作。和中断类似,ripcs保存在栈上。如果异常发生在ring 3,那么就会存在权限级别切换。

3 栈

​ 栈是由一些处理器指令隐式使用的地址范围,也会被处理器机制用到,比如说中断处理。处理器隐式访问栈是通过rsp寄存器进行的。

 

​ 举个例子,call指令调用函数时,处理器会执行以下操作:

  1. rsp减8
  2. 保存call指令后的地址到rsp指向的地址空间
  3. 修改rip到目标地址

​ 最终结果就是处理器跳到了一个新的地址,返回地址保存到了栈上,通过ret指令返回。ret指令会执行下面的操作:

  1. 从rsp寄存器读到返回地址到rip
  2. rsp加8

​ 当有新的值被写到栈上时,栈指针会减小。Windows上栈是向下生长的。当进程开始时,rsp由系统进行初始化。

 

​ 返回地址是返回到函数调用后的地址。其他隐式使用栈的指令还有pushpop等。

 

​ 栈的使用还涉及中断和异常。

 

​ 处理器不同的CPL下使用不同的栈。ring 0 栈和 ring 3栈分别用于CPL 0CPL 3

 

​ 中断和异常发生后的权限切换,rsp由处理器自动完成rsp切换。对于syscallsysret则由操作系统代码来完成rsp切换。

 

​ 0环的地址,3环是无法访问的。

4 中断和异常

4.1 中断

4.1.1 中断的处理

​ 中断时设备向芯片和处理器发送的电信号,以使后者执行一些与设备本身交互的代码。具体的例子是磁盘控制器发送了一个中断信号表示它完成了数据向内存的传输。我们把处理器对中断的响应行为称为中断处理或者中断服务。

 

​ 每一个硬件中断都关联一个中断向量号。处理器使用向量号作为索引去取得中断描述符表(IDT)。每个表项叫做门。中断门包含了地址和控制信息。总之一个硬件中断会导致处理器跳到对应的IDT项关联的地址中取执行。

 

​ 硬件中断发生时,处理器处在在ring 3的话,CPL会改为0,如果处理器已经在ring 0CPL就不会改变。

 

IDT只能在CPL 0修改。IDT里找出来的地址被称为中断处理例程。在启动阶段,操作系统加载中断处理到内存,初始化IDT表项。

 

​ 处理器收到一个中断后的执行流程如下:

  1. 完成当前指令的执行。中断服务是在指令边界上的。例如指令更新了处理器的寄存器或者内存的内容,那么在中断服务之前,这些更新都会被执行。
  2. 保存当前栈指针rsp和一个栈相关的寄存器ss
  3. 保存rflags寄存器到栈上,这个寄存器包含了处理器的状态信息。
  4. cs寄存器,这个寄存器存储了其他CPL和其他控制信息。
  5. 在栈上保存下一条指令的地址
  6. 如果处在ring 3,那么设置CPL为0
  7. 开始执行在IDT中找到的指令地址

上面的步骤形成的栈布局被称为中断帧。

 

4.1.2 中断返回

​ 中断返回是通过iret指令完成的。这条指令负责从栈上恢复原来的寄存器值。处理器的其他寄存器原来的值,则必须有中断例程执行iret返回之前进行恢复。

 

iret指令可以改变CPL从0到3。

4.1.3 栈切换上的权限级别变化

​ 中断帧是保存在内核栈上的。

 

​ 内核中栈地址无效的两个主要原因,一是地址不符合规范,二是该地址没有对应的物理地址。

 

​ 栈切换保证了代码执行在一个完好的新栈上,不受原来的栈的影响。

 

​ 那么rsp从哪里获取呢?处理器使用的是一个叫任务状态栈的数据结构(TSS)。TSS的初始化是由操作系统完成的,一般操作系统会让这个栈在内存中不能被ring 3访问到,所以应用程序无法篡改。

 

​ 正常来说,每一个处理器都有一个单独的TSS

4.2 异常

​ 异常和中断很类似,不过这是处理器在执行指令时自己产生的。为了避免和硬件中断向量号冲突,向量号0~31被保留为Intel专用。

 

​ 处理器产生异常后,会建立类似中断帧的栈布局,有一些异常还会额外保存错误码。每一个异常都有一个不同的IDT项,因此都会有自己的处理例程,也相应的决定了中断帧是否包含错误码。

 

​ 异常和中断的返回指令都是iret,如果栈上存放了错误码,那么异常处理例程在执行iret之前,必须自己调整rsp的值。

4.2.1 处理器异常 vs Windows异常

​ 不能将Windows异常和Intel架构定义的异常混为一谈。Windows异常是程序出错后系统代码产生的事件。Windows异常是操作系统实现的,他是操作系统定义的数据结构。处理器异常时类似中断的事件,用来响应无效指令。当然处理器异常也是Windows异常的一部分异常来源。比如说0xC0000005,访问异常。这意味着Windows ring 3发生异常后的恢复执行是由Windows系统代码完成的,Windows实现了自己的一套异常机制。iret不再是简单的重新加载存储在中断帧的状态。实际上,存储的状态由ring 0代码完成,因此当iret执行后,ring 3的程序神奇的恢复执行了。

4.2.2 异常的分类

4.2.2.1 错误

​ 错误产生是在指令执行之前,所以中断帧上的rip指向的就是导致这个错误的指令,被称为错误指令。当异常处理例程将错误的原因移除后,处理器将会重新执行这条错误指令,也许,现在就能执行成功了。

 

​ 众多的Intel定义的错误中有一个叫页错误的异常,当产生无效内存引用时将会产生这个错误。当一个程序引用的内存内容被移到页面文件里时,一个页错误将产生。VMM的工作就是获取页面的内容,然后重新执行这条错误指令。

4.2.2.1 陷阱

​ 陷阱的产生在指令执行之后,rip此时指向的是陷阱指令后边的指令地址。最常见的例子是调试陷阱,处理器在每一条指令上进行配置,然后实现调试器的单步执行。

4.2.2.1 中止

​ 中止是在多个错误条件被检测到后触发的,这时已经不能保证被中断的代码能恢复执行,因为rip可能已经不再指向导致异常的这条指令。中止的异常处理例程应该设法优雅地结束这个进程。

4.3 中断

​ Intel 架构定义了软中断。他实际上不是程序本身执行产生的中断。

4.4 软中断

int n指令会使处理器调用IDT表里指向的第n项的处理例程。这中调用时程序本身拥有的,因为int指令时他代码的一部分。

 

​ 这条指令实际上完成了一个间接调用,函数的地址存在对应的IDT项里。int n不会push 错误码到栈上。因为这条指令不能表达是否存在错误码,要么规定存在,要么规定不存在,intel选择了后者。int指令在ring 3无意义,因为IDT表项只能在ring 0访问。

4.4.1 处理器软中断 vs Windows 软中断

​ Windows定义了自己的软中断概念,他和处理器定义的完全不同。

5 分页

​ 分页是处理器内存管理单元提供的一系列功能,它实现了虚拟内存。拥有了分页以后,可以标记一个地址是无效的,当指令尝试访问这个地址,而该地址没有指向实际内存时,处理器会产生一个异常。异常处理例程会使地址有效,并恢复错误指令的执行。如果内存在哪里了,那么这条指令就会执行成功。

5.1 地址转换

​ 分页是基于地址转换的。代码执行中的每一个地址在实际引用内存前都会转换为一个不同的地址。地址转换不仅仅是存在于包含地址操作数的指令,栈指针里存放的地址也会被转换。

 

​ 所有引用内存的指令都会存在转换,不管是显式还是隐式。

 

​ 分页也可以被禁用。那么所有的地址就是实际的地址,不需要转换,直接引用内存。Windows启动运行起来后,总是启用的分页。

 

​ 通过上面的内容,我们定义了两种类型的地址,虚拟地址和物理地址。虚拟地址需要经过转换才能访问到内存里的东西。物理地址指向实际的内存内容。

5.2 编码地址形式

​ 由于64位的值包含的地址空间比较大,当前的x64处理器实际上将地址限制为了48位。简单地禁用48-63位使虚拟地址在256TB以下。32位的Windows使用了32bit的虚拟地址空间,然后对半分。但是这种方案在64位上不可行。解决的方案是64位系统上,虚拟地址都是47位。因此有效的地址空间访问就有两个:

  1. 0~0x7FF`FFFFFFFF
  2. 0xFFFF800000000000~0xFFFFFFFFFFFFFFFF

 

​ 通过使用这种方式,内存管理单元得到了简化,因为48-63位在地址转换中就不会被使用了。x64模式里使用的所有地址都是规范地址。不规范的地址访问将会触发页错误异常。

5.3 虚拟地址到物理地址的转换

​ 处理器使用了表来存放转换地址。如果一个虚拟地址不能被转换为物理地址,那么这个地址是无效的,反正有效。这就是有效地址无效地址

 

​ 尝试访问一个无效地址会触发页面错误异常。这个异常除了在栈上保存中断帧,还会保存错误码,用来表示访问类型:读取,写入或者指令获取。进一步的,cr2寄存器会设置代码尝试访问的地址。

 

​ 异常处理例程要么建立一个有效的转换,要么决定不允许这种访问,然后结束这个进程。

 

​ 处理器会使用下图的一些表来计算物理地址。

 

 

​ 转换的流程是这样的:

  1. cr3寄存器存储了物理地址的第一级表,这张表叫做PML4. PML4有4Kb大小,每一个表项是8字节大小,表项称为PML4Es。图中的其他表也是一样的大小和结构。
  2. 虚拟地址的39-47位用于索引PML4,然后取得PML4E。因为每一项是8 byte长度,索引偏移的起始向应该是index*8,或者说是index左移三位。
  3. PML4E存放了下一级表的物理地址,这个表称为PDPT
  4. 虚拟地址的30-38位用来索引PDPT项。PDPTE又存放了下一级表的物理地址。下一级表称为PD
  5. 虚拟地址的21-29位得到PDEPDE指向了最后一级表PT
  6. 根据12-20位得到PTEPTE存放了其他的物理地址。这个地址作为计算最后物理地址的基址。
  7. 0-11位就是offset,加上上一步得到的地址就是最后的物理地址。然后这个内存地址就被实际访问到了。

​ 我们引入分页结构(PS)代表上面提到的所有表。用PxE缩写来泛指上面提到的表项。分页结构是4kb,每一项是8byte长度,那么一个PS最多可以存储512项,因此虚拟地址拆分为9位来进行表索引的。根据设计,分页结构的起始地址必须是4kb的倍数。所以这些地址的0-11位是设置为0的。而这些位在转换过程中就可以被用来提供控制信息。这种要求也被应用到PTE里。

 

​ 第7步中,用到的offset是12位,因此范围就是2的12次方,也就是4KB大小。

 

​ 内存寻址的访问因此是4kb对齐和4kb大小的。我们称这个内存块为物理页。物理页的起始地址除以4k得到的数称为物理页号(PFN)。PFN是一个整数数值,第一个物理页面时 PFN 0,第二个是 PFN 1,依此类推。另外一个计算PFN的方法是物理地址右移12位。

 

​ 一个分页结构,考虑到其对齐和大小的要求,正好占据一个物理页。这使得操作系统能够以一致的方式管理内存分配,不管一个页面是用来存储程序代码和数据还是分页结构。

 

​ 虚拟地址除以4k然后向下取整得到的数,我们称为虚拟页号(VPN)。VPN包含了地址转换中用到的的所有位。地址的范围并不会影响转换到同一个物理页。这个范围是4kb对齐和4kb大小的,被称为虚拟页面。一个虚拟页面里所有的地址都会被转换到同一个物理页面,也就是说一个虚拟页面对应一个物理页面。

 

​ 现在我们可以用虚拟内存来指虚拟地址范围和他关联的内容。无效的地址的内容是未定义的。我们使用物理内存来指实际系统内存。

 

​ 有趣的是,英特尔的文件曾经将物理地址定义为在完成所有转换工作后,"出现在处理器的地址引脚 "的地址。当代的处理器集成了内存控制器,因此它们与内存芯片的互动使用的信号完全不同于地址总线,而且没有一组引脚可以实际观察到物理地址是一连串的高低电压水平。在这个意义上,物理地址已经不存在了。然而,即使在当代系统中,内存也被安排在由一个数字唯一标识的字节位置,即地址,无论硬件如何设计,处理器都必须使用这个地址来访问它们。在分页结构条目中发现的数值就是用于此目的的,它仍然被称为物理地址。

 

​ 使用不同的cr3寄存器,将会是完全不同的转换。PML4Es将会指向不同的PDPTs,他们的项又会指向不同的子结构,直到存储内存内容的物理页。

 

​ 改变cr3寄存器将建立一个新的地址空间,这个机制用来实现每一个进程的地址空间分离。

 

PML4项的映射地址是从39-47开始的,因此每个条目都映射了一个地址范围,这些位保持不变。对应的范围就是右边的39位全0到全1,即是2的39次方字节数,也就是512GB

 

​ 同样的道理,PDPT项映射的范围就是1GB

 

PD项映射的范围就是2MB

 

PT项银蛇的范围就是4KB

 

​ 一个有效的PxE存着一个物理页面,不管是子分页结构还是虚拟地址对应的物理页面。PxE的第0位决定是否有效,当设置为1时,这个PxE是有效的。

 

​ 下面是一个有效PxE的布局:

 

 

PxE的各位域含义如下:

 

P :当这个位被设置时,表示PxE是有效的,要么指向下一个PS,要么就是一个映射的物理页。当这个位置被清0,那么这个PxE是无效的。

 

R/W:这个位设置时,向这个虚拟地址写入是允许的,当被清零时,写入会导致页面错误异常。

 

U/S:这个置为1时,表示可以在CPL为3时访问。清零时表示只能在CPL<3访问。这个位存在的意义时为了让部分虚拟地址空间只能被ring 0访问,从而保护系统代码。试图在ring3 访问一个被保护的虚拟地址,将会触发页面异常错误。

 

PWT:控制内存如何被缓存

 

PCD:控制内存如何被缓存

 

A:指令访存时会涉及这个位的使用。这个位让代码知道这片虚拟内存区域是否已经被访问过。这个位在首次访问时会被设置,然后保留这个设置到代码显式更新PxE,才会清零。

 

D:当指令写入这个地址映射的空间时由处理器进行设置的。这提供了让代码知道这个物理页是否已经被写入的方法。这个位称为dirty位。处理器设置后并不进行clear操作。这个只会设置PTEsPDEs

 

PAT:如果是PTEs,这个控制是内存缓存。更高级别的PxEs,用来控制页面大小。PML4E里是保留位,必须设置为0。PDPTE可以设置为1,但是我们不分析这一位,因为Windows没有使用这一功能。PDE里可以设置为1,这个位会改变虚拟到物理地址转换的方式。当这个位在PDPTEPDE中被设置为0时,我们之前所描述的转换就发生了。

 

G:控制这个项在TLB中的缓存。这个位只在PS层次结构的最后一级使用。比如说PTEs和用于大页的PDEs

 

i:所有的i位被处理器忽略,软件可以使用他。

 

PFN:这个位表示该项所在物理页的PFN。对于PTEs和大页的PDEs则是虚拟地址映射的物理页的PFN。因为这个位从第12位开始的,那么我们从PxE取0-51位然后清掉0-11位,我们就可以得到物理地址所在的页面。

 

XD:这位设置时,从内存区域里提取指令是不被允许的。禁止取指令意味着不允许执行受这位影响的的内存区域代码。这是设计的一个安全功能,防止代码注入到数据内存区域。比如说注入到栈上的栈溢出攻击。

 

 

​ 上图是一个大页的地址转换方式。PDE的第7位如果设置了,那么会改变处理器执行寻址的方式。PDE将会直接用于映射虚拟页,PT会从地址转换过程中被移除。这样的话,虚拟地址的21-47位用于PML4EPDPTEPDE的获取。PDE存的就是物理地址转换中的基址。虚拟地址的0-20位就作为offset。因此这种转换的映射范围就等于2MB。这样的话,PDE必须就被赋值在2MB的边界上,PDE映射的范围就是2MB对齐和2MB大小的。这种情况下,PDE的0-20必须设置为0,这些位又可以用来存放控制信息或者保留了。大页的PDE格式如下图所示:

 

 

PxE的区别如下:

 

bit7:这个位必须是1,因为表示映射的是大页。这个位要是clear的话,PDE引用的就是PT了。

 

PAT:这个位和PTE是一样的,用来控制内存的缓存。

 

​ 对于大页也需要PAT位,然后移到了12位。对于小页面,12位是不可获得的。因为他是PTPFN。可以移到12位的原因还是在于大页必须是2MB对齐。

 

PFNPFN使用的是21-51位,原因还是2MB对齐问题。

 

转换层次中的任何一级,都可以设置成无效PxEs,只不过这样包含的无效虚拟地址更多了。

 

举个例子,PML4E可以无效,然后他就不能引用PDPT。这种情况发生的话,所有的512GB的地址就都无虚拟到物理的转换。同样的应用于PDPTEsPDEs,分别是1GB和2MB

 

0位是无效的,那么处理器会忽略其他所有的位。

 

 

这样的话,1-63位就可以被软件使用了。比方说来用存储PxE在分页文件中应该指向的内容的位置。

 

我们现在可以知道,地址转换做了实现虚拟内存所需的三件基本事情:

  1. 允许映射虚拟地址到完全不同的物理地址上,允许使用系统上实际不存在的系统内存,比如说0xFFFF800000000000-0xFFFFFFFFFFFFFFF。
  2. 允许映射相同的虚拟地址到不同的物理页上,这是通过改变cr3实现的。这提供了简单的地址空间切换的方法。
  3. 允许设置地址无效,因此允许页面错误例程来控制和决定是否映射这个地址(建立有效的转换)。

​ 我们可以看到,这些功能都不需要我们看到的多级方案。如果我们所需要的只是一个将虚拟地址与物理地址相关联(即映射)的数据结构,我们可以使用一个由虚拟地址索引的简单数组,其中每个条目存储一个物理页的地址。更进一步说,我们可以问自己为什么要有内存页,也就是说,为什么不让我们假设的数组的一个条目只是存储内存中的一个物理地址。

 

​ 简而言之,这种方法的最大缺点是,我们必须为每一个可能的虚拟页面设置一个条目,包括那些进程永远不会使用的页面。

 

 

​ 通过分级,无效的项就不会指向物理内存,这样根据分页结构仅需要映射有效的映射就行了。

 

​ 这种机制也允许共享分页结构。假设二级地址空间设置PML4项指向同一个PDPT,那么这样就实现了两个地址空间512GB的内存共享。Windows就是使用这种方式在所有进程中共享系统地址空间的。

 

​ 为了转换一个虚拟地址到物理地址,处理器要执行4次访存。因为内存延时比指令执行时间要长,这会大大降低了处理器的速度。为了避免这种情况,转换使用了两种类型的缓存:TLB和页结构缓存。

 

​ 如果需要转换的虚拟地址在TLB中找到了,那么处理器就不需要访问页结构的内存,直接完成转换。

 

​ 页结构缓存存储了部分PML4EPDPTEPDE

 

​ 这些缓存机制带来了一个重大的代价:软件需要自己显式的设置缓存无效。如果代码修改了PxE,那么处理器是不能检测到这个更新的,也不会刷新缓存里的转换。代码修改PxEs后,必须显式无效TLB和页结构缓存。这是一个比较复杂的任务,多核的系统上,所有的处理器都需要把自己的缓存无效化,这需要处理器内部中断和多核之间的进行同步。这样的操作不能经常进行,否则性能会受到很大影响,所以必须实现更复杂的逻辑,以分批进行缓存转换的无效化。

 

​ 处理器会自动无效化缓存,当一个值被加载到cr3后,因为这个操作改变了PML4的地址,切换到了不同的地址空间。

 

​ 然而Windows有一些内存是共享的,如果每次上下文切换都无效化缓存,那么是比较浪费时间的。这也是PTEs和大页PDEsG位的作用:如果这一位被set,那么在cr3切换时,不会刷新TLB,不过页结构缓存总是会被刷新的,因为G位在其他级别的PxEs中是被忽略的。

6 缓存控制

​ Intel 架构为缓存定义了内存类型和缓存控制寄存器已经他的位。

 

​ 内存类型指定了内存是否被缓存,即它的副本是否被带入处理器的缓存层次,以及当指令写到一个地址时,如何将更新写入主存。

 

​ 控制寄存器和位指定特定物理或虚拟地址范围的内存类型。

6.1 内存类型

6.1.1 回写型 (WB)

​ 这是最多的缓存类型,它使内存接口的流量保持在最低水平。如果内容在读取时它还没有存在的话,内存被复制到处理器的缓存中。随后的读取将不访问主存,而是从缓存中获取数值。写入将更新缓存中的值的副本,而不会被传到主存。
​ 一个系统有一个以上的处理器,也可以有其他设备从系统内存读取和写入,如DMA控制器。总的来说,所有访问主存的组件被称为总线代理。
对于多个总线代理,必须注意确保每个代理对内存内容一致性。例如,如果一个处理器在不更新主存的情况下更新其缓存中的一个内存位置的副本(这在WB内存中是很正常的),其他代理可能会看到该位置的一个陈旧副本。
​ 这可以通过窥探来避免:总线代理交换信号,允许他们每个人窥探其他代理对其缓存数据的更新。如果一个代理在其缓存中更新了一个值,而另一个代理有一个相同值的副本,那么第二个代理就会扔掉它的副本。下次第二个代理需要该特定数据时,将从内存中加载它。然而,这还不够,因为从主存拷贝的也是陈旧的,只要更新的值保留在修改它的代理的缓冲区内。为了解决这个问题,每个代理对其他代理对主存的访问进行窥探。当它检测到另一个代理想要一块已经在缓存中被修改的数据时,两个代理就会合作,这样需要数据的代理就会从缓存数据的代理那里得到最新的副本

 

​ 有了这种类型的存储器,当一个处理器写到一个不在缓存中的地址时,存储器的内容首先被复制到缓存中,然后在那里更新。换句话说,读和写都会填满缓冲区。
​ 缓存内存是以整个缓存行(通常为64字节边界对齐的64字节块)进行读写的,由内存接口最有效地处理。这意味着,读或写一个在缓存中找不到的单一字节会导致整个缓存行被复制到它。

6.1.2 合并写型 (WC)

​ 这种内存类型适用于视频缓冲器,它有一些特殊性。

 

​ 对视频缓冲区的写入必须及时传播到处理器之外,以更新视频内容,所以不能使用回写存储器。然而,写入不需要按照程序顺序进行,所以允许处理器在一个中间的内部缓冲区中合并它们,以使用最有效的传输周期将数据发送出去。
​ 内存写入不会导致包含数据的高速缓存行被加载到高速缓存中。如果发生这种情况,缓冲区就会被视频缓冲区的数据填满,大部分是无用的。内存读取也不会被缓存。
​ 处理器不保证WC内存的一致性,因为对它的访问不被窥探,所以有一个时间窗口,其他代理会看到陈旧的内容。同样,这对视频缓冲区来说是有意义的,因为它很少被读取,也不用于存储共享数据结构。如果代码需要确保WC内存是最新的,有指令明确地刷新中间缓冲区。

6.1.3 非缓存型(UC)

​ 这种内存类型没有缓存,所以读和写总是传播到接口总线上。它主要适用于内存映射的设备,因为读或写地址会导致与设备的交互。通常,设备对其内存映射寄存器的读写顺序很敏感,也就是说,改变读写的顺序会对设备产生不同的影响。在WB存储器的内存中,处理器并不完全按照程序顺序执行读和写。

  • 如果读和写覆盖了不同的内存位置,那么读可以在写之前执行,而写则在程序顺序中。
  • 在代码中,处理器可以在条件跳转后执行写入的读。它试图预测条件的结果,即是否会进行跳转,并可以执行读,这是预测正确时将执行的代码块的一部分。读取的数据被存储在中间缓冲区,如果预测结果是正确的,以后将用于实际的代码执行,或者被丢弃。如果处理器预测了错误的路径,它最终会读取那些根据程序逻辑和状态不应该被读取的地址。这对内存来说是无害的,但会对设备造成不希望的影响。
    在UC存储器中,只有实际代码执行产生的读和写才会被执行,并且完全按照程序顺序进行。

​ 对UC内存的访问会降低整个系统的性能,有如下原因

  • 每个处理器在执行其他内存访问之前都必须等待读和写的完全执行。
  • 所有总线代理共享的内存接口上的流量增加了,因为每一个访问都被传到了内存。这增加了总线争用的概率,从而进一步延迟了内存访问。

6.2 内存类型范围寄存器(MTRR)

MTRRs寄存器是为特定的物理地址范围指定可能的内存类型。一般来说,Windows对MTRRs进行编程,使所有实际的内存(相对于映射的设备)具有WB类型。

6.3 页属性表 (PAT)

​ 虽然MTRRs为物理地址范围指定了内存类型,但PAT允许在虚拟地址上指定相同的信息。因此,一个给定的VA将有一个由PAT指定的类型,并将被翻译成一个由MTRR指定的、可能不同的物理地址类型。正如我们将看到的那样,最终的类型来自于这两者的结合。

 

PAT寄存器是一个64位的寄存器,每一项有8位。对应的位被设置时对应了不同的内存类型。

 

 

Intel架构定义了比上表还多的内存类型,但是Windows程序只使用了上表的。

 

虚拟到物理转换的最后一步(PTE或者大页的PDE)通过其PWTPCDPAT位选择一个PAT条目,从而为页面选择一个内存类型。这三个位根据下表选择一个PAT条目。

 

 

 

​ 一个给定的虚拟页有一个由选定的PAT条目指定的内存类型,并被映射到一个物理地址,该物理地址有一个由MTRR指定的内存类型。intel文档定义了可能的组合的结果内存类型,对于我们的目的来说,只需要说当MTRR指定回写型时,结果类型是由PAT选择的。

 

​ 当MTRR指定为回写型时,表1中的UC-类型等同于UC,,表示非缓存型。

 

​ 当MTRR指定了回写型以外的内存类型时,UC-UC不同。例如,当MTRR指定合并写类型时(通常用于视频缓冲区),在PAT中选择UC-的最终的类型是WC,而选择UC会得到非缓存型。UC-指定的是未缓存的内存,但可以被MTRR中的WC覆盖。PAT中的UC覆盖了MTRR中的WC

 

​ 到目前为止,我们所看到的是物理页映射虚拟页的缓存。存储分页结构的物理页也有一个内存类型,这是以同样的方式确定的。唯一的区别是,指向子分页结构的PxE没有PAT位,所以处理器的行为就像PAT为0一样,并根据PCDPWT选择一个PAT条目。

 

​ 值得注意的是PxE的缓存控制位与其他控制位如R/WU/SXD之间的区别。缓存控制位控制条目所指向的页面的内存类型。如果该条目不是最底层的,例如是一个PDPTE,这些位指定下一级分页结构的内存类型,在我们的例子中是PD。只有最后一级条目的缓存控制位对转换后的物理页的缓存策略有影响。相反,其他的控制位总是作用于转换的结果。例如,一个R/W清零的PDEPD映射的整个范围保护为只读,但并没有指定它指向的PT是只读的。

7 MSR寄存器

​ Intel架构定义了系列寄存器来配置处理器如何工作。这些寄存器由一个被称为寄存器地址的编号来识别,并且有具体的指令来对其进行读写。

 

​ 我们可以使用windbg的rdmsr命令来获得寄存器地址。例如查看PAT寄存器:

1
2
kd> rdmsr 277
msr[277] = 00070406`00070406

​ 白皮书卷三附录B列出了MSRs的地址。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2022-2-25 16:41 被VirtualCC编辑 ,原因:
收藏
点赞8
打赏
分享
最新回复 (4)
雪    币: 2613
活跃值: (4657)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
smallzhong_ 2022-5-13 14:14
2
0

。。回复错了

最后于 2022-5-13 14:23 被smallzhong_编辑 ,原因: 。。回复错了
雪    币: 3906
活跃值: (5533)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
badboyl 2 2023-7-5 16:29
3
0
好文啊
雪    币: 3
活跃值: (626)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
爆浆鸡排J 2023-9-10 01:31
4
0
6666666666666
雪    币: 19299
活跃值: (28933)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-9-10 22:34
5
1
感谢分享
游客
登录 | 注册 方可回帖
返回