转自:
一个IA32CPU包含一组8个存储32位值的通用寄存器,这些寄存器用来存储整数数据和指针:
31-0 | 15-0 | 15-8 | 7-0 | 使用惯例 |
---|---|---|---|---|
%eax | %ax | %ah | %al | 调用者保存 |
%ecx | %cx | %ch | %cl | 调用者保存 |
%edx | %dx | %dh | %dl | 调用者保存 |
%ebx | %bx | %bh | %bl | 被调用者保存 |
%esi | %si | 无 | 无 | 被调用者保存 |
%edi | %di | 无 | 无 | 被调用者保存 |
%ebp | %bp | 无 | 无 | 不得占用 |
%esp | %sp | 无 | 无 | 不得占用 |
第一行数字代表其下方对应的寄存器存储的位数,例如寄存器%eax是32位的,从0到31正好32位,%ax是16位寄存器,从0到15正好是16位。注意,如果读取%eax的8-15位则读取的是%ah,其他情况类似。
程序可以独立地读取前四个寄存器的2个低位字节,即可以独立地读取前四个寄存器的0-7位和8-15位。而后四个则不能。表中无代表对应位置无寄存器。
其中0-7位和8-15位的寄存器可以存储一个字节的数据,如char型数据;0-15位的寄存器可以存储两个字节的数据,如short类型的数据;0-31位的寄存器可以存储四个字节的数据,如int型数据。还有如long类型的数据可以用寄存器%edx存储高32位,用%eax存储低32位的数据。
寄存器%eax,%ecx和%edx由调用者保存,而寄存器%ebx,%esi和%edi由被调用者保存。就是说如果一个函数调用另一个函数,由于寄存器数量有限(只有8个),如果函数中参数或局部变量过多,寄存器就不够用了,所以要把寄存器中保存的值保存到栈中防止数据丢失,然后该寄存器就可以用来存储别的数据了,那把这个活交给谁呢?所以就规定前三个寄存器由调用者保存,接下来的三个由被调用者保存。
寄存器%eax和寄存器%edx也有特殊用途,%eax一般用于保存函数的返回值,也用来保存64位数据的低32位;%edx用来保存64位数据的高32位。
CPU中只有这八个寄存器吗?显然不是,IA32还有8个80位的浮点寄存器。还有很多特殊用途的寄存器,比如控制寄存器%cr0,%cr2,%cr3和%cr4;还有debug寄存器%dr0,%dr1,%dr2和%dr3;段寄存器%cs, %ds, %es, %fs, %gs和%ss;还有全局和局部描述符表的虚拟寄存器%gdtr(global descriptor table register)和%ldtr(local descriptor table register)。这些寄存器在操作系统中都是很重要的,在这里不再赘述,想知道的可以上网查查。
0-63 | 0-31 | 0-15 | 8-15 | 0-7 | 使用惯例 |
---|---|---|---|---|---|
%rax | %eax | %ax | %ah | %al | 保存返回值 |
%rbx | %ebx | %bx | %bh | %bl | 被调用者保存 |
%rcx | %ecx | %cx | %ch | %cl | 第4个参数 |
%rdx | %edx | %dx | %dh | %dl | 第3个参数 |
%rsi | %esi | %si | 无 | %sil | 第2个参数 |
%rdi | %edi | %di | 无 | %dil | 第1个参数 |
%rbp | %ebp | %bp | 无 | %bpl | 被调用者保存 |
%rsp | %esp | %sp | 无 | %spl | 栈指针 |
%r8 | %r8d | %r8w | 无 | %r8b | 第5个参数 |
%r9 | %r9d | %r9w | 无 | %r9b | 第6个参数 |
%r10 | %r10d | %r10w | 无 | %r10b | 调用者保存 |
%r11 | %r11d | %r11w | 无 | %r11b | 调用者保存 |
%r12 | %r12d | %r12w | 无 | %r12b | 被调用者保存 |
%r13 | %r13d | %r13w | 无 | %r13b | 被调用者保存 |
%r14 | %r14d | %r14w | 无 | %r14b | 被调用者保存 |
%r15 | %r15d | %r15w | 无 | %r15b | 被调用者保存 |
上表的格式说明和IA32一样,可以看到x86-64共有16个寄存器(16行),可以独立访问前四个寄存器的8-15位,而其他寄存器不可以。
最后一列说明该寄存器的使用惯例,可以看到如果函数参数不超过6个则可以保存在寄存器中而不用转存在内存中,如果函数的参数的个数超过6个则其余的参数须保存在栈中。
还可以看见,x86-64并没有帧指针,而只用栈指针(%rsp),作为替代,x86-64对栈位置的引用相对于栈指针(而IA32相对于帧指针%ebp),大多数函数在调用开始时分配所需要的整个栈存储,并保持栈指针指向固定的位置。函数最多可以访问当前栈指针值128个字节的栈上的存储空间,也就是说x86-64的程序可以使用当前栈指针之外128字节范围内的数据。
在IA32中栈指针会随着值的压入和弹出不断前后移动,所以在IA32中通过帧指针(%ebp)来访问栈中的数据。但是x86-64过程中的栈帧通常有固定的大小,在函数开始时通过减少栈指针(%rsp)来设置。在调用过程中栈指针保持固定的位置,使得可以通过相对于栈指针的偏移量来访问数据。因此就不再需要帧指针了,可以把%rbp用作通用寄存器。
x86-64主要特性如下:
1.指针和长整数是64位长(IA32的指针是32位长),整数算术运算支持8,16,32,和64位数据类型。
2.通用目的寄存器由8个扩展到16个。
3.许多程序状态都保存在寄存器中而不是栈上(因为寄存器数量多了)。整型和指针类型的过程参数(最多6个)通过寄存器传递。减少了对栈的访问,从而提高了效率。
4.如果可能,条件操作用条件传送指令实现,这样会比传统的分支代码得到更好的性能。
5.浮点操作用面向寄存器指令集来实现,而不用基于IA32支持的基于栈的方法来实现。