Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1725899
  • 博文数量: 98
  • 博客积分: 667
  • 博客等级: 上士
  • 技术积分: 1631
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-27 15:59
个人简介

一沙一世界 一树一菩提

文章分类

全部博文(98)

文章存档

2021年(8)

2020年(16)

2019年(8)

2017年(1)

2016年(11)

2015年(17)

2014年(9)

2013年(4)

2012年(19)

2011年(1)

2009年(4)

分类: C/C++

2019-12-22 12:16:43

首先说说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) |
给主人留下些什么吧!~~