分类: LINUX
2011-11-05 22:28:57
前言
本卷讲解AMD64基本架构,内存组织结构以及四种应用编程模型。
四种应用编程模型为:
1) 通用编程(General-Purpose Programming)
16个GPRs的使用,程序异常,分支控制,I/O,内存优化;
2) SSE编程(Streaming SIMD Extensions Programming)
YMM/XMM寄存器(256bit/128bit),支持向量(Vector)/标量(Scalar)数据类型的整数/浮
点运算;
3) MMX编程(MultiMedia Extensions Programming)
64bit的MMX寄存器,支持向量/标量数据类型的整数/浮点运算;
4) X87浮点编程(X87 Folating-Point Programming)
80bit的X87寄存器,支持标量数据类型的浮点运算。
AMD64架构有2种模式:长模式(Long Mode)和传统模式(Legacy Mode)
a)长模式包括2种子模式:使能分页 和PAE;包括64-bit模式和兼容模式(Compatibility
Mode);64-bit模式不支持硬件任务切换,不支持16 bit;
b)传统模式包括3种子模式:实模式(Real Mode),保护模式(Protected Mode)和虚8086
模式(Virtual-8086 Mode)。
Legacy Mode即传统的32位X86 CPU,上电启动时AMD64 CPU处于Legacy Real Mode,
长模式的兼容模式支持32bit和16bit,相当于Legacy Protected Mode.
1架构综述AMD64架构设计原因:无缝兼容legacy x86,更大的寻址空间和高性能(尤其是大块数据操作)。表现为更多的寄存器,64bit的寄存器,64bit的虚拟地址和物理地址。
1.1 新特性
Ø 增加了8个GPRs,扩展了原来的8个GPRs,共16个GPRs
Ø 16个GPRs皆为64bit
Ø 在原来8个的基础上再增加8个YMM/XMM寄存器
Ø 指令前缀REX(操作码值为40h~4Fh) ,指令前加上后可访问扩展寄存器
Ø 64-bit模式增加新的RIP相对数据寻址模式(±2GB, 32bits offset)
Ø 64-bit模式下:平坦地址空间(Flat Address Space),去除了段
Ø 64位虚拟地址,地址大小前缀(操作码值为67h),以及操作数大小前缀(操作码值为66h)
Ø 长模式和传统模式
1.2 寄存器
述语:byte,word,doubleword/dword(32bits),quadword(64bits),double-quadword/octword(128bits)
XMM
1.3 四种指令集
1) General-Purpose指令;
2) SSE指令 - 高性能,适用于操作大块数据的媒体/科学计算;
3) MMX指令 – 包括MMX和AMD 3D Now! 两种技术,不适用于高精度计算,不支持
异常;AMD已渐渐淡化3D Now!技术,推荐使用SSE技术取代;
4) X87浮点指令 – 和MMX共用寄存器。
SSE和MMX称为媒体指令(Media Instructions),二者都使用SIMD向量化技术,技术特点:
Ø 单个向量寄存器可存放多份独立数据;例如,YMM0可以存放8个32bit单精度浮点数;
Ø 向量指令可以并行处理向量寄存器中的多份独立数据;例如,PADDB将2个XMM寄存器的16个字节并行相加,最终同时返回16个独立的字节的和。
向量(Vector)数据类型指的是向量化技术中的SIMD并行处理的数据。
另外,SSE和MMX指令还利用了算术饱和技术,防止数值溢出。
1.4 浮点指令
上小节提到的指令集中,SSE, MMX和X87都可以用作浮点运算:
1) SSE浮点:支持32bit单精度和64bit双精度浮点,符合IEEE-754标准;因为使用了
向量化SIMD技术,所以支持向量化数据;支持符点异常;
2) MMX浮点:同SSE一样,但不支持符点异常;
3) X87浮点:支持32bit单精度、64bit双精度和80bit扩展精度浮点,32bit单精度和64bit
双精度浮点符合IEEE-754标准;支持三角和对数等超越运算;因为没有
SIMD技术,所以只能支持标量(Scalar)数据。
2内存模型
有效地址(记EA)->虚拟地址 (线程地址,记VA)->物理地址(记PA)
(注:逻辑地址=有效地址+段选择子)
2.1 内存管理
1. 长模式
64-bit模式:无段管理;64bit VA -> 52bit PA,具体的实现可以设计更小的VA和PA;访问
虚拟空间2^64B=16EB,物理空间2^52B=4PB.
兼容模式:用户态为16bit/32bit EA,段单元将EA转成20bit/32bit VA后,然后零扩展至64bit
的VA,最终经过分页单元映射到PA;显然,兼容模式下也可以运行16bit的应
用程序;16bit/32bit EA->64bit VA->52bit PA;访问虚拟空间4GB,物理空间4PB;
2. 传统模式
保护模式:16bit/32bit EA->32bit VA->32bit PA; 访问虚拟空间4GB,物理空间4GB;
虚8086模式:16bit EA->20bit VA->32bit PA; 访问虚拟空间1MB,物理空间4GB;
实模式:16bit EA->20bit VA->20bit PA; 访问虚拟空间1MB,物理空间1MB.
2.2 内存寻址
字节序:
Little Endian, byte order, 内存和寄存器中的数据存放方式如下图。
指令举例:MOV RAX, 0x1122334455667788
内存分布:“MOV RAX”操作码值为B8h,扩展操作数前缀REX为48h;
内存地址从低到高数据:48 B8 88 77 66 55 44 33 22 11
64-bit模式的段:
64-bit模式下,DS, ES, SS段总被忽略(总是用值0来处理),但FS和GS可用于数据段基址寄存器。注:可以用段指令前缀来重新使用段。
地址大小前缀:
每种运行模式都有默认的地址大小,“地址大小前缀”可以改变某条指令所用的默认地址大小,操作码为67h;做法:截短->零扩展;此前缀不影响64-Bits模式的RIP相对寻址;
近(Near)指针和远(Far)指针:
近指针:EA
远指针:EA+Segment Selector,即逻辑地址
64-Bit模式下,没有段,因此没有远指针。
栈:
栈指针大小 - 兼容模式/传统模式下为16bits(SP)/32bits(ESP),64-bit模式下为64bits(RSP);
rBP为栈基址指针,rSP为栈指针,访问这2个寄存器时,硬件会自动访问SS段,64-bit模式下不会访问SS段。
3通用编程
3.1 64-bit模式寄存器
Ø 默认操作数大小为32 bits
Ø 指令前加上指令前缀REX后,操作数大小变成64 bits
Ø 寄存器操作时,8bit/16bit操作不扩展,32bit操作零扩展
Ø 从64-bit模式切换到兼容模式或传统模式,高32位的值不会保存(未定义)
Ø 指令前加上操作数大小(Operand-Size)前缀66h后,操作数大小变成16 bits
rFLAGS寄存器格式:
(reset值为0x20)
3.2操作数
BCD(Binary Coded Decimal) Digits
Ø Unpacked类型:0~9,共4个bit;对于byte为单位来讲,浪费了4个bit;AA? 调整
Ø Packed类型:0~99,共8个bit,刚好一个byte; DA? 调整
Ø 需要AA?/DA?指令调整成正确的二进制值;加减乘后调整,除前调整值。
Strings
Ø 一串同种数据类型
Ø 例如:Bit Strings, Char Strings, …
操作数大小
Operand-Size
Default Operand Size + Instruction Prefix -> Effective Operand Size
64-bit模式下,与寄存器操作的不扩展或零扩展相比,用MOV操作立即数时会做符号扩展;
3.3数据对齐
AMD64不要求数据对齐,但不对齐会影响性能。提高性能的惯例:
1) 数据对齐访问
2) load和store操作使用相同大小的操作数;使用大单位(doword/qword)操作数更高效;
3) 存储byte/word大小数据时,再次访问数据时使用存操作地址处的dword数据。
3.4 I/O空间
两种类型的I/O空间
1) I/O地址空间: I/O-Address Space, 0~0xFFFF,IN/OUT, INS/OUTS,低速,强序,
通常无保护,访问权限可通过rFLAGS.IOPL和TSS.IOPB设置;
2) 内存映射I/O:Memory-Mapped I/O,连在系统内存总线,内存访问,高速,可配置强/
弱序,更多I/O端口,有保护机制;,访问权限可通过页表映射设置。
3.5 内存优化
AMD64允许指令的乱序执行(Excute Out-Of-Order)以及预测(Speculative)执行来提高性能,指令乱序执行,但按程序顺序生效(commit in program order)。
为进一步提高性能,指令的乱序执行就需要读操作的乱序发生,指令的预测执行也需要读操作的预测读。
3.5.1内存乱序
读:
Ø 支持乱序读
Ø 支持预测读
Ø 支持乱序优先于写 – 读操作数据不存在时会造成流水线停止,优先级高于写;但当读
操作依赖于之前的写操作时,不能乱序优先于写。
写:
写操作影响程序顺序(Program Order),不允许发生乱序;写指令在之前所有指令完成后,才能生效(commit)。
Ø 不支持乱序写
Ø 不支持预测写
强序(Strongly Ordered)
不会发生乱序
弱序(Weakly Ordered)
会发生乱序
原子操作:
单条load/store指令为原子操作,但如果发生未对齐访问,就会变成非原子操作。
lock前缀会保证原子执行。
3.5.2避免乱序
1. FENCE指令
LFENCE:之前的所有读指令都先于之后所有读指令完成
SFENCE:之前的所有写指令都先于之后所有写指令完成 //事实上AMD64不支持写乱序
MFENCE:之前的所有读/写指令都都先于之后所有读/写指令完成
2. 串行指令
应用程序可用的串行指令为CPUID和IRET,此类指令在以下条件完成后生效:
Ø 之前所有的指令重新从内存中取
Ø 与本指令有关的所有的内存和寄存器都已更新
Ø Wirte Buffer中的所有数据都更新至内存中
3. I/O指令
Ø IN/INS:之前所有内存及I/O操作完成后才会执行 // 允许发生本指令后的乱序
Ø OUT/OUTS:之前所有内存,I/O操作以及当前指令都完成后,后续指令才允许执行
4. LOCK前缀
Ø 等待之前所有的读/写指令完成
Ø 本指令完成后,后续读/写指令才能执行
3.5.3 Cache
内存层次
Main Memory -> External Caches(L3) -> Internal Caches(L2, L1 Data, L1 Instruction)
Internal Caches位于理器芯片内部(Processor Chip),Write Buffers连在Internal Caches上,用于Internal Caches和External Caches/Main Memory之间的数据通讯。
一条Cache Line通常为32B或64B,Cache Line是操作时的最小单元,即使访问一个字节也会取完一条Cache Line的数据。
Cache一致性
AMD64 maintains coherency between caches and memory遵循MOESI协议。
自更改代码(Self-Modifying Code)
改写代码段数据的行为称为自更改代码,此种情况下AMD64的实现硬件会自动Invalidate更改位置相关的指令Cache,而不用软件参与(相比,mips架构一般需要软件去Invalidate相
关指令Cache)。产生自更改代码行为的指令操作数(即被更改的指令)涉及的是数据Cache.
自更改代码对于别的CPU称为”Cross-Modifying Code”,由于CPU间的并发性,指令数据需要保持同步(如使用信号量)。
Cache污染
Temporal Locality:短期内用多次
Spatial Locality:马上就要用到
造成Cache污染的类型有:Non-Temporal Locality和Stale(过时的) Cache;
出于性能考虑,应避免Cache污染:
1) PREFETCHNTA/MOVNTI等指令,避免Non-Temporal Locality类型的Cache污染;
2) CLFLUSH指令可以处理Stale Cache类型的Cache污染,它会invalidate所有级别的Cache
Line以及本地Write Buffers,对于脏数据会先做一次Write back,此指令会发生乱序。
3.6性能因素
尤其在设计编译器时请注意:
Ø 使用大单位操作数
Ø 使用短指令
Ø 数据对齐
Ø 避免过多分支
Ø 大块数据的预取
Ø 尽量使用寄存器
Ø 避免指令相关性
Ø 避免Load/Store相关性
Ø 优化栈分配
Ø 小心使用Rep前缀
Ø 尽量使用Media指令
Ø 数据尽量组织成内存块(更好利用Cache Line和Prefetch)