首先说说x86,x64,x86-64的区别:
个人认为什么x86,x64.....一类的术语都是cpu硬件系统架构,由于架构不同,也就对应不同的指令集,所以我们一般也就说指令集是x86,x64.....可能理解或者描述有误,反正就是那么一个硬件和指令集一一对应的一堆吧。
以下内容都来源于伟大的internet,也有自己的一些见解和一些例子。
x86是指intel的开发的一种32位指令集,从386开始时代开始的,一直沿用至今,是一种cisc指令集,所有intel早期的cpu,amd早期的cpu都支持这种指令集,intel官方文档里面称为“IA-32”
x84_64是x86 CPU开始迈向64位的时候,有2选择:1、向下兼容x86。2、完全重新设计指令集,不兼容x86。AMD抢跑了,比Intel率先制造出了商用的兼容 x86的CPU,AMD称之为AMD64,抢了64位PC的第一桶金,得到了用户的认同。而Intel选择了设计一种不兼容x86的全新64为指令集,称 之为IA-64(这玩意就是安腾),但是比amd晚了一步,而且IA-64也挺惨淡的,因为是全新设计的CPU,没有编译器,也不支持 windows(微软把intel给忽悠了,承诺了会出安腾版windows server版,但是迟迟拿不出东西)。。。后来不得不在时机落后的情况下也开始支持AMD64的指令集,但是换了个名字,叫x86_64,表示是x86 指令集的64扩展,大概是不愿意承认这玩意是AMD设计出来的。
也就是说实际上,x86_64,x64,AMD64基本上是同一个东西,我们现在用的intel/amd的桌面级CPU基本上都是x86_64,与之相对的arm,ppc等都不是x86_64。
x86、x86_64主要的区别就是32位和64位的问题,x86中只有8个32位通用寄存器,eax,ebx,ecx,edx, ebp, esp, esi, edi。x86_64把这8个通用寄存器扩展成了64位的,并且比x86增加了若干个寄存器(增加了8个,变成了总共16个通用寄存器)。同样的 MMX的寄存器的位数和数量也进行了扩展。此外cpu扩展到64位后也能支持更多的内存了,等等许多好处。
对于普通程序来说,CPU位数的扩展、寄存器数量的增加不会带来明显的性能提升,比如IE浏览器、Office办公这类的软件。特定的程序很能够充 分利用64位CPU、更多的寄存器带来的优势,比如MMX除了能提升多媒体程序的性能,对矩阵、多项式、向量计算都能带来提升,更多的MMX寄存器、更大 的寄存器字长都有利于SIMD指令的执行,能够提升CPU对数据的吞吐量(RISC指令集的CPU动不动就有数百个寄存器,可以有效的缓存中间计算结果, 不需要把中间结果写入内存,从而减少内存访问次数,显著提升性能)
另外这里也顺便说下ia64:以下内容来自baidu。
IA64,又称英特尔安腾架构(Intel Itanium architecture),使用在处理器家族上的,由英特尔公司与共同开发,2001年首次推出。
其实ia64的历史早于x86-64,最初由Intel和惠普于1990年联合推出。由于ia64不与32位兼容,所以没有受到重视。而后为了日益扩张的计算需求,重新将ia64拿出来,发布了系列。ia64是一种崭新的系统,和完全没有相似性,不应该把它与x86-64或x64弄混。基于ia64处理器架构的服务器具有64位运算能力、64位和64位,突破了传统的许多限制,在数据的处理能力,系统的稳定性、安全性、可用性、可管理性等方面获得了突破性的提高。它是Intel自推出32位微处理器以来,在高性能计算机领域的又一座里程碑。
x64和ia64处理器都能够运行和应用程序,但是区别在于:x64架构基于x86,是为了让CPU兼容而产生的技术。x64架构的设计是采用直接简单的方法将目前的x86指令集扩展。这个方法与当初的由16位扩展至32位的情形很相似。优点在于用户可以自行选择x86平台或x64平台,兼容性高。ia64则是原生的纯64位计算处理器,并且与指令不兼容。如果想要执行x86指令需要支持,而且效率不高。优点在于ia64架构体系将拥有64位能力,能够支持更大的内存寻址空间。并且由于架构的改变,性能比起x64的64位兼容模式更高更强。所以,ia64操作系统也是比较少见的,由于只能在系列处理器及AMD部分服务器处理器运行,所以主流市场并不常见。而且,这些ia64架构处理器也不能够使用x64操作系统。而x64处理器则可以自由选择x86或是x64操作系统。
下面进入正题,下面所说的所有内容都是使用GNU GCC编译器。GUN GCC使用传统的AT&T语法,它在Unix-like操作系统上使用,而不是dos和windows系统上通常使用的Intel语法。如:
movq %rsp, %rbp
movq是一个最常见的汇编指令的名称,百分号表示rsp和rbp是寄存器,在AT&T语法中,有两个参数的时候,始终先给出源(source),然后再给出目标(destination)。
在其他地方(例如英特尔手册),您将看到英特尔语法,区别之处是Intel语法省去了百分号并颠倒了参数的顺序。
在阅读手册和网页时,通过看有没有”%”就知道是用的哪种汇编格式了。
X86-64中,所有寄存器都是64位,相对32位的x86来说,标识符发生了变化,比如:从原来的%ebp变成了%rbp。为了向后兼容性,%ebp依然可以使用,不过指向了%rbp的低32位。下面的代码分析中,会有用到带“r”的寄存器,说明是64位操作,带“e”的寄存器,说明是32位操作。
让寄存器为己所用,就得了解它们的用途,这些用途都涉及函数调用,X86-64有16个64位寄存器,分别是:%rax,%rbx,%rcx,%rdx,%esi,%edi,%rbp,%rsp,%r8,%r9,%r10,%r11,%r12,%r13,%r14,%r15。其中:
-
%rax 作为函数返回值使用。
-
%rsp 栈指针寄存器,指向栈顶
-
%rbp 栈桢指针,指向栈基
-
%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
-
%rbx,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改
-
%r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值
-
%rip: 相当于PC指针指向当前的指令地址
下面说说%esp(rsp)和%ebp(rbp),这是两个和堆栈有关的寄存器。 esp是栈指针,是cpu机制决定的,push、pop指令会自动调整esp的值,ebp只是存取某时刻的esp,这个时刻就是进入一个函数内后,cpu会将esp的值赋给ebp,此时就可以通过ebp对栈进行操作,比如获取函数参数,局部变量等。下面内容是出自某博主的文笔,感觉描述比较实在:
EBP是当前函数的存取指针,即存储或者读取数时的指针基地址;ESP就是当前函数的栈顶指针。每一次发生函数的调用(主函数调用子函数)时,在被调用函数初始时,都会把当前函数(主函数)的EBP压栈,以便从子函数返回到主函数时可以获取EBP。
下面是按调用约定__stdcall 调用函数test(int p1,int p2)的汇编代码
假设执行函数前堆栈指针ESP为0xAAAAAAA ;EBP为0xAAAAAB0
push p2 //参数2入栈, ESP -= 4h , ESP = 0xAAAAAAA - 4h = 0xAAAAAA6
push p1 //参数1入栈, ESP -= 4h , ESP = 0xAAAAAAA - 8h = 0xAAAAAA2
call test //压入返回地址 ESP -= 4h, ESP = 0xAAAAAAA- 0Ch = 0xAAAAA9E,注意:这里是test函数的返回地址,即在代码段中的地址(偏移)。
//进入函数内
{
pushl %ebp //保护先前EBP指针, EBP入栈(即0xAAAAAB0入栈,注意与返回地址区别), ESP-=4h, ESP = 0xAAAAA9A
movl %esp, %ebp //设置EBP指针指向栈顶 0xAAAAA9A
movl 12(%ebp), %eax //ebp+12为0xAAAAAA6即参数2的位置
movl 8(%ebp), %ebx //ebp+8为0xAAAAAA2,即参数1的位置
sub $8, %esp //局部变量所占空间ESP-=8, ESP = 0xAAAAA92
...
add %8, %esp //释放局部变量, ESP+=8, ESP = 0xAAAAA9A
popl %ebp //出栈,恢复EBP, ESP+=4, ESP = 0xAAAAA9E,即把栈中地址0xAAAAA9A的内容pop到ebp中
ret 8 //ret返回,弹出返回地址,ESP+=4, ESP=0xAAAAAA2, 后面加操作数8为平衡堆栈,ESP+=8,ESP=0xAAAAAAA, 恢复进入函数前的堆栈.
}
看完汇编后,再看EBP和ESP的定义,哦,豁然开朗,
原来ESP就是一直指向栈顶的指针,而EBP只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。
另外,如果在汇编代码里看到call foo这样的语句,其实相当于下面2句的意思:
Pushl %rip //保存下一条指令(第41行的代码地址)的地址,用于函数返回继续执行
Jmp foo //跳转到函数foo
而看到ret,则相当于:
popl %rip //恢复指令指针寄存器
汇编代码里的call和ret对应子函数的入口和出口。
有时候也会在ret前面有一句leave语句,这也是恢复现场语句,相当于下面两句的意思:
Movl %ebp %esp //撤销栈空间,回滚%esp --- 回到当前帧的栈基位置
Popl %ebp //恢复上一个栈帧的%ebp
再来说说参数传递
X86时代,参数传递是通过入栈实现的,相对CPU来说,存储器访问太慢;这样函数调用的效率就不高,在x86-64时代,寄存器数量多了,GCC就可以利用多达6个寄存器来存储参数,多于6个的参数,依然还是通过入栈实现。了解这些对我们写代码很有帮助,起码有两点启示:
-
寄存器操作速度速度远大于栈操作速度,所以尽量使用6个以下的参数列表
-
传递大对象,尽量使用指针或者引用
阅读(1866) | 评论(0) | 转发(0) |