详情请见附件:
内存栅栏、原子操作、一致性、编译优化 等问题的探讨.pdf
1 引言
1.1 编写目的
本文档主旨在于总结和探讨提升性能的各种方式,包括硬件和软件两个方面。
本文档先从CPU基本原理开始说起,系统性能提升必须以了解CPU基本原理为前提条件,另外,CPU Cache工作原理也是提升系统整体性能的非常重要的方面,所以本文拿出专门章节对其原理进行了详细介绍。
本文档还从编程角度(C语言)探讨了多种旨在提升性能的方法,仅供参考。
1.2 背景
2 基本概念
在现代CPU体系设计结构中,一般提供了下面几种机制来提升系统的整体性能:
1)总线加锁、cache一致性管理
以实现对系统内存的原子操作、串行化指令(serializing instructions。这些指令仅对pentium4,Intel Xeon, P6,Pentium处理器有效)。
2)处理器芯片内置的高级可编程中断控制器(APIC)
3)二级缓存(level 2, L2)
对于Pentium4,Intel Xeon, P6处理器,L2 cache已经紧密的封装到了处理器中。而Pentium,Intel486提供了用于支持外部L2 cache的管脚。
4)超线程技术
它能够让一个处理器内核并发的执行两个或两个以上的指令流。
这些机制在对称多处理系统(symmetric-multiprocessing, SMP)中是极其有用的。然而,在RMI这些多核系统中,这些机制也是适用的。
多处理器机制的设计必须满足下面的需求:
1)保持系统内存的完整性(coherency)
当两个或多个处理器试图同时访问系统内存的同一地址时,必须有某种通信机制或内存访问协议来提升数据的完整性,以及在某些情况下,允许一个处理器临时锁定某个内存区域。
2)保持高速缓存的一致性
当一个处理器访问另一个处理器缓存中的数据时,必须要得到正确的数据。如果这个处理器修改了数据,那么所有的访问这个数据的处理器都要收到被修改后的数据。
3)允许以可预知的顺序写内存
在某些情况下,从外部观察到的写内存顺序必须要和编程时指定的写内存顺序相一致。
4)在一组处理器中派发中断处理
当几个处理器正在并行的工作在一个系统中时,有一个集中的机制是必要的,这个机制可以用来接收中断以及把他们派发到某一个适当的处理器。
5)采用现代操作系统和应用程序都具有的多线程和多进程的特性来提升系统的性能
2.1 原子操作
原子操作概念
当两个或多个处理器同时修改数据结构中的同一数据域或标志的时候,就存在原子操作的问题,也就是说,当某个处理器正在修改某个全局数据标志的时候,另外一个处理器也可能正在修改此标志,如果不保证这两个操作的原子性,则可能出现第一个处理器刚刚把此数据标志从内存中读入并存放在某个寄存器中,另一个处理器???
原子操作可以分为几个级别:可靠的原子操作(guaranteed atomic operations)、加锁的原子操作(locked atomic operations)。
一般某些基本的内存事务(memory transaction例如读写系统内存的一个字节)被保证是可靠的原子操作。也就是说,一旦开始,处理器会保证这个操作会在另一个处理器或总线代理(bus agent)访问相同的内存区域之前结束。
加锁的原子操作实际上是在可靠的原子操作基础上的进一步保障,它主要通过总线加锁和缓存完整性协议两种技术来达到。比如a++操作,它实际上包括读-改-写三个可靠的原子操作,原则上此操作需要三条原子操作指令才能够完成,那么就存在此序列操作被其他处理器打断,为了避免出现这种情况,一种方法是在a++的前后加锁保护,另一种方法是从指令上就保证原子性的。在IA或者MIPS系统中,基本都提供了类似的原子操作指令,通常称之为复合指令。总线加锁可以保证内存的操作是原子的,不被其他处理器打断。而且当执行a++操作的时候,总线加锁是自动的,不需要显式地执行总线加锁。但是如果a++操作非常频繁,由于频繁使用的内存数据经常被缓存在处理器的L1,L2高速缓存里,原子操作通常是在处理器缓存内部进行的,并不需要声明总线加锁,这时候如何保证各个处理器的缓存内部的数据始终一致呢?原来处理器缓存完整性协议可以保证在缓冲内存上执行原子操作时其他缓存了相同内存区域的处理器被正确管理。
处理器能够保证原子操作的情况
Pentium 4, Intel Xeon,P6系列,Pentium,以及Intel486处理器保证下面的基本内存操作总被自动保证原子性:
1)读或写一个字节
2)读或写一个在16位边界对齐的字
3)读或写一个在32位边界对齐的双字
Pentium 4, Intel Xeon,P6系列以及Pentium处理器还保证下列内存操作总被自动保证原子性:
1)读或写一个在64位边界对齐的四字(quadword)
2)对32位数据总线可以容纳的未缓存的内存位置进行16位方式访问
P6系列处理器还保证下列内存操作总被自动保证原子性:
对32位缓冲线(cache line)可以容纳的缓存中的数据进行非对齐的16位,32位,64位访问.
对于可以被缓存的但是却被总线宽度,缓冲线,页边界所分割的内存区域,Pentium 4, Intel Xeon, P6 family,Pentium以及Intel486处理器都不保证访问操作是原子的。Pentium 4, Intel Xeon,P6系列处理器提供了总线控制信号来允许外部的内存子系统完成对分割内存的原子性访问;但是,对于非对齐内存的访问会严重影响处理器的性能,因此应该尽量避免。
2.2 总线加锁
IA-32处理器提供了LOCK#信号。这个信号会在某些内存操作过程中被自动发出。当这个输出信号发出的时候,来自其他处理器或总线代理的总线控制请求将被阻塞。软件能够利用在指令前面添加LOCK前缀来指定在其他情况下的也需要LOCK语义(LOCK semantics)。
在Intel386,Intel486,Pentium处理器中,直接调用加锁的指令会导致LOCK#信号的产生。硬件的设计者需要保证系统硬件中LOCK#信号的有效性,以控制多个处理对内存的访问(cpu上有一根pin #HLOCK连到北桥,lock前缀会在执行这条指令前先去拉这根pin,持续到这个指令结束时放开#HLOCK pin,在这期间,北桥会屏蔽掉一切外设以及AGP的内存操作。也就保证了这条指令的atomic)。
1、 自动总线加锁
下面的操作会自动的带有LOCK语义(都是以Interl处理器为例):
1)执行引用内存的XCHG指令。
2)设置TSS描述符的B(busy忙)标志。在进行任务切换时,处理器检查并设置TSS描述符的busy标志。为了保证两个处理器不会同时切换到同一个任务。处理器会在检查和设置这个标志的时遵循LOCK语义。
3)更新段描述符时。在装入一个段描述符时,如果段描述符的访问标志被清除,处理器会设置这个标志。在进行这个操作时,处理器会遵循LOCK语义,因此这个描述符不会在更新时被其他的处理器修改。为了使这个动作能够有效,更新描述符的操作系统过程应该采用下面的方法:
(1)使用加锁的操作修改访问权字节(access-rights byte),来表明这个段描述符已经不存在,同时设置类型变量,表明这个描述符正在被更新。
(2)更新段描述符的内容。这个操作可能需要多个内存访问;因此不能使用加锁指令。
(3)使用加锁操作来修改访问权字节(access-rights byte),来表明这个段描述符存在并且有效。
注意,Intel386处理器总是更新段描述符的访问标志,无论这个标志是否被清除。Pentium 4, Intel Xeon,P6系列,Pentium以及Intel486处理器仅在该标志被清除时才设置这个标志。
4)更新页目录(page-directory)和页表(page-table)的条目。在更新页目录和页表的条目时,处理器使用加锁的周期(locked cycles)来设置访问标志和脏标志(dirty flag)。
5)响应中断。发生中断后,中断控制器可能会使用数据总线给处理器传送中断向量。处理器必须遵循LOCK语义来保证传送中断向量时数据总线上没有其他数据。
2、 软件控制的总线加锁
如果想强制执行LOCK语义,软件可以在下面的指令前使用LOCK前缀。当LOCK前缀被置于其他的指令之前或者指令没有对内存进行写操作(也就是说目标操作数在寄存器中)时,一个非法操作码(invalid-opcode)异常会被抛出。
可以使用LOCK前缀的指令:
1)位测试和修改指令(BTS, BTR, BTC)
2)交换指令(XADD, CMPXCHG, CMPXCHG8B)
3)XCHG指令自动使用LOCK前缀
4)单操作数算术和逻辑指令:INC, DEC, NOT, NEG
5)双操作数算术和逻辑指令:ADD, ADC, SUB, SBB, AND, OR, XOR
注意:
(1)一个加锁的指令会保证对目标操作数所在的内存区域加锁,但是系统可能会将锁定区域解释得稍大一些。
(2)软件应该使用相同的地址和操作数长度来访问信号量。例如,如果一个处理器使用一个字来访问信号量,其他的处理器就不应该使用一个字节来访问这个信号量。
(3)总线加锁的完整性不受内存区域对齐的影响。在所有更新操作数的总线周期内,加锁语义一直持续。但是建议加锁访问能够在自然边界对齐,这样可以提升系统性能:
·任何边界的8位访问(加锁或不加锁)
·16位边界的加锁字访问
·32位边界的加锁双字访问
·64位边界的加锁四字访问
(4)对所有的内存操作和可见的外部事件来说,加锁的操作是原子的。只有取指令和页表操作能够越过加锁的指令。
(5)加锁的指令能用于同步数据,这个数据被一个处理器写而被其他处理器读。
对于P6系列处理器来说,加锁的操作使所有未完成的读写操作串行化(serialize)(也就是等待它们执行完毕)。这条规则同样适用于Pentium4和Intel Xeon处理器,但有一个例外:对弱排序的内存类型的读入操作可能不会被串行化。
3、 自修改代码
处理器将数据写入当前的代码段以实现将该数据作为代码来执行的目的,这个动作称为自修改代码。IA-32处理器在执行自修改代码时采用特定模式的行为,具体依赖于被修改的代码与当前执行位置之间的距离。由于处理器的体系结构变得越来越复杂,而且可以在引退点(retirement point)之前推测性地执行接下来的代码(如:P4, Intel Xeon, P6系列处理器/MIPS处理器),如何判断应该执行哪段代码,是修改前地还是修改后的,就变得模糊不清。要想写出于现在的和将来的IA-32体系相兼容的自修改代码,必须选择下面的两种方式之一:
方式1:
将代码作为数据写入代码段;
跳转到新的代码位置或某个中间位置;
执行新的代码;
方式2:
将代码作为数据写入代码段;
执行一条串行化指令;(如:CPUID指令)
执行新的代码;
在Pentium或486处理器上运行的程序不需要以上面的方式书写,但是为了与Pentium 4, Intel Xeon, P6系列处理器兼容,建议采用上面的方式。
需要注意的是自修改代码将会比非自修改代码的运行效率要低。性能损失的程度依赖于修改的频率以及代码本身的特性。
4、 交叉修改代码
处理器将数据写入另外一个处理器的代码段以使得哪个处理器将该数据作为代码执行,这称为交叉修改代码(cross-modifying code)。像自修改代码一样,IA-32处理器采用特定模式的行为执行交叉修改代码,具体依赖于被修改的代码与当前执行位置之间的距离。要想写出于现在的和将来的IA-32体系相兼容的自修改代码,下面的处理器同步算法必须被实现:
修改的处理器:
Memory_Flag ← 0; (* Set Memory_Flag to value other than 1 *)
将代码作为数据写入代码段;
Memory_Flag ← 1;
执行的处理器:
WHILE (Memory_Flag ≠ 1)
等待代码更新;
ELIHW;
执行串行化指令; (* 例如, CPUID instruction *)
开始执行修改后的代码;
在Pentium或486处理器上运行的程序不需要以上面的方式书写,但是为了与Pentium 4, Intel Xeon, P6系列处理器兼容,建议采用上面的方式。
像自修改代码一样,交叉修改代码将会比非交叉修改代码的运行效率要低。性能损失的程度依赖于修改的频率以及代码本身的特性。
2.3 缓存加锁
对于Intel486和Pentium处理器,在进行加锁操作时,LOCK#信号总是在总线上发出,甚至锁定的内存区域已经缓存在处理器cache中的时候,LOCK#信号也从总线上发出。
对于Pentium 4, Intel Xeon,P6系列处理器,如果加锁的内存区域已经缓存在处理器cache中,处理器可能并不对总线发出LOCK#信号,而是仅仅修改cache缓存中的数据,然后依赖cache缓存一致性机制来保证加锁操作的自动执行。这个操作称为"缓存加锁"。缓存一致性机制会自动阻止两个或多个缓存了同一区域内存的处理器同时修改数据。
2.4 访问内存排序(Memory Ordering)
(1)编程排序(program ordering):
访存排序指的是处理器如何安排通过系统总线对系统内存访问的顺序。IA-32体系支持几种访存排序模型,具体依赖于体系的实现。例如, Intel386处理器强制执行"编程排序(program ordering)"(又称为强排序),在任何情况下,访存的顺序与它们出现在代码流中的顺序一致。
(2)处理器排序(processor ordering):
为了允许代码优化,IA-32体系在Pentium 4, Intel Xeon,P6系列处理器中允许强排序之外的另外一种模型——处理器排序(processor ordering)。这种排序模型允许读操作越过带缓存的写操作来提升性能。这个模型的目标是在多处理器系统中,在保持内存一致性的前提下,提高指令执行速度。
Pentium和Intel 486处理器的访存排序
1)普遍情况
Pentium和Intel 486处理器遵循处理器排序访存模型;但是,在大多数情况下,访存操作还是强排序,读写操作都是以编程时指定的顺序出现在系统总线上。除了在下面的情况时,未命中的读操作可以越过带缓冲的写操作: 当所有的带缓冲的写操作都在cache缓存中命中,因此也就不会与未命中的读操作访问相同的内存地址。
2)I/O操作访存
在执行I/O操作时,读操作和写操作总是以编程时指定的顺序执行。在"处理器排序"处理器(例如,Pentium 4, Intel Xeon,P6系列处理器)上运行的软件不能依赖Pentium或Intel486处理器的强排序。软件应该保证对共享变量的访问能够遵守编程顺序,这种编程顺序是通过使用加锁或序列化指令来完成的。
3)Pentium 4, Intel Xeon, P6系列处理器的访存排序
Pentium 4, Intel Xeon, P6系列处理器也是使用"处理器排序"的访存模型,这种模型可以被进一步定义为"带有存储缓冲转发的写排序"(write ordered with store-buffer forwarding)。
带有存储缓冲转发的写排序
"带有存储缓冲转发的写排序"(write ordered with store-buffer forwarding)这种模型有下面的特点:
单处理器系统中的排序规则
在一个单处理器系统中,对于定义为回写可缓冲(write-back cacheable)的内存区域,下面的排序规则将被应用:
a.读能够被任意顺序执行
b.读可以越过缓冲写,但是处理器必须保证数据完整性(self-consistent)。然而如果写操作和读操作都是访问同一个内存区域,那么处理器内部的监视机制将会检测到冲突并且在处理器使用错误的数据执行指令之前更新已经缓存的读操作。
c.对内存的写操作总是以编程顺序执行,除非写操作执行了CLFUSH指令以及利用非瞬时的移动指令(MOVNTI, MOVNTQ, MOVNTDQ, MOVNTPS, MOVNTPD)来执行流存储操作(streamint stores)
d.写可以被缓冲。写不能够预先执行;它们只能等到其他指令执行完毕
e.在处理器中,来自于缓冲写的数据可以直接被发送到正在等待的读操作
f.读写操作都不能跨越I/O指令,加锁指令,或者序列化指令
g.读操作不能越过LFENCE和MFENCE指令
h.写操作不能越过SFECE和MFENCE指令
注意"带有存储缓冲转发的写排序"指的是第b条规则和第f条规则的组合之后产生的效果。
多处理器系统中的排序规则
a.每个处理器使用同单处理器系统一样的排序规则
b.所有处理器所观察到的某个处理器的写操作顺序是相同的
c.每个处理器的写操作并不与其它处理器之间进行排序
例如:在一个三处理器的系统中,每个处理器执行三个写操作,分别对三个地址A, B,C。每个处理器以编程的顺序执行操作,但是由于总线仲裁和其他的内存访问机制,三个处理器执行写操作的顺序可能每次都不相同。最终的A, B, C的值会因每次执行的顺序而改变。
加强或者削弱访存排序模型
IA-32体系提供了几种机制用来加强和削弱访存排序模型以处理特殊的编程场合。这些机制包括:
1)I/O指令,加锁指令,LOCK前缀,以及序列化指令来强制执行"强排序"
2)SFENCE指令(在Pentium III中引入)和LFENCE,MFENCE指令(在Pentium 4和Intel Xeon处理器中引入)提供了某些特殊类型内存操作的排序和串行化功能
3)内存类型范围寄存器(memory type range registers (MTRRs))可以被用来加强和削弱物理内存中特定区域的访存排序模型。MTRRs只存在于Pentium 4, Intel Xeon, P6系列处理器。
4)页属性表可以被用来加强某个页或一组页的访存排序("页属性表"Page Attribute Table(PAT))。PAT只存在于Pentium 4, Intel Xeon,P6系列处理器。
这些机制可以通过下面的方式使用:
1)内存映射和其他I/O设备通常对缓冲区写操作的顺序很敏感。I/O指令(IN,OUT)以下面的方式对这种访问执行强排序。在执行一条I/O 指令之前,处理器等待之前的所有指令执行完毕以及所有的缓冲区都被写入了内存。只有取指令操作和页表查询(page table walk)能够越过I/O指令。后续指令要等到I/O指令执行完毕才开始执行。
2)一个多处理器的系统中的同步机制可能会依赖"强排序"模型。这里,一个程序使用加锁指令,例如XCHG或者LOCK前缀,来保证读-改-写操作是自动进行的。加锁操作像I/O指令一样等待所有之前的指令执行完毕以及缓冲区都被写入了内存。
3)程序同步可以通过序列化指令来实现。这些指令通常用于临界过程或者任务边界来保证之前所有的指令在跳转到新的代码区或上下文切换之前执行完毕。像I/O加锁指令一样,处理器等待之前所有的指令执行完毕以及所有的缓冲区写入内存后才开始执行序列化指令。
4)SFENCE,LFENCE,MFENCE指令提供了高效的方式来保证读写内存的排序,这种操作发生在产生弱排序数据的程序和读取这个数据的程序之间。
SFENCE——串行化发生在SFENCE指令之前的写操作但是不影响读操作。
LFENCE——串行化发生在SFENCE指令之前的读操作但是不影响写操作。
MFENCE——串行化发生在MFENCE指令之前的读写操作。
注意:SFENCE,LFENCE,MFENCE指令提供了比CPUID指令更灵活有效的控制内存排序的方式。
5)MTRRs在P6系列处理器中引入,用来定义物理内存的特定区域的高速缓存特性。
下面的两个例子是利用MTRRs设置的内存类型如何来加强和削弱Pentium 4, Intel Xeon, P6系列处理器的访存排序:
·强不可缓冲(strong uncached,UC)内存类型实行内存访问的强排序模型:
这里,所有对UC内存区域的读写都出现在总线上,并且不能够被乱序或预先执行。这种内存类型可以应用于映射成I/O设备的内存区域来强制执行访存强排序。
·对于可以容忍弱排序访问的内存区域,可以选择回写(write back, WB)内存类型
这里,读操作可以预先的被执行,写操作可以被缓冲和组合(combined)。对于这种类型的内存,锁定高速缓存是通过一个加锁的原子操作实现的,这个操作不会分割缓冲线,因此会减少典型的同步指令(如,XCHG在整个读-改-写操作周期要锁定数据总线)所带来的性能损失。对于WB内存,如果访问的数据已经存在于缓存cache中,XCHG指令会锁定高速缓存而不是数据总线。
(3)PAT在Pentium III中引入,用来增强用于存储内存页的缓存性能。
PAT机制通常被用来与MTRRs一起来加强页级别的高速缓存性能。在Pentium 4, Intel Xeon,P6系列处理器上运行的软件最好假定是 "处理器排序"模型或者是更弱的访存排序模型。
Pentium 4, Intel Xeon,P6系列处理器没有实现强访存排序模型,除了对于UC内存类型。尽管Pentium 4, Intel Xeon,P6系列处理器支持处理器排序模型,Intel并没有保证将来的处理器会支持这种模型。为了使软件兼容将来的处理器,操作系统最好提供临界区 (critical region)和资源控制构建以及基于I/O,加锁,序列化指令的API,用于同步多处理器系统对共享内存区的访问。同时,软件不应该依赖处理器排序模型,因为也许系统硬件不支持这种访存模型。
(4)向多个处理器广播页表和页目录条目的改变
在一个多处理器系统中,当一个处理器改变了一个页表或页目录的条目,这个改变必须要通知所有其它的处理器。这个过程通常称为"TLB shootdown"。广播页表或页目录条目的改变可以通过基于内存的信号量或者处理器间中断(interprocessor interrupts, IPI)。
例如一个简单的,但是算法上是正确的TLB shootdown序列可能是下面的样子:
a.开始屏障(begin barrier)——除了一个处理器外停止所有处理器;让他们执行HALT指令或者空循环。
b.让那个没有停止的处理器改变PTE or PDE。
c.让所有处理器在他们各自TLB中修改的PTE, PDE失效。
d.结束屏障(end barrier)——恢复所有的处理器执行。
(5)串行化指令(serializing instructions)
IA-32体系定义了几个串行化指令(SERIALIZING INSTRUCTIONS)。这些指令强制处理器完成之前的指令对标志,寄存器以及内存的修改,并且在执行下一条指令之前将所有缓冲区里的数据写入内存。
2.5 串行化指令
串行化指令强制处理器完成之前的指令对标志,寄存器以及内存的修改,并且在执行下一条指令之前将所有缓冲区里的数据写入内存。
串行化指令应用一:开启保护模式时的应用
例如:当MOV指令将一个操作数装入CR0寄存器以开启保护模式时,处理器必须在进入保护模式之前执行一个串行化操作。这个串行化操作保证所有在实地址模式下开始执行的指令在切换到保护模式之前都执行完毕。
串行化指令的概念在Pentium处理器中被引入IA-32体系。这种指令对于Intel486或更早的处理器是没有意义的,因为它们并没有实现并行指令执行。
非常值得注意的是,在Pentium 4, Intel Xeon,P6系列处理器上执行串行化指令会抑制指令的预执行(speculative execution),因为预执行的结果会被放弃掉。
下面的指令是串行化指令:
1. 特权串行化指令
MOV(目标操作数为控制寄存器),MOV(目标操作数为调试存器),WRMSR, INVD, INVLPG, WBINVD, LGDT, LLDT, LIDT, LTR。
INVLPG指令:
使TLB(转换后援缓冲器:用于存放最常使用的物理页的页码)项无效。该指令是特权指令,只有在实方式和保护方式的特权级0下,才可执行该指令。
2.非特权串行化指令
CPUID, IRET, RSM。
2. 非特权访存排序指令
SFENCE, LFENCE, MFENCE。
当处理器执行串行化指令的时候,它保证在执行下一条指令之前,所有未完成的内存事务都被完成,包括写缓冲中的数据。任何指令不能越过串行化指令,串行化指令也不能越过其他指令(读,写, 取指令, I/O)。
CPUID指令可以在任何特权级下执行串行化操作而不影响程序执行流(program flow),除非EAX, EBX, ECX, EDX寄存器被修改了。
SFENCE,LFENCE,MFENCE指令为控制串行化读写内存提供了更多的粒度。
在使用串行化指令时,最好注意下面的额外信息:
处理器在执行串行化指令的时候并不将高速缓存中已经被修改的数据写回到内存中。软件可以通过WBINVD串行化指令强制修改的数据写回到内存中。但是频繁的使用WBINVD指令会严重的降低系统的性能。
INVD指令:
INVD指令使片上的高速缓存无效,即:清洗片上的超高速缓存。但该指令并不把片上的超高速缓存中的内容写回主存。该指令是特权指令,只有在实方式和保护方式的特权级0下,才可执行该指令。
WBINVD指令:
WBINVD指令使片上的超高速缓存无效即:清洗片上的超高速缓存。但该指令将把片上的超高速缓存中更改的内容写回主存。该指令是特权指令,只有在实方式和保护方式的特权级0下,才可执行该指令。
串行化指令应用二:改变了控制寄存器CR0的PG标志的应用
当一条会影响分页设置(也就是改变了控制寄存器CR0的PG标志)的指令执行时,这条指令后面应该是一条跳转指令。跳转目标应该以新的PG标志 (开启或关闭分页)来进行取指令操作,但跳转指令本身还是按先前的设置执行。Pentium 4, Intel Xeon,P6系列处理器不需要在设置CR0之后放置跳转指令(因为任何对CR0进行操作的MOV指令都是串行化的)。但是为了与其他IA-32处理器向前和向后兼容,最好是放置一条跳转指令。
说明:CR0的第31位为PG标志,PG=1:启用分页管理机制,此时线性地址经过分页管理机制后转换为物理地址;PG=0:禁用分页管理机制,此时线性地址直接作为物理地址使用。
在允许分页的情况下,当一条指令会改变CR3的内容时,下一条指令会根据新的CR3内容所设置的转换表进行取指令操作。因此下一条以及之后的指令应该根据新的CR3内容建立映射。
说明:CR3用于保存页目录表的起始物理地址,由于目录表是对齐的,所以仅高20位有效,低12位无效。所以如果向CR3中装入新值,其低 12位当为0;每当用mov指令重置CR3的值时候,TLB中的内容会无效。CR3在实方式下也可以设置,以使分页机制初始化。在任务切换时候,CR3要被改变。但要是新任务的CR3的值==旧任务的CR3的值,则TLB的内容仍有效,不被刷新。
内存栅栏、原子操作、一致性、编译优化 等问题的探讨
详情请见附件:
内存栅栏、原子操作、一致性、编译优化 等问题的探讨.pdf