80X86寄存器詳解

引子 程序員

打算寫幾篇稍近底層或者說是基礎的博文,淺要介紹或者說是回顧一些基礎知識, 編程

天然,仍是得從最基礎的開始,那就從彙編語言開刀吧, 數據結構

從彙編語言開刀的話,咱們必須還先要了解一些其餘東西, 學習

像  CPU ,內存這些知識點仍是理解深入一點的比較好, 編碼

因此這一篇博文就繞着 80x86  CPU 中寄存器的基礎部分下手,至於其餘的一些將會在後續的博文中介紹 。 spa

同時在這裏說明一下,本篇博文介紹的算是比較詳細的了,並且介紹的知識點也是比較多的,因此形成博文長度過長, 操作系統

若是有興趣想了解這一塊的話,還請自行斟酌好閱讀比例,建議分 3 次以上閱覽 。 翻譯

讀者定位 設計

本博文主要將介紹的是  8086 CPU 中的寄存器,既然是 8086 CPU 寄存器簡介的話, 指針

天然,面向的是初級一些的讀者,其中不會涉及太多難點,同時,全部的介紹,我也會盡量的從基礎開始,

而後按部就班的介紹,同時也會盡可能的將知識點介紹詳細,

介紹的過程當中也會涉及到一些彙編程序代碼,固然,採用的是最簡單的方式介紹而已,

本篇博文也就是回顧一些基礎知識,讀者主要定位於想對 8086 CPU 有所瞭解,

但願對整個程序設計的底層有所瞭解的朋友,並且讀者最好是擁有必定的計算機基礎和彙編語言基礎 。

 開頭

首先淺要介紹一下  Intel  CPU 的發展史吧:

Intel CPU 系列,最初是 4 位微處理器 4004,而後到到 8 位微處理器的 8008 ,

再到 8 微微處理器 8080,以及稍後的 16 位微處理器 8086,

由 8086 開始,Intel 進入如今所謂的  x86  時代 。

Intel  8086 爲 16 位  CPU ,而由於在 8086 以前的 CPU 都是 8 位 CPU,這樣也就形成了不少的外設也只支持 8 位,

所以  Intel  緊接着就退出了 8 位的 8088 CPU,所以  Intel 8088 也就能夠看作是 8086 的 8 位版本;

若是是但從彙編語言的角度上來講,8086 和 8088 是沒有區別的,即 8086 上跑的程序能夠不加修改的移植到 8088 ,

8088 上跑的程序也能夠不加修改的移植到 8086 上,

固然,仍是有些特殊的地方是不一樣的,而這些基本上在這裏能夠忽略掉,

在 8088  CPU 以後,Intel  又推出了  80186 ,80286 ,這兩款 CPU 均是 16 位  CPU ,

而對於 80186 來講,其與 8086 的區別能夠簡單的看作是 80186 多了幾條指令而已,

而 80286 則不一樣,80286 的地址總線數目有了變化,

在 8086 , 8088 , 80186 上,CPU 的地址總線都是 20 根,便可最大尋址 220 即達到 1MB 的尋址能力,

而對於 80286 CPU 來講,其地址總線數目達到了 24 根,從而最大尋址能力爲 224 即 16MB,

因爲支持更多的物理內存尋址,所以 80286 便開始成爲了多任務,多用戶系統的核心。

然後來,Intel  又推出了 80386 ,80386 爲 32 位微處理器,Intel 80x86 家族的 32 位微處理器始於 80386;

同時 80386 也徹底兼容先前的 8086/8088,80186,80286,而且 80386 全面支持 32 位數據類型和 32 位操做,

而且 80386 的數據總線根數和地址總線根數均達到了 32 根,從而能夠最大物理尋址爲 232  即 4GB 。

而以後的 80486 也是 32 位微處理器,然後又出來了 Pentium 和 Pentium Pro 等等第五代微處理器,

這些處理器雖然也是 32 位微處理器,可是他們的數據總線和地址總線都有所擴展,

好比 Pentium 的數據總線達到 64 位,而 Pentium Pro 的地址總線位數達到了 36 位 。

             

好,關於 Intel CPU 的介紹就到這裏了,下面就要開始迴歸中心,看 CPU 中的寄存器了,

首先,從學習的角度來講,從 8086/8088  CPU 下手是不錯的選擇,而我這裏選擇的也是 8086 CPU 而已,

說實在的,像 80386 CPU 我也尚未研究過,像奔騰這些,呵呵,扯更遠了,

說到底也就只能拿 8086 出來曬曬而已,固然,從 8086 開始也是學習的最佳路徑 。

       

說了這麼久,到底寄存器是什麼呢?其實很簡單,寄存器就是個存儲信息的單元或者說是器件又或者說是容器而已,

就好比內存也是一個存儲介質或者說是存儲單元而已,其實寄存器從理解上來講和內存差很少,

只不過寄存器(這裏討論的寄存器都是 CPU 中的寄存器,不包括外設上的寄存器)位於  CPU  內部,而內存位於 CPU 外部,

並且,寄存器比內存但是珍貴得多啊,就拿內存和硬盤來比,確定是內存在使用上珍貴得多,是 PC 中的稀有資源,

而寄存器是 CPU 中的稀有資源,內存和寄存器相比就像硬盤和內存相比同樣 。

而對於一個彙編程序員來講,CPU 中主要可使用的也就是寄存器而已,彙編程序員可使用指令來讀寫 CPU 中的寄存器,

從而能夠實現對於 CPU 的控制,固然,不一樣的 CPU ,寄存器的個數和結構都是不同的,

好比 8086 CPU 中,寄存器的個數也就 14 個而已,

而且 8086 CPU 中全部的寄存器的結構爲 16 位,即一個寄存器中能夠存放下 2B 即 2 個字節,

而到了 80386 CPU 中,寄存器的個數也比 8086 增多了,好比在 80386 中添加了系統地址寄存器等寄存器,

同時寄存器的結構也變了,好比在 80386 中絕大多數的寄存器爲 32 位,而有些寄存器則是 16 位 。

8086  CPU 中寄存器總共爲 14 個,且均爲 16 位 。

即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 個。

而這 14 個寄存器按照必定方式又分爲了通用寄存器,控制寄存器和段寄存器。

通用寄存器:

AX,BX,CX,DX 稱做爲數據寄存器:

AX (Accumulator):累加寄存器,也稱之爲累加器;

BX (Base):基地址寄存器;

CX (Count):計數器寄存器;

DX (Data):數據寄存器;

SP 和 BP 又稱做爲指針寄存器:

SP (Stack Pointer):堆棧指針寄存器;

BP (Base Pointer):基指針寄存器;

SI 和 DI 又稱做爲變址寄存器:

SI (Source Index):源變址寄存器;

DI (Destination Index):目的變址寄存器;

控制寄存器:

IP (Instruction Pointer):指令指針寄存器;

FLAG:標誌寄存器;

段寄存器:

CS (Code Segment):代碼段寄存器;

DS (Data Segment):數據段寄存器;

SS (Stack Segment):堆棧段寄存器;

ES (Extra Segment):附加段寄存器;

           

           

通用寄存器

從上面能夠知道,在 8086 CPU 中,通用寄存器有 8 個,分別是 AX,BX,CX,DX,SP,BP,SI,DI ,

至於爲何給它們取名作通用寄存器,那是由於,這些個寄存器每個都有本身專門的用途,

好比 CX 做爲計數寄存器,則是在使用 LOOP 指令循環時用來指定循環次數的寄存器,

若是它們每個都只有一個專用的做用,那就它們只能稱之爲專用寄存器了,

正是由於這些個寄存器還能夠用來傳送數據和暫存數據,因此才稱它們爲通用寄存器 。

下面就按順序來一一介紹這幾個通用寄存器了:

數據寄存器(AX,BX,CX,DX):

數據寄存器有 AX,BX,CX,DX 四個組成,

因爲在 8086 以前的 CPU 爲 8 位 CPU,因此爲了兼容之前的 8 位程序,

在 8086 CPU 中,每個數據寄存器均可以當作兩個單獨的寄存器來使用,

由此,每個 16 位寄存器就能夠當作 2 個獨立的 8 位寄存器來使用了 。

AX 寄存器能夠分爲兩個獨立的 8 位的 AH 和 AL 寄存器;

BX 寄存器能夠分爲兩個獨立的 8 位的 BH 和 BL 寄存器;

CX 寄存器能夠分爲兩個獨立的 8 位的 CH 和 CL 寄存器;

DX 寄存器能夠分爲兩個獨立的 8 位的 DH 和 DL 寄存器;

除了上面 4 個數據寄存器之外,其餘寄存器均不能夠分爲兩個獨立的 8 位寄存器 ;

注意在上面標誌中的「獨立」二字,這兩個字代表 AH 和 AL 做爲 8 位寄存器使用時,

能夠看作它們是互不相關的,也就是看作兩個徹底沒有聯繫的寄存器 X 和 Y 便可,

好比指令   MOV   AH , 12H ,CPU 在執行時根本就不會知道 AL 中是什麼鬼東西,由於它只認識  AH 。

下面給出一幅 16 位數據寄存器的結構圖:

表示 16 位 寄存器 AX 能夠表示成兩個 8 位寄存器,

其中 AH 表示高位的 8 位寄存器,AL 表示低位的 8 位寄存器 。

image

AX 寄存器:

如上所說,AX 的另一個名字叫作累加寄存器或者簡稱爲累加器,其能夠分爲 2 個獨立的 8 位寄存器 AH 和 AL;

在寫彙編程序時,AX 寄存器能夠說是使用率最高的寄存器(不過,總共才那麼 14 個寄存器,哪個不常用咯?),

既然 AX 是數據寄存器的話,那麼理所固然,其能夠用來存放普通的數據,因爲其是 16 位寄存器,

天然也就能夠存放 16 位數據,可是由於其又能夠分爲 2 個獨立的 8 位寄存器 AH 和 AL ,

因此,在 AH 和 AL 中又能夠獨立的存放 2 個 8 位的數據,

能夠有如下代碼(即將 AX 當作普通的寄存器使用,便可以用來暫存數據):

MOV AX,1234H ;向寄存器 AX 傳入數據 1234H MOV AH,56H ;向寄存器 AX 的高 8 位寄存器 AH 中傳入數據 56H MOV AL,78H ;向寄存器 AX 的低 8 位寄存器 AL 中傳入數據 78H
3 條語句的執行過程以下:
image

而既然 AX 又被稱做爲累加器,天然其還有一點點特殊的地方的:

AX 寄存器還具備的特殊用途是在使用 DIV 和 MUL 指令時使用,

DIV 在 8086 CPU 中是除法指令,而在使用除法的時候有兩種狀況,即除數能夠是 8 位或者是 16 位的,

並且除數能夠存放在寄存器中或者是內存單元中,而至於被除數的話,天然,應該由 AX 來代替了,

當除數是 8 位時,被除數必定會是 16 位的,而且默認是放在 AX 寄存器中,

而當除數是 16 位時,被除數必定是 32 位的,由於 AX 是 16 位寄存器,天然,放不下 32 位的被除數,

因此,在這裏還須要使用另外一個 16 位寄存器 DX ,

其中 DX 存放 32 位的被除數的高 16 位,而 AX 則存放 32 位的被除數的低 16 位,

同時,AX 的做用還不只僅是用來保存被除數的,當除法指令執行完成之後,

若是除數是 8 位的,則在 AL 中會保存這次除法操做的商,而在 AH 中則會保存這次除法操做的餘數,

固然,若是除數是 16 位的話,則 AX 中會保存本次除法操做的商,而 DX 則保存本次除法操做的餘數。

上面介紹的是 AX 寄存器在除法操做中的應用,下面還須要介紹一下 AX 在乘法操做中的應用,

當使用 MUL 作乘法運算時,兩個相乘的數要麼都是 8 位,要麼都是 16 位,

若是兩個相乘的數都是 8 位的話,則一個默認是放在 AL 中,

而另外一個 8 位的乘數則位於其餘的寄存器或者說是內存字節單元中,

而若是兩個相乘的數都是 16 位的話,則一個默認存放在 AX 中,

另外一個 16 位的則是位於 16 的寄存器中或者是某個內存字單元中。

同時,當 MUL 指令執行完畢後,若是是 8 位的乘法運算,則默認乘法運算的結果是保存在 AX 中,

而若是是 16 位的乘法運算的話,則默認乘法運算的結果有 32 位,

其中,高位默認保存在 DX 中,而低位則默認保存在 AX 中。

AX 寄存器在 DIV  指令中的使用:

MOV DX,0H ;設置 32 位被除數的高 16 位爲 0H MOV AX,8H ;設置 32 位被除數的低 16 位爲 8H MOV BX,2H ;設置 16 位除數爲 2H DIV BX ;執行計算

4 條語句的執行過程以下:

image

AX 寄存器在 MUL  指令中的使用:

MOV AX,800H ;設置 16 位乘數爲 800H MOV BX,100H ;設置 16 位乘數爲 100H MOV DX,0H ;清空用來保存乘法結果的高 16 位 MUL BX ;執行計算
4 條語句的執行過程以下:

image

       
BX 寄存器:

首先能夠明確的是,BX 做爲數據寄存器,代表其是能夠暫存通常的數據的,

即在某種程度上,它和 AX 能夠暫存通常性數據的功能是同樣的,

其一樣爲了適應之前的 8 位 CPU ,而能夠將 BX 當作兩個獨立的 8 位寄存器使用,即有 BH 和 BL,

除了暫存通常性數據的功能外,BX 做爲通用寄存器的一種,BX 主要仍是用於其專屬功能 – 尋址(尋址物理內存地址)上,

BX 寄存器中存放的數據通常是用來做爲偏移地址使用的,何爲偏移地址呢?

既然是偏移地址的話,固然得有一個基地址了,而這個基地址其實就是段地址,這裏就涉及到了段寄存器,

固然,在介紹 BX 寄存器的時候,我不會去介紹段寄存器,上面提到 BX 的主要功能是用在尋址上,

那麼,其是如何尋址的呢?

對於尋址這個話題,我會在個人下一篇博文中做出詳細的介紹,

而這裏,我只點一下,在 8086 CPU 中,CPU 是根據 <段地址:偏移地址> 來進行尋址操做的,

而 BX 中存放的數據表示的是偏移地址的話,天然,即可以經過 <段地址:[BX]> 的方式來完成尋址操做了。

爲了介紹 BX 在尋址當中的做用,下面我給出一副示意圖:

image

上面的示意圖表示:能夠令 BX = 2,而後經過 DS : [BX] 來訪問到內存中段地址爲 DS,且偏移量爲 2 的內存單元了。

上面介紹的這種尋址方式是 BX 在尋址中最最簡單的應用了,而對於稍微複雜的尋址方式,

還能夠依賴於 SI,DI,BP 等寄存器來一塊兒完成,固然,這會是下一篇博文將要介紹的內容了。

BX 寄存器在尋址中的使用:

MOV BX,5H MOV AH,11H MOV AH,[BX] ;設置 AX 的值爲偏移地址爲 BX 中的值時所表明的內存單元
3 條語句的執行過程以下:

image

從上圖能夠看出,在偏移地址爲 5 時的內存單元中的數據位 BBH,

image

而從這幅圖上面就能夠看出,確實經過 [BX] 找到了偏移地址爲 5 處的內存單元,而且將內存單元移入了 AH 中。

              

CX 寄存器:

CX 寄存器做爲數據寄存器的一種呢,其一樣具備和 AX,BX 同樣的特色,便可以暫存通常性的數據,

同時還能夠將其當作兩個獨立的 8 位寄存器使用,即有 CH 和 CL 兩個 8 位寄存器,

固然,CX 也是有其專門的用途的,CX 中的 C 被翻譯爲 Counting 也就是計數器的功能,

當在彙編指令中使用循環 LOOP 指令時,能夠經過 CX 來指定須要循環的次數,

而 CPU 在每一次執行 LOOP 指令的時候,都會作兩件事:

一件就是令 CX = CX – 1,即令 CX 計數器自動減去 1;

還有一件就是判斷 CX 中的值,若是 CX 中的值爲 0 則會跳出循環,而繼續執行循環下面的指令,

固然若是 CX 中的值不爲 0 ,則會繼續執行循環中所指定的指令 。

CX 寄存器在循環中的使用(輸出 5 個白底藍字的 A):

MOV AX,0B800H MOV DS,AX ;使用 80x25 彩色字符模式,內存地址 0xB8000 - 0xBFFFFF MOV BX,0 ;從 0xB8000 開始 MOV CX,5H ;循環 5 次 MOV DX,41H ;A 的16 進製爲 41H MOV AX,01110001B ;顯示白底藍字 s: MOV [BX],DX ;顯示 ASCII 字符 ADD BX,1 MOV [BX],AX ;設置字符顯示屬性 ADD BX,1 LOOP s

語句的執行過程以下:

image

              

DX 寄存器:

DX 寄存器做爲數據寄存器的一種,一樣具備和 AX,BX,CX 同樣的特色,便可以暫存通常性的數據,

同時還能夠將其當作兩個獨立的 8 位寄存器使用,極有 DH 和 DL,

同時,DX 做爲一個通用寄存器的話,天然其還有其餘的用途,而關於 DX 在其餘方面的用途,

其實在前面介紹 AX 寄存器時便已經有所介紹了,

即當在使用 DIV 指令進行除法運算時,若是除數爲 16 位時,被除數將會是 32 位,而被除數的高 16 位就是存放在 DX 中,

並且執行完 DIV 指令後,本次除法運算所產生的餘數將會保存在 DX 中,

同時,在執行 MUL 指令時,若是兩個相乘的數都是 16 位的話,

那麼相乘後產生的結果顯然須要 32 位來保存,而這 32 位的結果的高 16 位就是存放在 DX 寄存器中 。

DX 寄存器在 DIV  指令中的使用(即 2293812 / 256 = 8960  餘數爲 52):

MOV DX,0023H ;32 位被除數的高 16 位 MOV AX,0034H ;32 位被除數的低 16 位 MOV BX,100H ;16 的除數 DIV BX 

語句的執行過程以下:

image
能夠看到在語句結束之後,AX = 2300H  即十進制的 8960,而 DX = 34H即十進制的 52 和咱們的結果是一致的。

DX 寄存器在 MUL  指令中的使用則各位能夠參考在 AX 中 MUL 運算的使用,這裏就不貼出來了。

          

指針寄存器(BP,SP):

BP 寄存器:

8086  CPU 中的指針寄存器包括兩個,即 SP 和 BP ,在這裏呢,我先只對 BP 寄存器作介紹,

由於 SP 寄存器實質上必須和 SS 段寄存器一塊兒使用,因此,我將會把 SP 寄存器留到後面和 SS 段寄存器一塊兒做介紹。

BP (Base Pointer)也就是基指針寄存器,它和其餘的幾個用來進行尋址操做所使用的寄存器(還有 BX,SI,DI)沒有太大的區別,

關於 SI 和 DI 寄存器的下面請見下文。

首先,BP 寄存器做爲通用寄存器的一種,說明其是能夠暫存數據的,然後,BP 又不是數據寄存器,

也就意味着其不能分割成 2 個獨立的 8 位寄存器使用,

然後當以 […] 的方式訪問內存單元並且在 […] 中使用了寄存器 BP 的話,

那麼若是在指令中沒有明確或者說是顯示的給出段地址時,

段地址則使用默認的 SS 寄存器中的值(BX,SI,DI 會默認使用 DS 段寄存器),

好比 DS:[BP] 則在這裏明確給出了段地址位於 DS 中,

因此,這裏表明的內存單元便是段地址爲 DS ,偏移量爲 BP 寄存器中的值的內存單元,

而若是單單是使用 [BP] 的話,則表明的內存單元是段地址爲 SS,偏移量爲 BP 寄存器中的值的內存單元。

而且 BP 寄存器主要適用於給出堆棧中數據區的偏移,從而能夠方便的實現直接存取堆棧中的數據,

至於堆棧的話,會在後面的博文中介紹。

在 8086 CPU 中,只有 4 個寄存器能夠以  […]  的方式使用,這四個寄存器分別是 BX,SI,DI,BP。

下面的  Demo  是 BX 寄存器在尋址中的使用:

MOV BP,0 MOV AX,[BP] ;將 SS:[BP] 表明的內存單元移入 AX 中 MOV AX,CS:[BP] ;將 CS:[BP] 表明的內存單元移入 AX 中

語句的執行過程以下:

image

            

變址寄存器(SI,DI):

首先,變址寄存器和上面介紹的指針寄存器(也就是 BP 和 SP),它們的功能其實都是用於存放某個存儲單元地址的偏移,

或者是用於某組存儲單元開始地址的偏移,即做爲存儲器指針使用,固然,因爲變址寄存器和指針寄存器都是屬於通用寄存器,

因此它們也能夠保存算術結果或者說是具備暫存數據的功能,可是由於它們不是數據寄存器,因此沒法分割成 2 個獨立的 8 位寄存器使用,

關於變址寄存器和指針寄存器的詳細使用,筆者將會在下一篇博文中做出最詳細的介紹,

SI (Source Index) 是源變址寄存器,DI (Destination Index) 便是目的變址寄存器,

8086 CPU 中的 SI 寄存器和 DI 寄存器其實和 BX 寄存器的功能是差很少的,

只不過 SI 寄存器和 DI 寄存器均不是數據寄存器,因此它們不可以拆分爲 2 個獨立的 8 位寄存器,

而這也就是 SI 寄存器和 DI 寄存器與BX 寄存器所不一樣的地方,

既然,SI,DI 兩個寄存器的功能和 BX 差很少,天然,SI 和 DI 中也是能夠暫存通常性數據的,

同時,經過使用 SI 和 DI 寄存器也是能夠用來完成尋址操做的。

好比下面的代碼就是可行的:

MOV SI,0 ;初始化偏移地址爲 0 MOV AX,[SI] ;將段地址爲 DS 偏移地址爲 SI 的內存單元中的值移入 AX 中 MOV AX,DS:[SI] ;將段地址爲 DS 偏移地址爲 SI 的內存單元中的值移入 AX 中 MOV AX,SS:[SI] ;將段地址爲 SS 偏移地址爲 SI 的內存單元中的值移入 AX 中 MOV DI,0 ;初始化偏移地址爲 0 MOV AX,[DI] ;將段地址爲 DS 偏移地址爲 DI 的內存單元中的值移入 AX 中 MOV AX,DS:[DI] ;將段地址爲 DS 偏移地址爲 DI 的內存單元中的值移入 AX 中 MOV AX,SS:[DI] ;將段地址爲 SS 偏移地址爲 DI 的內存單元中的值移入 AX 中

        

       

其餘寄存器(CS,IP,SS,SP,DS,ES)

因爲段寄存器老是和其餘一些像指針寄存器,變址寄存器,控制寄存器一塊兒使用,

因此在這裏,我並不會單獨介紹段寄存器,而是將段寄存器和一些其餘的經常使用寄存器搭配介紹 。

因爲下面的介紹中會涉及到不少關於段和棧的概念,而段和棧的介紹又都必須關係到物理內存,

因此在介紹段寄存器以及其餘一些呈協做關係的寄存器以前,仍是先來介紹一下這幾個基本的概念比較好。

8086 CPU 訪問內存(物理地址):

當 CPU 須要訪問一個內存單元時,須要給出內存單元的地址,

而每個內存單元在物理內存空間中都有一個惟一的地址,

便可以經過這個地址定位到內存單元,而這個地址即爲物理地址。

CPU 經過地址總線將一個內存單元的物理地址送入存儲器,

然後 CPU 即可以經過這個物理地址來訪問這個物理地址所指向的內存單元了。

那麼這個物理地址在 CPU 中是如何造成的呢?

首先,咱們知道 8086 CPU 的地址總線是 20 根,

即每次均可以傳輸 20 位的地址,從而尋址能力有 220 也就是 1MB 的大小,

可是 8086 CPU 的寄存器只有 16 位,也就是在 8086 CPU 的內部,

一次性處理,傳輸,暫存的地址都只能是 16 位,

即 8086 CPU 不能完整的保存下一個物理地址(物理地址爲 20 位),

若是單單以最簡單的方式(即直接用 16 位寄存器來保存物理地址)的話,那麼,尋址能力只有 216 ,也就是 64KB,

若是真以如此簡單的方式的話,那麼地址總線還須要 20 根幹嗎呢?並且,難不成咱們之後的內存就是 64KB 了嗎?

固然不是的,8086 CPU 在這裏採起了必定的措施從而使其尋址能力達到 1MB 。

8086 CPU 在內部經過兩個 16 位的地址進行合成從而造成一個 20 位的物理地址,由此,8086 CPU 的尋址能力即可以達到 1MB 。

那麼 8086 CPU 又是如何將兩個 16 位的地址合成爲一個20 位的物理地址的呢?

當 CPU 在訪問內存時,其會使用一個 16 位的基地址,而後再使用一個 16 位的偏移地址,

經過將基地址和偏移地址傳入 8086  CPU 的地址加法器中進行合成便可以構造出 20 位的物理地址。

至於合成的方式以下:

基地址實際上是經過一個 16 位的段地址來造成的,將一個段地址左移 4 位即造成了基地址,

而至於偏移地址的話,天然沒必要多說,爲 16 位,經過將基地址和偏移地址相加便造成了 20 位的物理地址 。

下面給出一幅示意圖來表示物理地址的合成:

image

段:

至於段的話,其實在物理內存中是沒有段這一律唸的,事實上,段的概念來自於  CPU ,

由於 CPU 擁有段寄存器,既然在 CPU 中擁有了段寄存器,天然,在 CPU 中就確定有段的概念了,

其實段也就是在編程時,咱們將若干個地址連續的內存單元看作是一個段,

而後經過將一個段地址左移 4 位造成基地址,再經過這個基地址來定位這個段的起始地址,

而後,再經過偏移地址即可以精肯定位到段中的內存單元了,因爲段的起始地址是一個段地址左移 4 位,

因此很明顯,段的起始地址確定是 16 的倍數,並且因爲一個段內部,只能經過偏移地址來定位,

而偏移地址爲 16 位,因此一個段的長度也就是 216 也就是 64KB 的大小。

在編程時,能夠講一段內存定義成爲一個段,而這裏,咱們又能夠引出數據段,代碼段,棧段這三種類型的段 。

何爲數據段呢?其實就是咱們自個兒定義一段內存(固然段起始地址確定是 16 的倍數,而且段長度 <= 64KB),

而後咱們在這個段裏頭存放咱們所須要使用的數據,這就是數據段;

何爲代碼段呢?其實也很簡單,也是咱們本身在編程的時候定義一段內存,而後這段內存用來存放咱們的代碼(也就是指令),

既然是存放的代碼,天然就稱之爲代碼段;

何爲棧段呢?至於棧段的話,有接觸過數據結構的朋友應該是很清楚棧的,而這裏咱們也就是在內存中分配出一個段,

而後將這個段當作棧來使用,對於棧的介紹,詳見下文;

這裏呢,順便還點出幾個關於段寄存器的內容,固然下文還會詳細介紹的,

首先,對於任何一個段來講,均有段地址,而這些段地址是存放在段寄存器中(段寄存器的做用也在於此),

可是對於不一樣的段,它們默認的段地址存放在不一樣的段寄存器中,像

數據段來講,它的段地址存放在  DS (Data  Segment)寄存器中,

代碼段的段地址存放在  CS (Code  Segment)寄存器中,

棧段的段地址存放在  SS (Stack  Segment)寄存器中 。

下面給出一幅在段中尋址的示意圖:

image 

上面的示意圖中,經過將段地址左移四位,而後與偏移地址相加即可以獲得 20 位的物理地址了 。

棧:

8086  CPU 中提供了對棧的支持,而且其還提供了相應的指令來以棧的方式訪問內存空間 。

什麼是棧?

經過上面在段中的介紹,棧其實就是一個段,再說白一點,也就是一塊內存,固然,這塊內存是一塊連續的內存 。

既然棧是一個段的話,那麼固然就能夠以使用段的方式來使用棧,固然,除了像段同樣的使用棧之外,

棧還提供了其特殊的訪問方式(若是和段如出一轍的話,那還須要棧幹嘛呢?),

衆所周知,棧是先進後出類型的數據結構,在 8086  CPU 中也是如此,

能夠經過 」PUSH「  指令將數據壓入棧中,而後再經過 」POP「  指令將棧頂的元素取出來 。

下面給出一幅示意圖來描述棧:

image

即經過 PUSH  10 來將元素 10 放入棧中,由於,先前棧中沒有任何數據,因此,10 就會做爲棧頂元素存在,

而後再在棧中壓入元素 20 ,此時,棧頂中的元素就是 20 了,而後再使用  POP 指令將棧頂元素取出,

此時取出的棧頂元素是 20 ,取出 20 後,棧中便只剩下 10 了,天然 10 就成爲了棧頂,

最後再經過 POP 指令將棧頂 10 取出,此時,棧便變成了空棧了 。

好了,在介紹段寄存器以前的基礎知識介紹就到這裏了,下面開始正式介紹段寄存器以及與它們協做使用的寄存器。

                  

CS 寄存器 和 IP 寄存器:

通過前面對段的介紹,相信各位朋友對段寄存器應該也有必定的瞭解了,

下面將要介紹的是一組很是很是重要的寄存器,即 CS:IP 。

CS:IP 兩個寄存器指示了 CPU 當前將要讀取的指令的地址,其中  CS  爲代碼段寄存器,而   IP  爲指令指針寄存器 。

什麼叫作指示了 CPU 當前將要讀取的指令呢?在 8086  CPU 中,爲何  CPU  會自動的執行指令呢?

這些指令確定是存放在內存中的,可是  CPU  怎麼知道這些指令存放在內存的那個位置呢?

好比,我有下面的兩條指令要執行:

 MOV AX,1234H MOV BX,AX

而假設這兩條指令在內存中存放爲:

image

很顯然, 1000H:0000H 指向的是  MOV  AX,1234H  的首地址,

若是 CPU 要讀取到個人指令的話,很顯然,必需要知道地址  1000H:0000H ,

而後  CPU  就能夠根據這個首地址,將彙編指令  MOV  AX,1234H  所對應的機器碼讀入到  CPU  的指令寄存器中,

最後即可以在  CPU  中進行處理了。

但關鍵是   CPU  如何知道個人  1000H:0000H  這個首地址?

其實這就須要使用到  CS:IP  這個寄存器組了 。

當咱們運行一個可執行文件時,很明顯,咱們須要另一個程序來將這個可執行文件加載到內存當中,

關於這個加載可執行文件的程序,咱們在這裏無論他,點一下便可,

通常是經過操做系統的外殼程序(也就是傳說中的  Shell  程序),

Shell  將可執行文件加載到內存中之後,就會設置  CPU  中的兩個寄存器,

即設置  CS:IP  兩個寄存器指向可執行文件的起始地址,此後  CPU  便從這個起始地址開始讀取內存中的指令,而且執行,

好比咱們在寫彙編程序時,一般會使用  START  標記,其實這個標記就是用來標記起始地址的,

當將一個彙編程序編譯,鏈接成可執行文件之後,再經過操做系統的  Shell  程序將可執行文件加載到內存中之後,

這個  START  所標記處的地址就是整個可執行文件的起始地址了 。

也就是說,當一個可執行文件加載到內存中之後,CS:IP  兩個寄存器便指向了這個可執行文件的起始地址,

而後  CPU  就能夠從這個起始地址開始往下讀取指令,

當讀取完指令後,CS:IP  將會自動的改變,基本上是改變  IP ,從而指向下一條要讀取的指令,這樣就能夠執行這個可執行文件了 。

最後再對  CS:IP  總結一下:

  1. 你想讓  CPU  執行哪行指令,你就讓  CS:IP  指向保存有指令的那塊內存便可。
  2. 任什麼時候候,CS:IP  指向的地址中的內容都是  CPU  當前執行的指令。

下面咱們來看一個  Demo,並詳細觀察其執行的過程:

ASSUME CS:CODES CODES SEGMENT START: MOV AX,1234H MOV BX,AX MOV AH,4CH INT 21H CODES ENDS END START

語句的執行過程以下:

image

從上面的截圖中能夠看出,當我使用  Shell (在  DOS  下也就是  Command  命令解釋器)將可執行文件加載進內存後,

能夠看到,整個程序的起始地址爲   0C54H : 0000 H  ,而且,能夠看到  CS  的地址爲  0C54H ,IP  的地址爲  0000H,

這正好吻合咱們上面對  CS:IP  的分析,很明顯,CPU  將會讀取    MOV    AX ,1234H   到 CPU 中而且執行 ,

而後咱們繼續向下看:

image

能夠看到,咱們單步執行後,AX 中的值編成了  1234H ,而  IP  寄存器中的值變成了  0003H,

對於  AX  中的值的改變,咱們是可以理解的,可是   IP  中的值爲何會從  0000H  變到  0003H  呢?

從最上面的一幅關於指令在內存中的存放能夠看出    MOV    AX ,1234H   在內存中須要  3 個內存單元存放,

也就是  CPU  爲了執行    MOV    AX ,1234H   這條指令,已經將內存中相對應的 3  個內存單元讀入內存中了,

執行完這條指令後,天然,CPU  就要將偏移地址向下移動  3  個單元,從而使得  CS:IP  指向下一條須要執行的指令了 ,

爲了更深入的理解,咱們再來繼續看執行過程,

image

從最上面的一幅關於指令在內存中的存放能夠看出    MOV    BX ,AX  在內存中只佔  2  個內存單元,

這也就是爲何  IP  這一次只向下移動了  2  個單元的緣故 。

                  

關於  CS: IP  的遐想:

從上面關於  CS:IP  的介紹中,咱們能夠大膽的猜測,咱們只須要經過手動的改變  CS:IP  所指向的內存地址,

讓  CS:IP  指向咱們另外的代碼,那麼咱們就可讓  CPU  執行咱們本身指定的代碼了 。

便可以經過修改  CS:IP  來達到咱們想要讓  CPU  幹什麼它就幹什麼的目的 。

上面的雖然是遐想,可是你們要相信,咱們寫的是彙編,不是  JAVA  也不是  NET  ,

因此咱們還真的能夠達到上面的目的,也就是說咱們的遐想實際上是能夠實現的,固然這仍是有必定的限制的 ,

關於這個遐想呢,可能會在我後續的博文中有所介紹,不過感興趣的固然能夠本身去嘗試了,蠻有味的哦 。

              

SS 寄存器和 SP 寄存器:

根據前面對棧的介紹,相信各位對棧也確定是有必定了解了的,更況且,估計你們也是職場打滾多年的,

要是棧都沒用過的話,那也確實蠻悲劇的 ,因此,我在這裏也不會對棧作十分詳細的介紹了,

可是,最基本的介紹仍是要的,畢竟在底層的話,不像高級語言那麼方便,能夠直接一個  Stack  就 OK 的,

在底層涉及的是棧在內存中的具體實現 。

不知道,大夥有沒有注意筆者在本篇博文的上面介紹關於棧的知識時,我並無提到如何找到這個棧,

我只提到了一個棧就是先進後出操做,同時可使用  」PUSH「 和  」POP「 指令,

而後就是稍微帶了一下  SS 這個寄存器的介紹,

咱們雖然在內存中是能夠方便的定義一個棧了,可是,咱們爲何要定義這麼一個棧呢?

天然,是爲了操做方便,同時提供給  CPU  使用的,

既然  CPU  要使用的話,天然,CPU  又必須根據必定的方式找到這個棧,

而這就須要使用  SS 和  SP 寄存器了 。

同時,一個棧也就是一塊內存區域,經過上面的介紹,咱們也知道了若是要在一塊內存中精確地定位到內存單元的話(尋址),

咱們必需要有基地址(也就是段地址左移  4  位)和偏移地址,天然,要在一個棧中尋址的話,也須要段地址和偏移地址,

而對於一個棧來講,咱們使用的最多的是什麼呢?

固然是棧頂了,由於只有棧頂能夠用來存取數據,因此對於一個棧來講,咱們只須要有棧頂的段地址和偏移地址便可,

而對於棧頂的段地址,其是存放在段寄存器  SS  中的,而對於棧頂的偏移地址,其則是存放在  SP  寄存器中的 。

記住,在任什麼時候刻,SS:SP  都是指向棧頂元素 。

其實關於棧的使用仍是比較簡單的,可是要注意的是  8086  CPU  並不會保證咱們對棧的操做會不會越界 。

因此咱們在使用棧的時候須要特別注意棧的越界問題 。

當使用  PUSH 指令向棧中壓入 1 個字節單元時,SP = SP - 1;即棧頂元素會發生變化;

而當使用  PUSH 指令向棧中壓入  2 個字節的字單元時,SP = SP – 2 ;即棧頂元素也要發生變化;

當使用  POP 指令從棧中彈出 1 個字節單元時, SP = SP + 1;即棧頂元素會發生變化;

當使用  POP 指令從棧中彈出 2 個字節單元的字單元時, SP = SP + 2 ;即棧頂元素會發生變化;

下面經過一個  Demo 來介紹棧的使用:

ASSUME CS:CODES CODES SEGMENT START: MOV AX,1000H ;首先是定義好棧的段地址 MOV SS,AX MOV AX,10H ;再定義好棧的長度(初始時刻的棧頂偏移地址即棧的長度) MOV SP,AX MOV AX,0001H PUSH AX MOV AX,0002H PUSH AX MOV AX,0003H PUSH AX MOV AX,0004H PUSH AX MOV AX,0005H PUSH AX POP AX POP AX POP AX POP AX POP AX MOV AH,4CH INT 21H CODES ENDS END START
 
而後咱們來看棧在內存中的結構圖:

image

語句的執行過程以下:

首先咱們來看還沒有執行上述任何指令時棧中的數據狀況:

image

而後咱們再來依次執行上述指令:

image

從上副截圖中能夠看出已經設置好了  SS:SP ,也就是棧已經設置 OK 了,

下面開始往棧中壓入數據了,

image

因爲咱們壓入棧中的數據爲字數據,即佔 2 個內存單元,因此,每次  SP = SP – 2 ;

將 5 個字型數據壓入棧中後,咱們能夠來查看棧中的數據了,

image

所以,在內存中的一個好看點的結構圖以下所示:

image

下面開始進行出棧操做了

image

因爲咱們彈出棧時的數據爲字數據,即佔 2 個內存單元,因此,每次  SP = SP + 2 ;

將 5 個字型數據所有彈出棧中後,咱們能夠來查看棧中的數據了,

image

能夠看到 SP 變成了初始狀態了,也就是說棧中全部的數據已經所有彈出了,雖然咱們查看內存時看到的不是 0 ,

可是咱們看到的這些數據都是無效的,咱們這裏不理會 。

                  

DS 寄存器和 ES 寄存器:

DS  寄存器和  ES  寄存器都屬於段寄存器,其實它們和  CS  寄存器以及  SS  寄存器用起來區別不大,

既然是段寄存器的話,天然它們存放的就是某個段地址了 。

經過上面對基礎知識的介紹呢,咱們已經知道,若是  CPU  要訪問一個內存單元時,

咱們必需要提供一個指向這個內存單元的物理地址給  CPU ,

而咱們也知道在  8086  CPU  中,物理地址是由段地址左移 4  位,而後加上偏移地址造成的,

因此,咱們也就只須要提供段地址和偏移地址即 OK 。

8086  CPU  呢,提供了一個  DS  寄存器,而且一般都是經過這個  DS  段寄存器來存放要訪問的數據的段地址 。

DS(Data  Segment):很顯然,DS 中存放的是數據段的段地址 。

可是這裏不得再也不點一下,那就是咱們對段的支持是在  CPU  上體現的,而不是在內存中實現了段,

因此事實上咱們使用的段實際上是一個邏輯概念,便是咱們本身定義的,

再說白了,我定義一個段,我說它是數據段那它就是數據段,我說它是代碼段那麼它就是代碼段,

它們其實都是一塊連續的內存而已,至於爲何要區分爲數據段和代碼段,

很明顯,是用來給咱們編程提供方便的,即咱們在本身的思想上或者說是編碼習慣上規定,

數據放數據段中,代碼放代碼段中 。而咱們在使用數據段的時候,爲了方便或者說是代碼的編寫方便起見,

咱們通常把數據段的段地址放在  DS  寄存器中,固然,若是你硬要以爲  DS  不順眼,那你能夠換個  ES  也是同樣的,

至於  ES(Extra  Segment)  段寄存器的話,天然,是一個附加段寄存器,若是再說得過度點,

就當它是個擴展吧,當你發現,你幾個段寄存器不夠用的時候,你能夠考慮使用   ES  段寄存器,

在使用方式上,則和其餘的段寄存器沒什麼區別  。

下面看一個介紹使用  DS  寄存器的  Demo:

ASSUME CS:CODES CODES SEGMENT START: MOV AX,1000H MOV DS,AX MOV AL,1 MOV BX,0 MOV CX,5 ;設計一個循環,讓其循環 5 次 s: MOV [BX],AL ;這裏 [BX] 並無指定段地址哦 INC AL INC BX LOOP s MOV AH,4CH INT 21H CODES ENDS END START

上面的代碼所作的事情,就是循環將  1,2,3,4,5 寫入到地址  1000H:0000H ,1000H:0001H,

1000H:0002H,1000H:0003H,1000H:0004H  中,

語句的執行過程以下:

首先咱們來看還沒有執行上述任何指令時棧中的數據狀況:

image

而當循環執行完成之後,咱們再來看內存  1000H:0000H 處的值:

image

在這裏,咱們能夠看到確實達到了咱們預期的效果,可是你們注意看代碼:

 s: MOV [BX],AL ;這裏 [BX] 並無指定段地址哦 INC AL INC BX LOOP s 

這裏能夠看到,咱們在  [BX]  中並無給其指定段地址,而只有一個偏移地址,

可是根據咱們一開始的介紹,必需要有段地址和偏移地址纔可以定位內存單元,

莫非這裏出問題了?

其實不是的,由於咱們在最前面定義了段地址   DS  爲  1000H,

當咱們定義好段地址後,每一次  CPU  執行到  [BX]  時,便會自動或者說是默認的從  DS  中取值,

而且將取得的值做爲段地址,所以,當  [BX]  爲  0001H  時,CPU  會從   DS  中取得一個  1000H ,

由這兩個一合成便可以獲得正確的物理地址   1000H:0000H 。

最後還提醒一點,那就是   8086  CPU  不支持直接將一個數據送入段寄存器中,

也就是下面的作法是錯誤的:

 MOV DS,1000H

           

               

標誌寄存器(FLAG):

前面呢,已經介紹了  8086  CPU  14 個寄存器中的 13 個了,下面咱們將介紹最後一個寄存器也就是  FLAG  寄存器,

FLAG  寄存器之因此放到最後一個介紹,是由於其和其餘的一些寄存器不一樣,像   AX,BX,CX,DX  這些寄存器來講,

它們都是用來存放數據的,固然  FLAG  中存放的也是數據啦,

呵呵,不過,AX,BX 這些寄存器中的數據是做爲一個總體使用的,

最多也就分紅一個  AL  和  AH  使用而已,可是在  FLAG  中,數據是按位起做用的,

也就是說,FLAG  中的每個位都表示不一樣的狀態,

因爲一個位也就能表示  0  和  1 ,天然,FLAG  中的每個位就是用來描述狀態的,

並且  FLAG  寄存器中存儲的信息一般又被稱做程序狀態字(PSW) 。

下面我給出一幅  FLAG  寄存器中各個位的示意圖:

image

從上面這幅圖中能夠看出,FLAG  的第  0  個位表示的是 CF  ,第 2 個位表示的是  PF  ,與此類推 . . . . 

首先,咱們來看一個列表:

image

上面的這個表怎麼看呢?咱們經過看下面一幅截圖就知道了 。

image

從上面的標記中能夠看出,從左到右依次表明   OF,DF,SF,ZF,PF,CF  標誌位的值,

再經過與上面的表格相對照能夠知道:

OF = 0 ;

DF = 0 ;

SF = 0 ;

ZF = 0 ;

PF = 0 ;

CF = 0  ;

至於爲何咱們在  Debug  模式下,使用  R  命令時,只會列出這幾個標誌位,我菜的話是由於相對來講,

列出的這幾個標誌位更爲經常使用,其餘的幾個標誌位並不常用的緣故吧 。

下面咱們就按不一樣的位來分別介紹這些位所描述的狀態,以及它們表明的意義:

CF(Carry  FLag) - 進位標誌(第 0 位):

CF:    進位標誌是用來反映計算時是否產生了由低位向高位的進位,或者產生了從高位到低位的借位 。

if(運算過程當中產生了進位或者借位)
{
        CF  =  1;
} else {
        CF  =  0;
}

          

PF(Parity  FLag) - 奇偶標誌(第 2 位):

PF:    奇偶標誌是用來記錄相關指令執行後,其結果的全部的  Bit  位中  1  的個數是否爲偶數 。

if(運算結果中 1 的個數爲偶數)
{
        PF  =  1;
} else {
        PF  =  0;
}

      

AF(Auxiliary  Carry  FLag) - 輔助進位標誌(第 4 位):

AF:    用來輔助進位標誌 。

if(字節操做中發生低半個字節向高半個字節借位或者進位  ||  字操做中發生低字節向高字節借位或者進位)
{
       AF = 1;
} else {
       AF = 0;
}

             

ZF(Zero  FLag) – 零標誌(第 6 位):

ZF:    記錄的是相關的指令執行完畢後,其執行的結果是否爲  0 。

if(執行的結果  ==  0)
{
       ZF = 1;
} else {
       ZF = 0;
}

           

SF(Sign  FLag) - 符號標誌(第 7 位):

SF:    符號標誌,其記錄相關指令執行完之後,其結果是否爲負數 。

if(運算結果爲負數)
{
        SF  =  1;
} else {
        SF  =  0;
}

     

TF(Trap  FLag) - 追蹤標誌(第 8 位):

TF:    追蹤標誌,主要是用於調試時使用 。

if(TF  ==  1)
{
       CPU 進入單步方式;
}

     

IF(Interrupt-Enable  FLag) - 中斷容許標誌(第 9 位):

IF:    中斷容許標誌,其決定  CPU  是否可以響應外部可屏蔽中斷請求(之後會作詳細介紹) 。

if(IF  ==  1)
{
        CPU 可以響應外部的可屏蔽中斷請求;
} else {
        CPU 不可以響應外部的可屏蔽中斷請求;
}

            

DF(Direction  FLag) - 方向標誌(第 10 位):

DF:    方向標誌,其用於在串處理指令中,用來控制每次操做後  SI  和  DI  是自增仍是自減 。

if(DF == 0)
{
        SI++;
DI++;
} else {
SI--;
DI--;
}
              

OF(OverFlow  FLag) - 溢出標誌(第 11 位):

OF:    溢出標誌,其一般記錄了有符號數運算的結果是否發生了溢出 。

if(運算髮生溢出)
{
        OF  =  1;
} else {
        OF  =  0;
}

               

總結

上面呢,從最簡單的開始,按部就班的介紹了  8086  CPU  中的各個寄存器,

同時也經過一些  Demo  來列舉了各個寄存器的使用,

因爲寫的比較基礎,並且量也比較多,因此,形成博文過長了,讀者需必定耐心才能看完,

寫本篇博文呢,並非說未來要用匯編去開發個什麼東東,

實質上,筆者學習彙編的目的也不在此,只是由於先前在接觸到底層的寄存器以及內存時,

筆者總有一絲不爽的感受,老是感受不得要領,因此纔會開始彙編的學習,

這次推出本系列博文,本意也並非說要學習彙編作開發,只是爲了提高內功而已 。

相關文章
相關標籤/搜索