附錄二 80x86 及 80x87 暫存器


8086 暫存器

暫存器是在 CPU 中一個暫時儲存資料的地方。它有點兒像記憶體 ( DRAM ),但是不像記憶體這麼大,一個暫存器只有一個、兩個或四個位元組的大小,到了 AMD64、INTEL64 ( 或合稱 x86-64 ) 的 CPU 時代,暫存器才有四字組 ( QWORD,共 64 位元 ) 的大小。CPU 可以直接對暫存器作加、減、乘、除、且、或、搬移等運算。8086/8088 共有 14 個 16 位元的暫存器,其名稱都以兩個英文字表示,大致可分為以下四類。

1.通用暫存器

共有四個,其名稱分別是 AX、BX、CX、DX,在組合語言程式中大致沒有太大的差別,但是其中只有 AX ( accumulator,也稱為累加器) 可作為除法或乘法中的被除數與被乘數,當 16 位元不夠大時,常常用 DX:AX 來表示 32 位元。此外這四個暫存器,只有 BX ( base register,也稱為基底暫存器) 可以被作為位址存取之用。CX 也稱為計數暫存器 ( count register),用於計算迴圈之次數或字串處理之次數。DX 也稱為資料暫存器 ( data register),可用來存取埠。

這四個暫存器也可以分成兩個 8 位元的暫存器來使用,例如 AX 可被分成較低的 8 位元稱為 AL,以及較高的 8 位元 AH 來使用。其餘 BX、CX、DX 也都類似。

2.指標與索引暫存器

有五個,其名稱分別是 SP、BP、IP、SI、DI。前面兩個 SP (stack pointer,稱為堆疊指標)與(base pointer,也稱為基底指標)是與堆疊(stack)有關的暫存器,請參考第五章內容有關堆疊的部份,以獲得詳細的堆疊資料。堆疊是一塊區域,用來暫時存放資料之用,在 8086/8088 中,堆疊是由最高位址中開始存放,每次都必須存入一個字組的長度,並用一組指標,來表示堆疊已經使用到那兒了,這組指標就是 SS:SP。也就是說,當成是要將資料存入堆疊時,該資料應該存放在 SS:SP 所指的位址再低 2 個位元組,然後 CPU 再使 SP 之內容減 2,使 SP 再指到下一個未使用的空間。

那什麼情形會要將資料存入堆疊內呢?有好幾種情形,例如呼叫副程式時,會預先把返回位址存入堆疊﹔呼叫中斷時也是如此。BP 通常用於呼叫副程式時,傳遞參數之用。

IP (instruction pointer,稱為指令指標) 配合 CS 變成 CS:IP,指向將要執行的 8086/8088 位址。當 CPU 要執行程式時,必須到記憶體去提取要執行的指令,而要到那一個記憶體位址去提取指令呢?這時 CPU 就會到 CS:IP 指到的位址去提取。在程式中,一般是沒有辦法改變 CS:IP 的值,除非是跳躍 (jmp、jz等) 指令或是呼叫 (call、ret等) 指令。

SI (source index,稱為來源索引暫存器) 和 DI (destination index,稱為目的索引暫存器) 通常是用來當作位址指標,也可用作加減法。這五個暫存器,每一個都不能分開來當作兩個 8 位元的暫存器使用。

3.區段暫存器

有 CS、DS、ES、SS 四個,分別表示程式碼(code segment register)、資料(data segment register)、額外(extra segment register)、堆疊(stack segment register)區段之用。在 DOS 系統中,每一個區段容量只有 64KBytes。

當資料區段不夠用時,就可以用額外區段來補足,例如想要將一個區段的某些內容複製到另一區段中,就可以同時指定 DS、ES 分別表示這兩個區段。

4.旗標暫存器

旗標暫存器 (flag register) 是一個 16 位元的暫存器,但只有其中九個位元有用到,它們分散在這十六個位元中,採用這種分散方式是為了與舊式的 8080 CPU 的旗標相同,其分布如下圖所示:

這 9 個旗標可分為三類:狀態旗標 ( status flag )、控制旗標 ( control flag ) 與系統旗標 ( system flag )。狀態旗標包含 CF、PF、AF、ZF、SF 和 OF,它們會受到算術、比較或邏輯運算結果狀態的影響,而使旗標被設定 ( set,其值為 1 ),或被清除 ( clear,其值為零 )。第 10 位元的 DF 用來控制掃描、搬移、搜尋字串的方向,屬於控制旗標。至於 IF、TF 為設計作業系統、除錯時才需要用到,屬於系統旗標,一般應用程式很少使用。底下簡單介紹這些旗標:

名稱位元 狀態說明
進位旗標
carry flag
CF
0 CF=1,CY
CF=0,NC
當運算發生進位或借位時,CF 被設為 1;反之設為 0。例如兩數相加:
        mov     ax,8000h
        add     ax,8000h
8000h+8000h 應為 10000h,發生進位,雖然 AX 為 0,但是會使 CF 設為一,表示進位。
同位旗標
parity flag
PF
2 PF=1,PE
PF=0,PO
運算的結果換成二進位後,最低的 8 個位元中,若有偶數個 1,則此位元設為 1,反之為 0。
輔助進位旗標
auxiliary carry flag
AF
4 AF=1,AC
AF=0,NA
當運算過程中,第 3 位元與第 4 位元之間發生進位或借位時,AF 被設為 1,否則被設為 0,常用於 BCD 的運算。
零旗標
zero flag
ZF
6 ZF=1,ZR
ZF=0,NZ
運算結果為零時,ZF 會被設定為 1。若比較相同兩數, ZF 也會被設為一,若比較不相同的兩數,ZF 會被清除為零。例如:
        mov     ax,8000h
        add     ax,8000h
相加後,AX 為零,故 ZF 設為一。
符號旗標
sign flag
SF
7 SF=1,NG
SF=0,PL
運算結果的最高位元為 1 時,SF 會被設為 1 ( 表示負數 ),否則被清除。
陷阱旗標
trap flag
TF
8   用於單步追蹤除錯時,所以也稱為追蹤旗標 ( trace flag ),例如在 MS-DOS 的 DEBUG 中,就是利用 TF 達到單步追蹤的目的。當 TF 設為一時,每執行一個指令便會發生中斷,此中斷就將執行該指令後暫存器列出。
中斷旗標
interrupt flag
IF
9 IF=1,EI
IF=0,DI
當 IF 被設定時,可遮罩的硬體中斷才能使 CPU 產生中斷效果;反之所有可遮罩的硬體中斷均會被抑制。但是不可遮罩中斷以及 CPU 產生的例外卻完全不受 IF 影響。
方向旗標
direction flag
DF
10 DF=1,DN
DF=0,UP
當 DF 被清除時 ( 即 DF=0 ),處理字串的索引暫存器會遞增,往高位址方向處理,反之則遞減。
溢位旗標
overflow flag
OF
11 OF=1,OV
OF=0,NV

當運算結果無法容納於目的運算元中,此旗標會被設為一。大致可分三種情形:(1)兩同號數相加或兩異號數相減,(2)乘除運算時,所得的積或商超過運算元存放範圍,在移位或旋轉指令時,最高位元值受到更動 ( 0 變成 1 或 1 變成 0 )。( 詳細情形請參閱第 35 章溢位。 )

上面的表說明旗標暫存器的意義。以旗標暫存器的位元 0 為例,位元 0 稱為『進位旗標』,當它被設定時,其值為 1,在 DEBUG 堨H CY 表示;被清除時,其值為 0,在 DEBUG 堨H NC 表示。底下列出 DEBUG 中執行 r 指令後的意義:

-r [Enter]
AX=0000  BX=0000  CX=0025  DX=0000  SP=FFFE  BP=0000  SI=0000  DI=0000
DS=10F7  ES=10F7  SS=10F7  CS=10F7  IP=0100   NV UP EI PL NZ NA PO NC
10F7:0100 EB19           JMP    0119

白色的部分就是旗標的狀態,請看下表:

旗標名稱 設定(1)清除(0)
CF,進位(是/否)
PF,同位 ( 偶數/奇數 )
AF,輔助進位 ( 是/否 )
ZF,零 ( 是/否 )
SF,符號 ( 是/否 )
IF,中斷 ( 允許/抑制 )
DF,方向 ( 遞減/遞增 )
OF,溢位 ( 是/否 )
CY
PE
AC
ZR
NG
EI
DN
OV
NC
PO
NA
NZ
PL
DI
UP
NV


80x87 暫存器

最早使用在 IBM PC 及相容機種的 FPU 是 8087,後來又分別有進階的 80287、80387,她們合稱為 x87。x87 共有五類暫存器,它們是堆疊暫存器 ( register stack )、狀態字組 ( status word )、控制字組 ( control word )、標籤字組 ( tag word )、例外指標 ( exception pointer )。

1.堆疊暫存器

x87 共有八個堆疊暫存器,其名稱是 ST(0)、ST(1)、ST(2)……ST(7),其中 ST(0) 被稱為堆疊頂 ( TOS,Top Of Stack ),在組合語言中也可簡寫成 ST。這八個堆疊暫存器每一個都有 80 位元的大小來存放浮點數,並且以暫時實數的形態存放,可以說相當的準確。當 x87 執行 FLD、FILD 等指令,從記憶體中的整數、短實數等不同格式載入數值時,x87 能自動轉換成暫時實數格式到堆疊暫存器堙F當 x87 遇到像 FIST、FST 等指令,要把堆疊暫存器內的數值存入記憶體中,x87 會自動轉換成所指定的格式。

x87 可以執行許多複雜的計算,如求正弦、餘弦、對數……等運算,都是以堆疊暫存器內的暫時實數為運算的資料,因此也稱為資料暫存器 ( data register )。有許多的運算都是會牽涉到 TOS ,有時也有 TOS 和其他堆疊暫存器做運算,所以 TOS 常常是可以省略,程式設計師得小心這種『隱含』的寫法,以免造成困擾。

這 8 個堆疊暫存器的名稱,ST(0)、ST(1)、ST(2)……等等,並不是固定指某個堆疊暫存器。而是由狀態字組 ( 底下說明 ) 的三個 ST 位元指定堆疊頂,而下一個堆疊暫存器就是 ST(1)、再下一個就是 ST(2)……。

2.控制字組

這個 16 位元的暫存器決定了 x87 對不同例外條件的處理、如何捨入、控制實數精確度等等。控制字組的各個欄位如下圖所示:

x87 控制字組
對於控制字組說明如下:
  1. X ( 也稱為 IC,infinity control bit,無限大控制位元 ):8087、80287 有兩種方式可以對『無窮大』與有限數作比較,一種是把正無窮大與負無窮大看成數線上的兩端,沒有數比正無窮大還大,也沒有數比負無窮大還小,這樣的方式下有限數是可以和正、負無窮大比較。另一種是把正、負無窮大看成同一點,相當於把數線繞合,這時有限數不可以和正、負無窮大比較。前者稱『affine closure』,IC 設為 1;後者稱『projective closure』,IC 設為 0,這種方式也是 FINIT 後的內定值。在後來的 FPU 堙A並無太大用處,但仍保留下來,為了與 8087、80287 相容。

  2. RC ( rounding control bits,捨入控制位元 ):第 10、11 位元是用來決定如何做捨入動作的。
    RC捨入控制 說明例子
    00四捨五入 向最近的整數
    逢四捨去,遇五進位
    4.5 ==> 5
    -4.5 ==> -5
    01向負無窮大捨入 正值捨去小數部分
    負值捨去小數部分後再減一
    4.5 ==> 4
    -4.4 ==> -5
    10向正無窮大捨入 正值捨去小數部分後再加一
    負值捨去小數部分
    4.5 ==> 5
    -4.5 ==> -4
    11向零捨去 不論正負值均捨去小數部分 4.5 ==> 4
    -4.5 ==> -4


  3. PC ( precision control bits,精密度控制位元 ):這是為了配合某些電腦廠商所製造較低精密度的機器而設的,其實在 8087 內部的堆疊暫存器都是以 80 位元的精密度 ( 亦即 64 位元有效數、15 位元指數及一位元符號 ),存放及處理資料。當然,如果有效數的精密度低,處理時間就比較短,因此如果不須高精密度,而又要求快速的情形下,可以設定 PC 位元。PC 位元的表示方式是:
    00  表示 24 位元有效數
    01  保留未使用
    10  表示 53 位元有效數
    11  表示 64 位元有效數  內定值
  4. 第 0 到第 5 位元分別是處理例外時的遮罩方式,所謂例外是指 80x87 運算時發生不合法的操作、除以零、高過上限、低於下限、反常值、精確度錯誤這六種情形。當發生這六種情形的任何一種,是否要發出訊號,通知 80x86。如果要讓 80x86 知道稱之為『未遮罩』(unmasked) 此時該對應位元設為零,那麼如果發生例外時,可造成程式中斷而跳到設計者所設計的程式來處理。如果不使 80x86 知道稱為『被遮罩』,對應位元設為一,當例外發生時 80x87 能自動處理。因為 80x87 對各種例外的處理方式,可說設計得相當不錯,因此內定值設為一。在 FINIT 之後,也是把這六個位元設為 1。以下對這六種例外情形遮罩反應做說明:

IM、DM、ZM 這三個位元是 x87 在執行指令之前,就能偵測到的;OM、UM、PM 則是在指令執行後,才能偵測到。

3.狀態字組

顧名思義,這個暫存器是用來表示 80x87 狀態的,其結構如下圖:

x87 狀態字組
它包含了四項資訊。
  1. 忙碌指示器 ( busy indicator ):在第 15 位元,這個位元表示 8087 是否正在執行命令或運算,並沒太大的用處。

  2. 條件碼 ( condition code ):在第 14、10、9、8 位元,以 C3、C2、C1、C0 表示,這幾個位元會受 FTST、FCOM、FXAM 等指令的影響,一般都是用來比較大小,決定程式流程分支所用。

  3. 堆疊頂端指標 ( TOP,top of stack pointer ):第 13、12、11 位元,這三個位元是用來指示現在的堆疊頂是那一個堆疊暫存器。

  4. 錯誤摘要狀態:第 7 個位元為 ES ( error summary status ),當位元 0 到 5 中的任一個位元設定時,此位元就會設定。若此時未罩遮的話,就會呼叫相關的程式執行例外處理。

  5. 堆疊錯誤:第 6 個位元稱為 SF ( stack fault ) 位元。當堆疊暫存器中,發生高過上限或低於下限時,x87 會設定此位元。

  6. 指示例外:第 5、4、3、2、1、0 位元,其作用如下表:

4.標籤字組

8087 有一個 16 位元的標籤字組,標籤字組埵酗K個標籤,每兩個位元為一個標籤,分別對應到八個堆疊暫存器。如下圖:

x87 的標籤字組

每個標籤代表相對應的堆疊暫存器內存入的數值形態。

  1. 00:可用數值,包含正常 ( normal ) 或異常值 ( unnormal )。
  2. 01:零。
  3. 10:非數值 ( NaN,Not a Number )、無窮大 ( NaN,Not a Number )、反常值 ( denormal )。
  4. 11:空的。

8087 所能處理的數值資料形態,除了七種基本形態 ( 字組整數、短整數、長整數、短實數、長實數、暫時實數、聚集 BCD 整數 ) 之外,還保留了某些特殊的編碼方式來表示特殊的資料,這些特殊的資料一般應用上較少使用,可參考第 22 章註四的說明。

5.例外指標

例外指標包含三個暫存器:指令指標暫存器 ( last instruction pointer )、運算元指標暫存器 ( last operand pointer,也叫資料指標暫存器,last data pointer )、指令碼暫存器 ( last instruction opcode )。這三個暫存器用於例外發生時,讓程式知道發生例外時,x87 的執行情形。指令指標暫存器與運算元指標暫存器都是 48 位元長,指令碼暫存器為 11 位元長。


80386 暫存器

到了民過 74 年十月,英特爾推出了新一代的 CPU,80386。就組合語言的觀點來看,這顆 CPU 與之前的 8086/80286 有很大的不同,它具有 32 位元暫存器,可以一次做 32 位元運算,一直到 Pentium 4 為止,通用暫存器、指標與索引暫存器、區段暫存器、旗標暫存器都相同。

通用暫存器

80386 的通用暫存器共有 4 個,分別是 EAX、EBX、ECX、EDX,它們的名稱和 8086/80286 通用暫存器名稱很相似,前面的『E』表示『extended』,延伸之意。這四個 32 位元的通用暫存器,每一個較低字組的 16 位元又可獨立成一個 16 位元的暫存器,名稱與 8086 相同。而這低字組的 16 位元暫存器又可再分成高位元組的 8 位元與低位元組的 8 位元兩個暫存器。例如 EAX 較低字組的 16 位元為 AX 暫存器,AX 又可分成 AH 與 AL 兩個八位元的暫存器,但是 EAX 較高字組的 16 位元無法單獨使用了,也沒特殊的名稱。至於 EBX、ECX、EDX 也和 EAX 相同,見下圖。

AX 與 EAX

指標與索引暫存器

80386 有 4 個 32 位元的指標與索引暫存器,分別是 ESP、EBP、EIP、ESI、EDI,而每一個又可以把較低的 16 位元拆出來使用,分別稱為 SP、BP、IP、SI、DI,同樣的較高的字組無法單獨使用,也沒特殊的名稱。這些暫存器的用法與 8086/80286 相同,例如在使用

        rep     movsb

時,就必須把來源字串位址存於 ESI,目的字串位址存於 EDI,移動的位元組數存於 ECX。

區段暫存器

80386/80486/Pentium 有六個 16 位元的區段暫存器,分別是 CS、DS、ES、SS、FS 和 GS,比 8086/80286 多出兩個,FS 和 GS。前面四個的用法與 8086/80286 相同,例如 CS 指向程式碼區段、DS 指向資料區段。多出來的 FS 和 GS 可視為額外區段。但是在保護模式中區段的意義與真實模式不同,請參考保護模式的文獻。

延伸旗標暫存器 ( EFLAG )

與 8086 的 16 位元旗標暫存器比較,80386/80486/Pentium 的延伸旗標暫存器有 32 位元寬,雖然多了一些大部分與保護模式有關系統旗標,但仍然有許多位元未被使用,並且控制旗標與狀態旗標都能與 8086 相容。下圖是延伸旗標的各位元名稱:

名稱位元說明
I/O 特權等級
I/O privilege level
12、13

此兩位元是用來決定程式是否允許存取 I/O 埠或執行需要特權等級指令 ( 如 lgdt、mov cr0,ax 等等 ),當程式的特權等級 ( CPL ) 小於或等於 IOPL 時,才可以存取 I/O 埠或執行需要特權等級指令。注意,在 386 及其高階 CPU 的保護模式中,共分為 RING0、RING1、RING2、RING3 四種特權等級,RING0 是最高級,一般作業系統、驅動程式都在這種等級下執行。RING3 是最低等級,一般應用程式在 RING3 中執行。

Task 串接旗標
nested task
NT
14

此旗標用來表示目前執行的程式 ( task ) 之前是否還有其他程式等待被執行,若 NT=1,表示還有程式等待執行;反之則否。

回復旗標
resume flag
RF
16

此旗標用來是否暫時抑制偵錯例外,若 RF=1,CPU 會去處理偵錯例外,反之不會。

虛擬 86 模式
virtual 86 mode
VM
17

虛擬 86 模式 ( V86 mode ) 可視為一種特殊的保護模式,CPU 要先進入保護模式之後,才可進入 V86 模式,所以當 VM=1 時為 V86 模式;VM=0 時為保護模式。

對齊檢查
alignment check
AC
18

當 AC 與 CR0 暫存器同時為一時,CPU 會檢查記憶體存取位址是否對齊,沒有對齊的存取動作會產生『對齊檢查例外』。所謂『檢查記憶體存取位址是否對齊』的意思是:對於存取字組 ( word ) 資料型態的資料所在位址必須是 2 的倍數、存取雙字組 ( dword ) 資料型態的資料所在位址必須是 4 的倍數。

虛擬中斷旗標
virtual interrupt flag
VIF
19

用於多工環境下支援中斷旗標。

虛擬中斷懸置旗標
virtual interrupt pending flag
VIP
20

用於多工環境下支援中斷旗標。

識別旗標
identification flag
ID
21

如果應用程式可以清除或設定識別旗標,表示 CPU 支援 CPUID 指令。

記憶體管理暫存器

80386/80486/Pentium 使用四個記憶體管理暫存器來記錄保護模式底下,記憶體描述器表格位址所在,它們是:

  1. GDTR ( global descriptor table register,全域描述器表格暫存器 ):用來記錄 GDT ( 全域描述器表格 ) 所在之起始位址。
  2. LDTR ( local descriptor table register,區域描述器表格暫存器 ):用來記錄 LDT ( 區域描述器表格 ) 所在之起始位址。
  3. IDTR ( interrupt descriptor table register,中斷描述器表格暫存器 ):用來記錄 IDT ( 中斷描述器表格 ) 所在之起始位址。
  4. TR ( task register,TASK 暫存器 ):用來記錄正在執行的工作 ( task ) 所對應的 TSS ( task state segment ) 選擇器。

控制暫存器

80386/80486 有四個控制暫存器,分別是 CR0、CR1、CR2、CR3,Pentium 還多了一個 CR4。

  1. CR0:其欄位如下:
    CR0 控制暫存器


  2. CR1:被保留,不使用。

  3. CR2:當『頁錯誤』例外 ( page-fault ) 發生時,造成錯誤的線性位址 ( linear address ) 會儲存在 CR2。

  4. CR3:此控制暫存器處理有關分頁目錄 ( page directory ),其欄位如下:
    CR3 控制暫存器
  5. CR4:Pentium 及其以上等級的 CPU 才有 CR4 暫存器,80386/80486 沒有這個暫存器。CR4 的欄位如下:
    CR4 控制暫存器