Chinaunix首页 | 论坛 | 博客
  • 博客访问: 414137
  • 博文数量: 49
  • 博客积分: 1346
  • 博客等级: 中尉
  • 技术积分: 936
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-21 01:49
文章分类

全部博文(49)

文章存档

2013年(7)

2012年(12)

2011年(30)

我的朋友

分类: LINUX

2011-11-05 22:28:57

1 应用编程

 

前言

本卷讲解AMD64基本架构,内存组织结构以及四种应用编程模型。

       四种应用编程模型为:

       1) 通用编程(General-Purpose Programming)

         16GPRs的使用,程序异常,分支控制,I/O,内存优化;

2) SSE编程(Streaming SIMD Extensions Programming)

  YMM/XMM寄存器(256bit/128bit),支持向量(Vector)/标量(Scalar)数据类型的整数/

点运算;

3) MMX编程(MultiMedia Extensions Programming)

  64bitMMX寄存器,支持向量/标量数据类型的整数/浮点运算;

4) X87浮点编程(X87 Folating-Point Programming)

  80bitX87寄存器,支持标量数据类型的浮点运算。

 

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即传统的32X86 CPU,上电启动时AMD64 CPU处于Legacy Real Mode

长模式的兼容模式支持32bit16bit,相当于Legacy Protected Mode.

1架构综述

       AMD64架构设计原因:无缝兼容legacy x86,更大的寻址空间和高性能(尤其是大块数据操作)。表现为更多的寄存器,64bit的寄存器,64bit的虚拟地址和物理地址。

1.1 新特性

Ø  增加了8GPRs,扩展了原来的8GPRs,共16GPRs

Ø  16GPRs皆为64bit

Ø  在原来8个的基础上再增加8YMM/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使用了YMM的低Octword即低128bits

1.3 四种指令集

1) General-Purpose指令;

2) SSE指令 - 高性能,适用于操作大块数据的媒体/科学计算;

3) MMX指令 包括MMXAMD 3D Now! 两种技术,不适用于高精度计算,不支持

异常;AMD已渐渐淡化3D Now!技术,推荐使用SSE技术取代;

4) X87浮点指令 MMX共用寄存器。

 

SSEMMX称为媒体指令(Media Instructions),二者都使用SIMD向量化技术,技术特点:

Ø  单个向量寄存器可存放多份独立数据;例如,YMM0可以存放832bit单精度浮点数;

Ø  向量指令可以并行处理向量寄存器中的多份独立数据;例如,PADDB2XMM寄存器的16个字节并行相加,最终同时返回16个独立的字节的和。

 

向量(Vector)数据类型指的是向量化技术中的SIMD并行处理的数据。

 

另外,SSEMMX指令还利用了算术饱和技术,防止数值溢出。

1.4 浮点指令

上小节提到的指令集中,SSE, MMXX87都可以用作浮点运算:

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,具体的实现可以设计更小的VAPA;访问

虚拟空间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,扩展操作数前缀REX48h

内存地址从低到高数据:48 B8 88 77 66 55 44 33 22 11

 

64-bit模式的段:

64-bit模式下,DS, ES, SS段总被忽略(总是用值0来处理),但FSGS可用于数据段基址寄存器。注:可以用段指令前缀来重新使用段。

 

地址大小前缀

       每种运行模式都有默认的地址大小,“地址大小前缀”可以改变某条指令所用的默认地址大小,操作码为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,共4bit;对于byte为单位来讲,浪费了4bitAA? 调整

Ø  Packed类型:0~99,共8bit,刚好一个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) loadstore操作使用相同大小的操作数;使用大单位(doword/qword)操作数更高效;

3) 存储byte/word大小数据时,再次访问数据时使用存操作地址处的dword数据。

3.4 I/O空间

两种类型的I/O空间

1) I/O地址空间 I/O-Address Space, 0~0xFFFFIN/OUT, INS/OUTS,低速,强序,

通常无保护,访问权限可通过rFLAGS.IOPLTSS.IOPB设置;

2) 内存映射I/OMemory-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. 串行指令

应用程序可用的串行指令为CPUIDIRET,此类指令在以下条件完成后生效:

Ø  之前所有的指令重新从内存中取

Ø  与本指令有关的所有的内存和寄存器都已更新

Ø  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 CachesExternal Caches/Main Memory之间的数据通讯。

一条Cache Line通常为32B64BCache 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 LocalityStale(过时的) 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 LinePrefetch)

阅读(4179) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~