IA32暫存器與x86-64暫存器的區別
IA32暫存器
一個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個),如果函式中引數或區域性變數過多,暫存器就不夠用了,所以要把暫存器中儲存的值儲存到棧中防止資料丟失,然後該暫存器就可以用來儲存別的資料了,那把這個活交給誰呢?所以就規定前三個暫存器由呼叫者儲存,接下來的三個由被呼叫者儲存。
而暫存器%ebp和%esp有特殊用途,%ebp儲存幀指標(基地址,base pointer),%esp儲存棧指標(stack pointer)。程式棧中每呼叫一個函式就會建立一個棧幀,棧是向下增長的。暫存器%ebp儲存一個棧幀的起始地址(即基地址),一般來說函式中的區域性變數靠%ebp加上偏移量尋找;暫存器%esp儲存指向棧頂的指標。
暫存器%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)。這些暫存器在作業系統中都是很重要的,在這裡不再贅述,想知道的可以上網查查。
x86-64暫存器
x86-64最初由AMD提出並命名,將IA32擴充套件到64位並且新增加了8個暫存器,到達16個通用目的暫存器。這大大提高了機器的效能。擴充套件到64位可以大大擴大機器可以使用的虛擬地址空間的大小。x86-64的暫存器無論只在數量上還是在使用方法上都與IA32有很大不同,先看下錶:
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支援的基於棧的方法來實現。