彙編語言8086CPU之寄存器總結

寄存器是什麼呢?

其實很簡單,寄存器就是個存儲信息的單元或者說是器件又或者說是容器而已,就比如內存也是一個存儲介質或者說是存儲單元而已,其實寄存器從理解上來說和內存差不多,只不過寄存器(這裏討論的寄存器都是 CPU 中的寄存器,不包括外設上的寄存器)位於 CPU 內部,而內存位於 CPU 外部,而且,寄存器比內存可是珍貴得多啊,就拿內存和硬盤來比,肯定是內存在使用上珍貴得多,是 PC 中的稀有資源,而寄存器是 CPU 中的稀有資源,內存和寄存器相比就像硬盤和內存相比一樣 。

而對於一個彙編程序員來說,CPU 中主要可以使用的也就是寄存器而已,彙編程序員可以使用指令來讀寫 CPU 中的寄存器,從而可以實現對於 CPU 的控制,當然,不同的 CPU ,寄存器的個數和結構都是不一樣的。比如 8086 CPU 中,寄存器的個數也就 14 個而已,並且 8086 CPU 中所有的寄存器的結構爲 16 位,即一個寄存器中可以存放下 2B 即 2 個字節。

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 位寄存器 。
在這裏插入圖片描述
AX 寄存器:
如上所說,AX 的另外一個名字叫做累加寄存器或者簡稱爲累加器,其可以分爲 2 個獨立的 8 位寄存器 AH 和 AL;在寫彙編程序時,AX 寄存器可以說是使用率最高的寄存器,既然 AX 是數據寄存器的話,那麼理所當然,其可以用來存放普通的數據,由於其是 16 位寄存器,自然也就可以存放 16 位數據,但是因爲其又可以分爲 2 個獨立的 8 位寄存器 AH 和 AL ,所以,在 AH 和 AL 中又可以獨立的存放 2 個 8 位的數據。

1、可以有以下代碼(即將 AX 當做普通的寄存器使用,即可以用來暫存數據):

MOV AX,1234H	       ;向寄存器 AX 傳入數據 1234H
MOV AH,56H	       ;向寄存器 AX 的高 8 位寄存器 AH 中傳入數據 56H
MOV AL,78H		;向寄存器 AX 的低 8 位寄存器 AL 中傳入數據 78H

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

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

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

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 則保存本次除法操作的餘數。

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

MOV AX,800H		;設置 16 位乘數爲 800H
MOV BX,100H		;設置 16 位乘數爲 100H
MOV DX,0H		;清空用來保存乘法結果的高 16 位    
MUL BX		       ;執行計算

當使用 MUL 做乘法運算時,兩個相乘的數要麼都是 8 位,要麼都是 16 位,如果兩個相乘的數都是 8 位的話,則一個默認是放在 AL 中,而另一個 8 位的乘數則位於其他的寄存器或者說是內存字節單元中,而如果兩個相乘的數都是 16 位的話,則一個默認存放在 AX 中,另一個 16 位的則是位於 16 的寄存器中或者是某個內存字單元中。

同時,當 MUL 指令執行完畢後,如果是 8 位的乘法運算,則默認乘法運算的結果是保存在 AX 中,而如果是 16 位的乘法運算的話,則默認乘法運算的結果有 32 位,其中,高位默認保存在 DX 中,而低位則默認保存在 AX 中。

BX 寄存器:

首先可以明確的是,BX 作爲數據寄存器,表明其是可以暫存一般的數據的,即在某種程度上,它和 AX 可以暫存一般性數據的功能是一樣的,其同樣爲了適應以前的 8 位 CPU ,而可以將 BX 當做兩個獨立的 8 位寄存器使用,即有 BH 和 BL,除了暫存一般性數據的功能外,BX 作爲通用寄存器的一種,BX 主要還是用於其專屬功能 – 尋址(尋址物理內存地址)上,BX 寄存器中存放的數據一般是用來作爲偏移地址使用的,何爲偏移地址呢?

既然是偏移地址的話,當然得有一個基地址了,而這個基地址其實就是段地址,這裏就涉及到了段寄存器,當然,在介紹 BX 寄存器的時候,我不會去介紹段寄存器,上面提到 BX 的主要功能是用在尋址上,那麼,其是如何尋址的呢?

簡單說一下,在 8086 CPU 中,CPU 是根據 <段地址:偏移地址> 來進行尋址操作的,而 BX 中存放的數據表示的是偏移地址的話,自然,便可以通過 <段地址:[BX]> 的方式來完成尋址操作了。爲了介紹 BX 在尋址當中的作用,下面我給出一副示意圖:

在這裏插入圖片描述
上面的示意圖表示:可以令 BX = 2,然後通過 DS : [BX] 來訪問到內存中段地址爲 DS,且偏移量爲 2 的內存單元了。上面介紹的這種尋址方式是 BX 在尋址中最最簡單的應用了,而對於稍微複雜的尋址方式,還可以依賴於 SI,DI,BP 等寄存器來一起完成。

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

MOV BX,5H
MOV AH,11H
MOV AH,[BX]		;設置 AX 的值爲偏移地址爲 BX 中的值時所代表的內存單元

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

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  

指針寄存器(BP,SP)

BP 寄存器:

8086 CPU 中的指針寄存器包括兩個,即 SP 和 BP ,在這裏呢,我先只對 BP 寄存器做介紹,因爲 SP 寄存器實質上必須和 SS 段寄存器一起使用,所以,我將會把 SP 寄存器留到後面和 SS 段寄存器一起作介紹。

BP (Base Pointer)也就是基指針寄存器,它和其他的幾個用來進行尋址操作所使用的寄存器(還有 BX,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 中

變址寄存器(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 位的地址,從而尋址能力有 2的20次方 也就是 1MB 的大小,但是 8086 CPU 的寄存器只有 16 位,也就是在 8086 CPU 的內部,一次性處理,傳輸,暫存的地址都只能是 16 位,即 8086 CPU 不能完整的保存下一個物理地址(物理地址爲 20 位),如果單單以最簡單的方式(即直接用 16 位寄存器來保存物理地址)的話,那麼,尋址能力只有 2的16次方 ,也就是 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 位的物理地址 。

下面給出一幅示意圖來表示物理地址的合成:
在這裏插入圖片描述

段:

至於段的話,其實在物理內存中是沒有段這一概念的,事實上,段的概念來自於 CPU ,因爲 CPU 擁有段寄存器,既然在 CPU 中擁有了段寄存器,自然,在 CPU 中就肯定有段的概念了。

其實段也就是在編程時,我們將若干個地址連續的內存單元看做是一個段,然後通過將一個段地址左移 4 位形成基地址,再通過這個基地址來定位這個段的起始地址,然後,再通過偏移地址便可以精確定位到段中的內存單元了,由於段的起始地址是一個段地址左移 4 位,所以很明顯,段的起始地址肯定是 16 的倍數,而且由於一個段內部,只能通過偏移地址來定位,而偏移地址爲 16 位,所以一個段的長度也就是 2^16 也就是 64KB 的大小。

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

何爲數據段呢?
其實就是我們自個兒定義一段內存(當然段起始地址肯定是 16 的倍數,並且段長度 <= 64KB),然後我們在這個段裏頭存放我們所需要使用的數據,這就是數據段;

何爲代碼段呢?
其實也很簡單,也是我們自己在編程的時候定義一段內存,然後這段內存用來存放我們的代碼(也就是指令),既然是存放的代碼,自然就稱之爲代碼段;

何爲棧段呢?
至於棧段的話,有接觸過數據結構的朋友應該是很清楚棧的,而這裏我們也就是在內存中分配出一個段,然後將這個段當做棧來使用。

首先,對於任何一個段來說,均有段地址,而這些段地址是存放在段寄存器中(段寄存器的作用也在於此),但是對於不同的段,它們默認的段地址存放在不同的段寄存器中,像

  • 數據段來說,它的段地址存放在 DS (Data Segment)寄存器中,
  • 代碼段的段地址存放在 CS (Code Segment)寄存器中,
  • 棧段的段地址存放在 SS (Stack Segment)寄存器中 。

下面給出一幅在段中尋址的示意圖:
在這裏插入圖片描述
上面的示意圖中,通過將段地址左移四位,然後與偏移地址相加便可以得到 20 位的物理地址了 。

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

什麼是棧?
通過上面在段中的介紹,棧其實就是一個段,再說白一點,也就是一塊內存,當然,這塊內存是一塊連續的內存 。既然棧是一個段的話,那麼當然就可以以使用段的方式來使用棧,當然,除了像段一樣的使用棧以外,棧還提供了其特殊的訪問方式(如果和段一模一樣的話,那還需要棧幹嗎呢?)

衆所周知,棧是先進後出類型的數據結構,在 8086 CPU 中也是如此,可以通過 ”PUSH“ 指令將數據壓入棧中,然後再通過 ”POP“ 指令將棧頂的元素取出來 。

下面給出一幅示意圖來描述棧:
在這裏插入圖片描述

CS 寄存器 和 IP 寄存器:

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

什麼叫做指示了 CPU 當前將要讀取的指令呢?在 8086 CPU 中,爲什麼 CPU 會自動的執行指令呢?這些指令肯定是存放在內存中的,但是 CPU 怎麼知道這些指令存放在內存的那個位置呢?

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

MOV AX,1234H
MOV BX,AX

而假設這兩條指令在內存中存放爲:
在這裏插入圖片描述
很顯然, 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 總結一下:

  • 你想讓 CPU 執行哪行指令,你就讓 CS:IP 指向保存有指令的那塊內存即可。
  • 任何時候,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

在這裏插入圖片描述
從上面的截圖中可以看出,當我使用 Shell (在 DOS 下也就是 Command 命令解釋器)將可執行文件加載進內存後,可以看到,整個程序的起始地址爲 076AH : 0000 H ,並且,可以看到 CS 的地址爲 0C76AH ,IP 的地址爲 0000H,這正好吻合我們上面對 CS:IP 的分析,很明顯,CPU 將會讀取 MOV AX ,1234H 到 CPU 中並且執行 。

然後我們繼續向下看:
在這裏插入圖片描述

可以看到,我們單步執行後,AX 中的值編成了 1234H ,而 IP 寄存器中的值變成了 0003H,對於 AX 中的值的改變,我們是能夠理解的,但是 IP 中的值爲什麼會從 0000H 變到 0003H 呢?

從最上面的一幅關於指令在內存中的存放可以看出 MOV AX ,1234H 在內存中需要 3 個內存單元存放,也就是 CPU 爲了執行 MOV AX ,1234H 這條指令,已經將內存中相對應的 3 個內存單元讀入內存中了,執行完這條指令後,自然,CPU 就要將偏移地址向下移動 3 個單元,從而使得 CS:IP 指向下一條需要執行的指令了 。

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

在這裏插入圖片描述
從最上面的一幅關於指令在內存中的存放可以看出 MOV BX ,AX 在內存中只佔 2 個內存單元,這也就是爲什麼 IP 這一次只向下移動了 2 個單元的緣故 。

關於 CS: IP 的遐想:

從上面關於 CS:IP 的介紹中,我們可以大膽的猜想,我們只需要通過手動的改變 CS:IP 所指向的內存地址,讓 CS:IP 指向我們另外的代碼,那麼我們就可以讓 CPU 執行我們自己指定的代碼了 。即可以通過修改 CS:IP 來達到我們想要讓 CPU 幹什麼它就幹什麼的目的 。

SS 寄存器和 SP 寄存器:

不知道,大家有沒有注意我在本篇博文的上面介紹關於棧的知識時,我並沒有提到如何找到這個棧,我只提到了一個棧就是先進後出操作,同時可以使用 ”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

然後我們來看棧在內存中的結構圖:
在這裏插入圖片描述語句的執行過程如下:

首先我們來看尚未執行上述任何指令時棧中的數據情況:
在這裏插入圖片描述

然後我們再來依次執行上述指令:
在這裏插入圖片描述
從上副截圖中可以看出已經設置好了 SS:SP ,也就是棧已經設置 OK 了,下面開始往棧中壓入數據了
在這裏插入圖片描述
由於我們壓入棧中的數據爲字數據,即佔 2 個內存單元,所以,每次 SP = SP – 2 ;將 5 個字型數據壓入棧中後,我們可以來查看棧中的數據了。
在這裏插入圖片描述
因此,在內存中的一個好看點的結構圖如下所示:
在這裏插入圖片描述
下面開始進行出棧操作了
在這裏插入圖片描述由於我們彈出棧時的數據爲字數據,即佔 2 個內存單元,所以,每次 SP = SP + 2 ;將 5 個字型數據全部彈出棧中後,我們可以來查看棧中的數據了。
在這裏插入圖片描述

可以看到 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 中,

語句的執行過程如下:
首先我們來看尚未執行上述任何指令時棧中的數據情況:
在這裏插入圖片描述

而當循環執行完成以後,我們再來看內存 1000H:0000H 處的值:
在這裏插入圖片描述
在這裏,我們可以看到確實達到了我們預期的效果,但是大家注意看代碼:

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 寄存器中各個位的示意圖:
在這裏插入圖片描述從上面這幅圖中可以看出,FLAG 的第 0 個位表示的是 CF ,第 2 個位表示的是 PF ,與此類推 . . . .
首先,我們來看一個列表:
在這裏插入圖片描述
上面的這個表怎麼看呢?我們通過看下面一幅截圖就知道了 。
在這裏插入圖片描述

從上面的標記中可以看出,從左到右依次代表 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;
}

文章參考自Just Love U

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章