王爽匯編語言第三版是一款高清完整版的專業編程圖書,該書結構設計合理,內容全面涵蓋知識點豐富,適合自學者使用,有需要者快來
一、基礎知識
1、指令
機器指令:CPU能直接識別并執行的二進制編碼
匯編指令:匯編指令是機器指令的助記符,同機器指令一一對應。
指令:指令通常由操作碼和地址碼(操作數)兩部分組成
指令集:每種CPU都有自己的匯編指令集。
匯編語言由3類指令組成。
匯編指令
偽指令:沒有對應的機器碼,由編譯器執行,計算機并不執行
其他符號:如+、-、*、/等,由編譯器識別,沒有對應的機器碼。
編譯器:夠將匯編指令轉換成機器指令的翻譯程序每一種CPU都有自己的匯編指令集。
在內存或磁盤上,指令和數據沒有任何區別,都是二進制信息
2、存儲器
隨機存儲器(RAM)在程序的執行過程中可讀可寫,必須帶電存儲
只讀存儲器(ROM)在程序的執行過程中只讀,關機數據不丟失
(以上3張圖片來自王道考研 - 計算機組成原理課件)
3、總線
1、總線
總線是連接各個部件的信息傳輸線,是各個部件共享的傳輸介質。
主板上有核心器件和一些主要器件,這些器件通過總線(地址總線、數據總線、控制總線)相連。這些器件有CPU、存儲器、外圍芯片組、擴展插槽等。擴展插槽上一般插有RAM內存條和各類接口卡。
總線根據位置分類:
-
片內總線(芯片內部總線)
-
系統總線(計算機各部件之間的信息傳輸線)
根據傳送信息的不同,系統總線從邏輯上又分為3類,地址總線、控制總線和數據總線。
CPU要想進行數據的讀寫,必須和外部器件(標準的說法是芯片)進行以下3類信息的交互。
- 地址總線:CPU通過地址總線來指定存儲單元
1根導線可以傳送的穩定狀態只有兩種,高電平或是低電平。用二進制表示就是1或0
圖示有10根地址線即一次可以傳輸10位,訪問存儲單元地址為1011,尋址范圍為0 ~ (210 - 1)
-
數據總線:CPU與內存或其他器件之間的數據傳送是通過數據總線來進行的
8根數據線一次可傳送一個8位二進制數據(即一個字節),傳送2個字節需要兩次;16根數據線一次可傳送2個字節(內存對齊核心原理) -
控制總線:CPU對外部器件的控制是通過控制總線來進行的。
有多少根控制總線,就意味著CPU提供了對外部器件的多少種控制。
所以,控制總線的寬度決定了CPU對外部器件的控制能力。
2、CPU對存儲器的讀寫
1、 CPU通過地址線將地址信息3發出。
2、 CPU通過控制線發出內存讀命令,選中存儲器芯片,并通知它,將要從中讀取數據。
3、 存儲器將3號單元中的數據8通過數據線送入CPU。寫操作與讀操作的步驟相似。
聯想:在組成原理中用微操作表示:(PC) → MAR; 1 → R; M(MAR) → MDR; …
3、CPU對外設的控制
CPU對外設都不能直接控制,如顯示器、音箱、打印機等。
直接控制這些設備進行工作的是插在擴展插槽上的接口卡。
擴展插槽通過總線和CPU相連,所以接口卡也通過總線同CPU相連。CPU可以直接控制這些接口卡,從而實現CPU對外設的間接控制。
如:CPU無法直接控制顯示器,但CPU可以直接控制顯卡,從而實現對顯示器的間接控制
4、內存地址空間
CPU將系統中各類存儲器看作一個邏輯存儲器,這個邏輯存儲器就是我們所說的內存地址空間。
對于CPU,所有存儲器中的存儲單元都處于一個統一的邏輯存儲器中,它的容量受CPU尋址能力限制。(或許就是計組中學的統一編址吧)
每個物理存儲器在這個邏輯存儲器中占有一個地址段,即一段地址空間。CPU在這段地址空間中讀寫數據,實際上就是在相對應的物理存儲器中讀寫數據(對ROM寫無效)。
二、寄存器
1、寄存器
CPU由運算器、控制器、寄存器等器件構成,這些器件靠片內總線相連。
運算器進行信息處理;控制器控制各種器件進行工作;寄存器進行信息存儲;
8086CPU有14個寄存器:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW都是16位
16位結構CPU具有下面幾方面的結構特性。
- 運算器一次最多可以處理16位的數據;
- 寄存器的最大寬度為16位;
- 寄存器和運算器之間的通路為16位。
8086CPU可以一次性處理以下兩種尺寸的數據。
- 字節:記為byte,一個字節由8個bit組成,可以存在8位寄存器中。
- 字:記為word,一個字由兩個字節組成,可以存在一個16位寄存器中(16位CPU)
8086采用小端模式:高地址存放高位字節,低地址存放低位字節。
2、通用寄存器
通用寄存器:通常用來存放一般性的數據,有AX、BX、CX、DX,它們可分為兩個可獨立使用的8位寄存器,
16位 | 8高位 | 8低位 |
---|---|---|
AX | AH | AL |
BX | BH | BL |
CX | CH | CL |
DX | DH | DL |
在進行數據傳送或運算時,要注意指令的兩個操作對象的位數應當是一致的
一個8位寄存器所能存儲的數據范圍是0 ~ 28-1。
3、8086CPU給出物理地址的方法
8086CPU有20位地址總線,可以傳送20位地址,達到1MB尋址能力。
8086CPU又是16位結構,在內部一次性處理、傳輸、暫時存儲的地址為16位。
從8086CPU的內部結構來看,如果將地址從內部簡單地發出,那么它只能送出16位的地址,表現出的尋址能力只有64KB。
8086CPU采用一種在內部用兩個16位地址合成的方法來形成一個20位的物理地址。
當8086CPU要讀寫內存時:
- CPU中的相關部件提供兩個16位的地址,一個稱為段地址,另一個稱為偏移地址;
- 地址加法器將兩個16位地址合成為一個20位的物理地址;
地址加法器采用物理地址 = 段地址×16 + 偏移地址的方法用段地址和偏移地址合成物理地址。
例如,8086CPU要訪問地址為123C8H
的內存單元,1230H
左移一位(空出4位)加上00C8H
合成123C8H
4、段寄存器
我們可以將一段內存定義為一個段,用一個段地址指示段,用偏移地址訪問段內的單元,可以用分段的方式來管理內存。
用一個段存放數據,將它定義為“數據段”;
用一個段存放代碼,將它定義為“代碼段”;
用一個段當作棧,將它定義為“棧段”。
注意:
- 一個段的起始地址一定是16的倍數;
- 偏移地址為16位,變化范圍為0-FFFFH,所以一個段的長度最大為64KB。
- CPU可以用不同的段地址和偏移地址形成同一個物理地址。
段寄存器:8086CPU有4個段寄存器:CS、DS、SS、ES
,提供內存單元的段地址。
1、CS和IP
CS為代碼段寄存器,IP為指令指針寄存器,
CPU將CS、IP中的內容當作指令的段地址和偏移地址,用它們合成指令的物理地址,
CPU將CS:IP指向的內容當作指令執行。(即PC)
8086CPU的工作過程簡要描述
- 從CS:IP指向的內存單元讀取指令,讀取的指令進入指令緩沖器;
- IP=IP+所讀取指令的長度,從而指向下一條指令;
- 執行指令。轉到步驟1,重復這個過程。
在8086CPU加電啟動或復位后(即CPU剛開始工作時)CS和IP被設置為CS=FFFFH,IP=0000H,即在8086PC機剛啟動時,FFFF0H單元中的指令是8086PC機開機后執行的第一條指令。
8086CPU提供轉移指令修改CS、IP的內容。
-
jmp 段地址:偏移地址:用指令中給出的段地址修改CS,偏移地址修改IP。如:
jmp 2AE3:3
-
jmp 某一合法寄存器:僅修改IP的內容。如:
jmp ax
。在含義上好似:mov IP,ax
8086CPU不支持將數據直接送入段寄存器的操作,這屬于8086CPU硬件設計
2、DS 和 [address]
DS寄存器:通常用來存放要訪問數據的段地址
[address]表示一個偏移地址為address的內存單元,段地址默認放在ds中
通過數據段段地址和偏移地址即可定位內存單元。
mov bx, 1000H ;8086CPU不支持將數據直接送入段寄存器的操作
mov ds, bx ;ds存放數據段地址
mov [0], al ;將al數據(1字節)存到1000H段的0偏移地址處,即10000H
mov ax, [2] ;將數據段偏移地址2處的一個字(8086為2字節)存放到ax寄存器
add cx, [4] ;將偏移地址4處的一個字數據加上cx寄存器數據放到cx寄存器
sub dx, [6] ;dx寄存器數據減去數據段偏移地址6處的字數據存到dx
3、SS 和 SP
在基于8086CPU編程的時候,可以將一段內存當作棧來使用。
棧段寄存器SS,存放段地址,SP寄存器存放偏移地址,任意時刻,SS:SP指向棧頂元素
8086CPU中,入棧時,棧頂從高地址向低地址方向增長。
push ax表示將寄存器ax中的數據送入棧中,由兩步完成。
1、SP=SP-2,SS:SP指向當前棧頂前面的單元,以當前棧頂前面的單元為新的棧頂;
2、將ax中的內容送入SS:SP指向的內存單元處,SS:SP此時指向新棧頂。
pop ax
表示從棧頂取出數據送入ax,由以下兩步完成。
- 將SS:SP指向的內存單元處的數據送入ax中;
- SP=SP+2,SS:SP指向當前棧頂下面的單元,以當前棧頂下面的單元為新的棧頂。
實驗
- 將10000H~1000FH這段空間當作棧,初始狀態棧是空的;
- 設置AX=001AH,BX=001BH;
- 將AX、BX中的數據入棧;
- 然后將AX、BX清零;
- 從棧中恢復AX、BX原來的內容。
mov ax, 1000H
mov ss, ax
mov sp, 0010H ;初始化棧頂
mov ax, 001AH
mov bx, 001BH
push ax
push bx ;ax、bx入棧
sub ax, ax ;將ax清零,也可以用mov ax,0,
;sub ax,ax的機器碼為2個字節,
;mov ax,0的機器碼為3個字節。
sub bx, bx
pop bx ;從棧中恢復ax、bx原來的數據
pop ax ;
三、第一個程序
1、匯編程序從寫出到執行的過程
加載后,CPU的CS:IP指向程序的第一條指令(即程序的入口)
;1.asm
assume cs:codesg ;將用作代碼段的段codesg和段寄存器cs聯系起來。
codesg segment ;定義一個段,段的名稱為“codesg”,這個段從此開始
;codesg是一個標號,作為一個段的名稱,最終被編譯連接成一個段的段地址
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H ;這兩條指令實現程序的返回
codesg ends ;名稱為“codesg”的段到此結束
end ;編譯器在編譯匯編程序的過程中,碰到了偽指令end,結束對源程序的編譯
2、程序執行過程跟蹤
DOS系統中.EXE文件中的程序的加載過程
四、[bx] 和 loop指令
1、[bx] 和 loop指令
[bx] 的含義:[bx]同樣表示一個內存單元,它的偏移地址在bx中,段地址默認在ds中
loop指令的格式是:loop 標號,CPU執行loop指令的時候,要進行兩步操作,
-
(cx) = (cx) - 1;
-
判斷 cx 中的值,不為零則轉至標號處執行程序,如果為零則向下執行。
例如:計算212
assume cs:code
code segment
mov ax, 2
mov cx, 11 ;循環次數
s: add ax, ax
loop s ;在匯編語言中,標號代表一個地址,標號s實際上標識了一個地址,
;這個地址處有一條指令:add ax,ax。
;執行loop s時,首先要將(cx)減1,然后若(cx)不為0,則向前
;轉至s處執行add ax,ax。所以,可以利用cx來控制add ax,ax的執行次數。
mov ax,4c00h
int 21h
code ends
end
loop 和 [bx] 的聯合應用
計算ffff:0 ~ ffff:b單元中的數據的和,結果存儲在dx中
問題分析:
這些內存單元都是字節型數據范圍0 ~ 255 ,12個字節數據和不會超過65535,dx可以存下
對于8位數據不能直接加到 dx
解決方案:
用一個16位寄存器來做中介。將內存單元中的8位數據賦值到一個16位寄存器a中,再將ax中的數據加到dx
assume cs:code
code segment
mov ax, 0ffffh ;在匯編源程序中,數據不能以字母開頭,所以要在前面加0。
mov ds, ax
mov bx, 0 ;初始化ds:bx指向ffff:0
mov dx, 0 ;初始化累加寄存器dx,(dx)= 0
mov cx, 12 ;初始化循環計數寄存器cx,(cx)= 12
s: mov al, [bx]
mov ah, 0
add dx, ax ;間接向dx中加上((ds)* 16 +(bx))單元的數值
inc bx ;ds:bx指向下一個單元
loop s
mov ax, 4c00h
int 21h
code ends
end
2、段前綴
mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
mov ax, ss:[0]
mov ax, cs:[0]
這些出現在訪問內存單元的指令中,用于顯式地指明內存單元的段地址
的“ds:”,“cs:”,“ss:”,“es:”,在匯編語言中稱為段前綴。
段前綴的使用
將內存ffff:0 ~ ffff:b
單元中的數據復制到0:200 ~ 0:20b
單元中。
assume cs:code
code segment
mov ax, 0ffffh
mov ds, ax ;(ds)= 0ffffh
mov ax, 0020h
mov es, ax ;(es)= 0020h 0:200 等效于 0020:0
mov bx, 0 ;(bx)= 0,此時ds:bx指向ffff:0,es:bx指向0020:0
mov cx,12 ;(cx)=12,循環12次
s: mov dl,[bx] ;(d1)=((ds)* 16+(bx)),將ffff:bx中的字節數據送入dl
mov es:[bx],dl ;((es)*16+(bx))=(d1),將dl中的數據送入0020:bx
inc bx ;(bx)=(bx)+1
loop s
mov ax,4c00h
int 21h
code ends
end
五、包含多個段的程序
程序中對段名的引用,將被編譯器處理為一個表示段地址的數值。
mov ax, data
mov ds, ax
mov bx, ds:[6]
在代碼段中使用數據
;計算 8 個數據的和存到 ax 寄存器
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;define word 定義8個字形數據
start: mov bx, 0 ;標號start
mov ax, 0
mov cx, 8
s: add ax, cs:[bx]
add bx, 2
loop s
mov ax, 4c00h
int 21h
code ends
end start ;end除了通知編譯器程序結束外,還可以通知編譯器程序的入口在什么地方
;用end指令指明了程序的入口在標號start處,也就是說,“mov bx,0”是程序的第一條指令。
在代碼段中使用棧
;利用棧,將程序中定義的數據逆序存放。
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 0-15單元
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 16-47單元作為棧使用
start: mov ax, cs
mov ss, ax
mov sp, 30h ;將設置棧頂ss:sp指向棧底cs:30。 30h = 48d
mov bx, 0
mov cx, 8
s: push cs:[bx]
add bx, 2
loop s ;以上將代碼段0~15單元中的8個字型數據依次入棧
mov bx, 0
mov cx, 8
s0: pop cs:[bx]
add bx,2
loop s0 ;以上依次出棧8個字型數據到代碼段0~15單元中
mov ax,4c00h
int 21h
codesg ends
end start ;指明程序的入口在start處
將數據、代碼、棧放入不同的段
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;0-15單元
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;0-31單元
stack ends
code segment
start: mov ax, stack;將名稱為“stack”的段的段地址送入ax
mov ss, ax
mov sp, 20h ;設置棧頂ss:sp指向stack:20。 20h = 32d
mov ax, data ;將名稱為“data”的段的段地址送入ax
mov ds, ax ;ds指向data段
mov bx, 0 ;ds:bx指向data段中的第一個單元
mov cx, 8
s: push [bx]
add bx, 2
loop s ;以上將data段中的0~15單元中的8個字型數據依次入棧
mov bx, 0
mov cx, 8
s0: pop [bx]
add bx, 2
loop s0 ;以上依次出棧8個字型數據到data段的0~15單元中
mov ax, 4c00h
int 21h
code ends
end start
;“end start”說明了程序的入口,這個入口將被寫入可執行文件的描述信息,
;可執行文件中的程序被加載入內存后,CPU的CS:IP被設置指向這個入口,從而開始執行程序中的第一條指令
關于可執行文件結構與程序入口的詳細描述參考:PE文件結構
六、更靈活的定位內存地址的方法
1、and 和 or
and指令:邏輯與指令,按位進行與運算。
mov al, 01100011B
and al, 00111011B
執行后:al=00100011B即都為1才為1
or指令:邏輯或指令,按位進行或運算。
mov al, 01100011B
or al, 00111011B
執行后:al=01111011B 即只要有一個為1就為1
關于ASCII碼
世界上有很多編碼方案,有一種方案叫做ASCII編碼,是在計算機系統中通常被采用的。簡單地說,所謂編碼方案,就是一套規則,它約定了用什么樣的信息來表示現實對象。比如說,在ASCII編碼方案中,用61H表示“a”,62H表示“b”。一種規則需要人們遵守才有意義。
在文本編輯過程中,我們按一下鍵盤的a鍵,就會在屏幕上看到“a”。我們按下鍵盤的a鍵,這個按鍵的信息被送入計算機,計算機用ASCII碼的規則對其進行編碼,將其轉化為61H存儲在內存的指定空間中;文本編輯軟件從內存中取出61H,將其送到顯卡上的顯存中;工作在文本模式下的顯卡,用ASCII碼的規則解釋顯存中的內容,
61H被當作字符“a”,顯卡驅動顯示器,將字符“a”的圖像畫在屏幕上。我們可以看到,顯卡在處理文本信息的時候,是按照ASCII碼的規則進行的。這也就是說,如果我們要想在顯示器上看到“a”,就要給顯卡提供“a”的ASCIⅡ碼,61H。如何提供?當然是寫入顯存中。
以字符形式給出的數據
assume cs:code,ds:data
data segment
db 'unIx' ;相當于“db 75H,6EH,49H,58H”
db 'foRK'
data ends
code segment
start: mov al, 'a' ;相當于“mov al, 61H”,“a”的ASCI碼為61H;
mov b1, 'b'
mov ax, 4c00h
int 21h
code ends
end start
大小寫轉換的問題
小寫字母的ASCII碼值比大寫字母的ASCII碼值大20H
大寫字母ASCII碼的第5位為0,小寫字母的第5位為1(其他一致)
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTion'
datasg end
codesg segment
start: mov ax, datasg
mov ds, ax ;設置ds 指向 datasg段
mov bx, 0 ;設置(bx)=0,ds:bx指向'BaSic'的第一個字母
mov cx, 5 ;設置循環次數5,因為'Basic'有5個字母
s: mov al, [bx] ;將ASCII碼從ds:bx所指向的單元中取出
and al, 11011111B;將al中的ASCII碼的第5位置為0,變為大寫字母
mov [bx], al ;將轉變后的ASCII碼寫回原單元
inc bx ;(bx)加1,ds:bx指向下一個字母
loop s
mov bx, 5 ;設置(bx)=5,ds:bx指向,iNfOrMaTion'的第一個字母
mov cx, 11 ;設置循環次數11,因為‘iNfOrMaTion'有11個字母
s0: mov al, [bx]
or al, 00100000B;將a1中的ASCII碼的第5位置為1,變為小寫字母
mov [bx], al
inc bx
loop s0
mov ax, 4c00h
int 21h
codesg ends
2、[bx+idata]
[bx+idata]表示一個內存單元, 例如:mov ax, [bx+200]
該指令也可以寫成如下格式:
mov ax, [200+bx]
mov ax, 200[bx]
mov ax, [bx].200
用[bx+idata]的方式進行數組的處理
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC';轉為大寫
db 'MinIx';轉為小寫
datasg ends
codesg segment
start:
mov ax, datasg
mov ds, ax
mov bx, 0 ;初始ds:bx
mov cx, 5
s: mov al, 0[bx]
and al, 11011111b ;轉為大寫字母
mov 0[bx], al ;寫回
mov al, 5[bx] ;[5 + bx]
or al, 00100000b ;轉為小寫字母
mov 5[bx], al
inc bx
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
C語言描述
int main()
{
char a[] = "BaSic";
char b[] = "MinIX";
int i = 0;
do
{
a[i] = a[i] & 0xDF;
b[i] = b[i] | 0x20;
i++;
} while(i < 5);
return 0;
}
3、SI 、DI 與 尋址方式的靈活應用
1、si 、di
si和di是8086CPU中和bx功能相近的寄存器,si和di不能夠分成兩個8位寄存器來使用。
assume cs: codesg, ds: datasg
datasg segment
db 'welcome to masm!';用si和di實現將字符串‘welcome to masm!"復制到它后面的數據區中。
db '................'
datasg ends
codesg segment
start: mov ax, datasg
mov ds, ax
mov si, 0
mov cx, 8
s: mov ax, 0[si] ;[0 + si]
mov 16[si], ax ;[16 + si] 使用[bx +idata]方式代替di,使程序更簡潔
add si, 2
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
2、[bx + si] 和 [bx + di]
[bx+si]和[bx+di]的含義相似
[bx+si]表示一個內存單元,它的偏移地址為(bx)+(si)
指令mov ax, [bx + si]的含義:將一個內存單元字數據的內容送入ax,段地址在ds中
該指令也可以寫成如下格式:mov ax, [bx][si]
3、[bx+si+idata]和[bx+di+idata]
[bx+si+idata]表示一個內存單元,它的偏移地址為(bx)+(si)+idata
指令mov ax,[bx+si+idata]的含義:將一個內存單元字數據的內容送入ax,段地址在ds中
4、不同的尋址方式的靈活應用
[idata]用一個常量來表示地址,可用于直接定位一個內存單元;
[bx]用一個變量來表示內存地址,可用于間接定位一個內存單元;
[bx+idata]用一個變量和常量表示地址,可在一個起始地址的基礎上用變量間接定位一個內存單元;
[bx+si]用兩個變量表示地址;
[bx+si+idata]用兩個變量和一個常量表示地址。
;將datasg段中每個單詞改為大寫字母
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db 'ibm ' ;16
db 'dec '
db 'dos '
db 'vax ' ;看成二維數組
datasg ends
stacksg segment ;定義一個段,用來做棧段,容量為16個字節
dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends
codesg segment
start: mov ax, stacksg
mov ss, ax
mov sp, 16
mov ax, datasg
mov ds, ax
mov bx, 0 ;初始ds:bx
;cx為默認循環計數器,二重循環只有一個計數器,所以外層循環先保存cx值,再恢復,我們采用棧保存
mov cx, 4
s0: push cx ;將外層循環的cx值入棧
mov si, 0
mov cx, 3 ;cx設置為內層循環的次數
s: mov al, [bx+si]
and al, 11011111b ;每個字符轉為大寫字母
mov [bx+si], al
inc si
loop s
add bx, 16 ;下一行
pop cx ;恢復cx值
loop s0 ;外層循環的loop指令將cx中的計數值減1
mov ax,4c00H
int 21H
codesg ends
end start
七、數據處理的兩個基本問題
1、 bx、si、di和bp
在8086CPU中,只有這4個寄存器可以用在“[…]”中來進行內存單元的尋址。
在[ ]中,這4個寄存器可以單個出現,或只能以4種組合出現:bx和si、bx和di、bp和si、bp和di。
只要在[……]中使用寄存器bp,而指令中沒有顯性地給出段地址, 段地址就默認在ss中
2、機器指令處理的數據在什么地方
數據處理大致可分為3類:讀取、寫入、運算。
在機器指令這一層來講,并不關心數據的值是多少,而關心指令執行前一刻,它將要處理的數據所在的位置。指令在執行前,所要處理的數據可以在3個地方:CPU內部、內存、端口
3、匯編語言中數據位置的表達
匯編語言中用3個概念來表達數據的位置
立即數(idata)
mov ax, 1 ;對于直接包含在機器指令中的數據(執行前在CPU的指令緩沖器中)
add bx, 2000h ;在匯編語言中稱為:立即數(idata)
or bx, 00010000b
mov al, 'a'
寄存器
mov ax, bx ;指令要處理的數據在寄存器中,在匯編指令中給出相應的寄存器名。
mov ds, ax
push bx
mov ds:[0], bx
push ds
mov ss, ax
mov sp, ax
段地址(SA)和偏移地址(EA)
;指令要處理的數據在內存中,在匯編指令中可用[X]的格式給出EA,SA在某個段寄存器中。
mov ax, [0]
mov ax, [di]
mov ax, [bx+8]
mov ax, [bx+si]
mov ax, [bx+si+8] ;以上段地址默認在ds中mov ax, [bp]
mov ax, [bp+8]
mov ax, [bp+si]
mov ax, [bp+si+8] ;以上段地址默認在ss中mov ax, ds:[bp]
mov ax, es:[bx]
mov ax, ss:[bx+si]
mov ax, cs:[bx+si+8] ;顯式給出存放段地址的寄存器
尋址方式
4、指令要處理的數據有多長
8086CPU的指令,可以處理兩種尺寸的數據,byte和word
通過寄存器名指明要處理的數據的尺寸。
例如: mov al, ds:[0] 寄存器al指明了數據為1字節
在沒有寄存器名存在的情況下,用操作符X ptr指明內存單元的長度,X在匯編指令中可以為word或byte。
例如:mov byte ptr ds:[0], 1 byte ptr 指明了指令訪問的內存單元是一個字節單元
有些指令默認了訪問的是字單元還是字節單元
例如,push [1000H],push 指令只進行字操作。
5、尋址方式的綜合應用
mov ax, seg
mov ds, ax
mov bx, 60h ;確定記錄地址,ds:bxmov word ptr [bx+0ch], 38 ;排名字段改為38 [bx].0ch
add word ptr [bx+0eh], 70 ;收入字段增加70 [bx].0eh
mov si, 0 ;用si來定位產品字符串中的字符
mov byte ptr [bx+10h+si], 'V' ;[bx].10h[si]
inc si
mov byte ptr [bx+10h+si], 'A'
inc si
mov byte ptr [bx+10h+si], 'X'
C語言描述
/*定義一個公司記錄的結構體*/
struct company
{
char cn[3];/*公司名稱*/
char hn[9];/*總裁姓名*/
int pm;/*排名*/
int sr;/*收入*/
char cp[3];/*著名產品*/
};
//sizeof (struct company) == 24
int main()
{
/*定義一個公司記錄的變量,內存中將存有一條公司的記錄*/
struct company dec = {"DEC", "Ken Olsen", 137, 40, "PDP"};
int i;
dec.pm = 38;
dec.sr = dec.sr + 70;
i = 0;
dec.cp[i] = 'V'; //mov byte ptr [bx].10h[si], 'V'
i++;
dec.cp[i] = 'A';
i++;
dec.cp[i] = 'X';
return 0;
}
6、div指令、dd、dup、mul指令
div是除法指令
除數:有8位和16位兩種,在一個寄存器或內存單元中。
被除數:默認放在AX或DX和AX中,
如果除數為8位,被除數則為16位,默認在AX中存放;
如果除數為16位,被除數則為32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
結果:
如果除數為8位,則AL存儲除法操作的商,AH存儲除法操作的余數;
如果除數為16位,則AX存儲除法操作的商,DX存儲除法操作的余數。
;利用除法指令計算100001/100。
;100001D = 186A1H
mov dx, 1
mov ax, 86A1H ;(dx)*10000H+(ax)=100001
mov bx, 100
div bx;利用除法指令計算1001/100
mov ax, 1001
mov bl, 100
div b1
偽指令dd
db和dw定義字節型數據和字型數據。
dd是用來定義dword(double word,雙字)型數據的偽指令
操作符dup
dup在匯編語言中同db、dw、dd等一樣,也是由編譯器識別處理的符號。
它和db、dw、dd等數據定義偽指令配合使用,用來進行數據的重復
db 3 dup (0) ;定義了3個字節,它們的值都是0,相當于db 0,0,0。
db 3 dup (0, 1, 2) ;定義了9個字節,它們是0、1、2、0、1、2、0、1、2,相當于db 0,1,2,0,1,2,0,1,2。
db 3 dup ('abc', 'ABC') ;定義了18個字節,它們是abcABCabcABCabcABCC,相當于db 'abc', 'ABC' ,'abc' , 'ABC, 'abc', 'ABC'。
mul 指令
mul是乘法指令,使用 mul 做乘法的時候:相乘的兩個數:要么都是8位,要么都是16位。
-
8 位: AL中和 8位寄存器或內存字節單元中;
-
16 位: AX中和 16 位寄存器或內存字單元中。
結果
-
8位:AX中;
-
16位:DX(高位)和 AX(低位)中。
格式:mul 寄存器 或 mul 內存單元
;計算100*10
;100和10小于255,可以做8位乘法
mov al,100
mov bl,10
mul bl;結果: (ax)=1000(03E8H)
;計算100*10000
;100小于255,可10000大于255,所以必須做16位乘法,程序如下:
mov ax,100
mov bx,10000
mul bx;結果: (ax)=4240H,(dx)=000FH (F4240H=1000000)
八、轉移指令的原理
可以修改IP,或同時修改CS和IP的指令統稱為轉移指令。概括地講,轉移指令就是可以控制CPU執行內存中某處代碼的指令。
8086CPU的轉移行為有以下幾類。
- 只修改IP時,稱為段內轉移,比如:jmp ax。
- 同時修改CS和IP時,稱為段間轉移,比如:jmp 1000:0。
由于轉移指令對IP的修改范圍不同,段內轉移又分為:短轉移和近轉移。
- 短轉移IP的修改范圍為-128 ~ 127。
- 近轉移IP的修改范圍為-32768 ~ 32767。
8086CPU的轉移指令分為以下幾類。
- 無條件轉移指令(如:jmp)
- 條件轉移指令
- 循環指令(如:loop)
- 過程
- 中斷
1、操作符offset
操作符offset在匯編語言中是由編譯器處理的符號,它的功能是取得標號的偏移地址。
;將s處的一條指令復制到s0處
assume cs:codesg
codesg segment
s: mov ax, bx ;(mov ax,bx 的機器碼占兩個字節)
mov si, offset s ;獲得標號s的偏移地址
mov di, offset s0 ;獲得標號s0的偏移地址
mov ax, cs:[si]
mov cs:[di], ax
s0: nop ;(nop的機器碼占一個字節)
nop
codesg ends
ends
2、jmp指令
jmp為無條件轉移,轉到標號處執行指令可以只修改IP,也可以同時修改CS和IP;
jmp指令要給出兩種信息:
- 轉移的目的地址
- 轉移的距離(段間轉移、段內短轉移,段內近轉移)
jmp short 標號 jmp near ptr 標號 jcxz 標號 loop 標號 等幾種匯編指令,它們對 IP的修改
是根據轉移目的地址和轉移起始地址之間的位移來進行的。在它們對應的機器碼中不包含轉移的目的地址,而包含的是到目的地址的位移距離。
1、依據位移進行轉移的jmp指令
jmp short 標號(段內短轉移)
指令“jmp short 標號”的功能為(IP)=(IP)+8位位移,轉到標號處執行指令
(1)8位位移 = “標號”處的地址 - jmp指令后的第一個字節的地址;
(2)short指明此處的位移為8位位移;
(3)8位位移的范圍為-128~127,用補碼表示
(4)8位位移由編譯程序在編譯時算出。
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s ;s不是被翻譯成目的地址
add ax, 1
s:inc ax ;程序執行后, ax中的值為 1
codesg ends
end start
CPU不需要這個目的地址就可以實現對IP的修改。這里是依據位移進行轉移
jmp short s指令的讀取和執行過程:
- (CS)=0BBDH,(IP)=0006,上一條指令執行結束后CS:IP指向EB 03(jmp short s的機器碼);
- 讀取指令碼EB 03進入指令緩沖器;
- (IP) = (IP) + 所讀取指令的長度 = (IP) + 2 = 0008,CS:IP指向add ax,1;
- CPU指行指令緩沖器中的指令EB 03;
- 指令EB 03執行后,(IP)=000BH,CS:IP指向inc ax
jmp near ptr 標號 (段內近轉移)
指令“jmp near ptr 標號”的功能為:(IP) = (IP) + 16位位移。
2、轉移的目的地址在指令中的jmp指令
jmp far ptr 標號(段間轉移或遠轉移)
指令 “jmp far ptr 標號” 功能如下:
- (CS) = 標號所在段的段地址;
- (IP) = 標號所在段中的偏移地址。
- far ptr指明了指令用標號的段地址和偏移地址修改CS和IP。
assume cs:codesg
codesg segment
start: mov ax, 0
mov bx, 0
jmp far ptr s ;s被翻譯成轉移的目的地址0B01 BD0B
db 256 dup (0) ;轉移的段地址:0BBDH,偏移地址:010BH
s: add ax,1
inc ax
codesg ends
end start
3、轉移地址在寄存器或內存中的jmp指令
jmp 16位寄存器 功能:IP =(16位寄存器)
轉移地址在內存中的jmp指令有兩種格式:
jmp word ptr 內存單元地址(段內轉移)
功能:從內存單元地址處開始存放著一個字,是轉移的目的偏移地址。
mov ax, 0123H
mov ds:[0], ax
jmp word ptr ds:[0]
;執行后,(IP)=0123H
jmp dword ptr 內存單元地址(段間轉移)
功能:從內存單元地址處開始存放著兩個字,高地址處的字是轉移的目的段地址,低地址處是轉移的目的偏移地址。
1、(CS)=(內存單元地址+2)
2、(IP)=(內存單元地址)
mov ax, 0123H
mov ds:[0], ax;偏移地址
mov word ptr ds:[2], 0;段地址
jmp dword ptr ds:[0]
;執行后,
;(CS)=0
;(IP)=0123H
;CS:IP 指向 0000:0123。
4、jcxz指令和loop指令
jcxz指令
jcxz指令為有條件轉移指令,所有的有條件轉移指令都是短轉移,
在對應的機器碼中包含轉移的位移,而不是目的地址。對IP的修改范圍都為-128~127。
指令格式:jcxz 標號(如果(cx)=0,則轉移到標號處執行。)
當(cx) = 0時,(IP) = (IP) + 8位位移
- 8位位移 = “標號”處的地址 - jcxz指令后的第一個字節的地址;
- 8位位移的范圍為-128~127,用補碼表示;
- 8位位移由編譯程序在編譯時算出。
當(cx)!=0時,什么也不做(程序向下執行)
loop指令
loop指令為循環指令,所有的循環指令都是短轉移,在對應的機器碼中包含轉移的位移,而不是目的地址。
對IP的修改范圍都為-128~127。
指令格式:loop 標號 ((cx) = (cx) - 1,如果(cx) ≠ 0,轉移到標號處執行)。
(cx) = (cx) - 1;如果 (cx) != 0,(IP) = (IP) + 8位位移。
- 8位位移 = 標號處的地址 - loop指令后的第一個字節的地址;
- 8位位移的范圍為-128~127,用補碼表示;
- 8位位移由編譯程序在編譯時算出。
如果(cx)= 0,什么也不做(程序向下執行)。
九、call和ret指令
call和ret指令都是轉移指令,它們都修改IP,或同時修改CS和IP。
1、ret 和 retf
ret指令用棧中的數據,修改IP的內容,從而實現近轉移;
retf指令用棧中的數據,修改CS和IP的內容,從而實現遠轉移。
CPU執行ret指令時,相當于進行: pop IP:
(1)(IP) = ( (ss) * 16 + (sp) )
(2)(sp) = (sp) + 2
CPU執行retf指令時,相當于進行:pop IP, pop CS:
(1)(IP) = ( (ss) * 16 + (sp) )
(2)(sp) = (sp) + 2
(3)(CS) = ( (ss) * 16 + (sp) )
(4)(sp) = (sp) + 2
assume cs:code
stack seqment
db 16 dup (0)
stack ends
code segment
mov ax, 4c00h
int 21h
start: mov ax, stack
mov ss, ax
mov sp, 16
mov ax, 0
push ax ;ax入棧
mov bx, 0
ret ;ret指令執行后,(IP)=0,CS:IP指向代碼段的第一條指令。可以push cs push ax retf
code ends
end start
2、call 指令
call指令經常跟ret指令配合使用,因此CPU執行call指令,進行兩步操作:
(1)將當前的 IP 或 CS和IP 壓入棧中;
(2)轉移(jmp)。
call指令不能實現短轉移,除此之外,call指令實現轉移的方法和 jmp 指令的原理相同。
call 標號(近轉移)
CPU執行此種格式的call指令時,相當于進行 push IP jmp near ptr 標號
call far ptr 標號(段間轉移)
CPU執行此種格式的call指令時,相當于進行:push CS,push IP jmp far ptr 標號
call 16位寄存器
CPU執行此種格式的call指令時,相當于進行: push IP jmp 16位寄存器
call word ptr 內存單元地址
CPU執行此種格式的call指令時,相當于進行:push IP jmp word ptr 內存單元地址
mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
call word ptr ds:[0]
;執行后,(IP)=0123H,(sp)=0EH
call dword ptr 內存單元地址
CPU執行此種格式的call指令時,相當于進行:push CS push IP jmp dword ptr 內存單元地址
mov sp, 10h
mov ax, 0123h
mov ds:[0], ax
mov word ptr ds:[2], 0
call dword ptr ds:[0]
;執行后,(CS)=0,(IP)=0123H,(sp)=0CH
3、call 和 ret 的配合使用
分析下面程序
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s ;(1)CPU指令緩沖器存放call指令,IP指向下一條指令(mov bx, ax),執行call指令,IP入棧,jmp
mov bx,ax ;(4)IP重新指向這里 bx = 8
mov ax,4c00h
int 21h
s: add ax,ax
loop s;(2)循環3次ax = 8
ret;(3)return : pop IP
code ends
end start
call 與 ret 指令共同支持了匯編語言編程中的模塊化設計
編寫子程序
十、標志寄存器
1、標志寄存器
CPU內部的寄存器中,有一種特殊的寄存器(對于不同的處理機,個數和結構都可能不同)具有以下3種作用。
(1)用來存儲相關指令的某些執行結果;
(2)用來為CPU執行相關指令提供行為依據;
(3)用來控制CPU的相關工作方式。
這種特殊的寄存器在8086CPU中,被稱為標志寄存器(flag)。
8086CPU的標志寄存器有16位,其中存儲的信息通常被稱為程序狀態字(PSW-Program Status Word)
flag寄存器是按位起作用的,它的每一位都有專門的含義,記錄特定的信息。
在8086CPU的指令集中,有的指令的執行是影響標志寄存器的,比如,add、sub、mul、div、inc、or、and等,它們大都是運算指令(進行邏輯或算術運算);有的指令的執行對標志寄存器沒有影響,比如,mov、push、pop等,它們大都是傳送指令
1、零標志位 (ZF)
零標志位(Zero Flag)。它記錄相關指令執行后,其結果是否為0。
如果結果為0,那么zf = 1(表示結果是0);如果結果不為0,那么zf = 0。
mov ax, 1
sub ax, 1 ;執行后,結果為0,則zf = 1
mov ax, 2
sub ax, 1 ;執行后,結果不為0,則zf = 0
2、奇偶標志位 (PF)
奇偶標志位(Parity Flag)。它記錄相關指令執行后,其結果的所有bit位中1的個數是否為偶數。
如果1的個數為偶數,pf = 1,如果為奇數,那么pf = 0。
mov al, 1
add al, 10 ;執行后,結果為00001011B,其中有3(奇數)個1,則pf = 0;mov al, 1
or al, 2 ;執行后,結果為00000011B,其中有2(偶數)個1,則pf = 1;
3、符號標志位(SF)
符號標志位(Symbol Flag)。它記錄相關指令執行后,其結果是否為負。
如果結果為負,sf = 1;如果非負,sf = 0。
計算機中通常用補碼來表示有符號數據。計算機中的一個數據可以看作是有符號數,也可以看成是無符號數。
00000001B,可以看作為無符號數1,或有符號數+1;
10000001B,可以看作為無符號數129,也可以看作有符號數-127。
對于同一個二進制數據,計算機可以將它當作無符號數據來運算,也可以當作有符號數據來運算
CPU在執行add等指令的時候,就包含了兩種含義:可以將add指令進行的運算當作無符號數的運算,也可以將add指令進行的運算當作有符號數的運算
SF標志,就是CPU對有符號數運算結果的一種記錄,它記錄數據的正負。在我們將數據當作有符號數來運算的時候,可以通過它來得知結果的正負。如果我們將數據當作無符號數來運算,SF的值則沒有意義,雖然相關的指令影響了它的值
mov al, 10000001B
add al, 1 ;執行后,結果為10000010B,sf = 1,表示:如果指令進行的是有符號數運算,那么結果為負;
1
2
mov al, 10000001B
add al, 01111111B ;執行后,結果為0,sf = 0,表示:如果指令進行的是有符號數運算,那么結果為非負
3、進位標志位(CF)
進位標志位(Carry Flag)。一般情況下,在進行無符號數運算的時候,它記錄了運算結果的最高有效位向更高位的進位值,或從更高位的借位值
97H - 98H 產生借位CF = 1 ==》 (al) = 197H - 98H = FFH
4、溢出標志位(OF)
溢出標志位(Overflow Flag)。一般情況下,OF記錄了有符號數運算的結果是否發生了溢出。
如果發生溢出,OF = 1;如果沒有,OF = 0。
CF和OF的區別:CF是對無符號數運算有意義的標志位,而OF是對有符號數運算有意義的標志位
CPU在執行add等指令的時候,就包含了兩種含義:無符號數運算和有符號數運算。
- 對于無符號數運算,CPU用CF位來記錄是否產生了進位;
- 對于有符號數運算,CPU用OF位來記錄是否產生了溢出,當然,還要用SF位來記錄結果的符號。
mov al, 98
add al, 99 ;執行后將產生溢出。因為進行的"有符號數"運算是:(al)=(al)+ 99 = 98 + 99=197 = C5H 為-59的補碼
;而結果197超出了機器所能表示的8位有符號數的范圍:-128-127。
;add 指令執行后:無符號運算沒有進位CF=0,有符號運算溢出OF=1
;當取出的數據C5H按無符號解析C5H = 197, 當按有符號解析通過SP得知數據為負,即C5H為-59補碼存儲,
mov al,0F0H ;F0H,為有符號數-16的補碼 -Not(F0 - 1)
add al,088H ;88H,為有符號數-120的補碼 -Not(88- 1)
;執行后,將產生溢出。因為add al, 088H進行的有符號數運算結果是:(al)= -136
;而結果-136超出了機器所能表示的8位有符號數的范圍:-128-127。
;add 指令執行后:無符號運算有進位CF=1,有符號運算溢出OF=1
2、adc指令和sbb指令
adc是帶進位加法指令,它利用了CF位上記錄的進位值。
指令格式:adc 操作對象1, 操作對象2
功能:操作對象1 = 操作對象1 + 操作對象2 + CF
mov ax, 2
mov bx, 1
sub bx, ax ;無符號運算借位CF=1,有符號運算OF = 0
adc ax, 1 ;執行后,(ax)= 4。adc執行時,相當于計算:(ax)+1+CF = 2+1+1 = 4。
;計算1EF000H+201000H,結果放在ax(高16位)和bx(低16位)中。
;將計算分兩步進行,先將低16位相加,然后將高16位和進位值相加。
mov ax, 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H
sbb指令
sbb是帶借位減法指令,它利用了CF位上記錄的借位值。
指令格式:sbb 操作對象1, 操作對象2
功能:操作對象1 = 操作對象1 - 操作對象2 - CF
;計算 003E1000H - 00202000H,結果放在ax,bx中,程序如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
3、cmp指令
cmp是比較指令,cmp的功能相當于減法指令,只是不保存結果。cmp指令執行后,將對標志寄存器產生影響。
其他相關指令通過識別這些被影響的標志寄存器位來得知比較結果。
cmp指令格式:cmp 操作對象1,操作對象2
例如:
指令cmp ax, ax,做(ax)-(ax)的運算,結果為0,但并不在ax中保存,僅影響flag的相關各位。
指令執行后:zf=1,pf=1,sf=0,cf=0,of=0。
CPU在執行cmp指令的時候,也包含兩種含義:進行無符號數運算和進行有符號數運算。
cmp ax, bx | 無符號比較時 |
---|---|
(ax) = (bx) | zf = 1 |
(ax) ≠ (bx) | zf = 0 |
(ax) < (bx) | cf = 1 |
(ax) ≥ (bx) | cf = 0 |
(ax) > (bx) | cf = 0 且 zf = 0 |
(ax) ≤ (bx) | cf = 1 且 zf = 1 |
上面的表格可以正推也可以逆推
如果用cmp來進行有符號數比較時
SF只能記錄實際結果的正負,發生溢出的時候,實際結果的正負不能說明邏輯上真正結果的正負。
但是邏輯上的結果的正負,才是cmp指令所求的真正結果,所以我們在考察SF的同時考察OF,就可以得知邏輯上真正結果的正負,同時就知道比較的結果。
mov ah, 08AH ; -Not(8A-1) = -118 即當成有符號數時為-118
mov bh, 070H ; 有符號數時最高位為0為正數, 70H = 112
cmp ah, bh ;(ah)-(bh)實際得到的結果是1AH
; 在邏輯上,運算所應該得到的結果是:(-118)- 112 = -230
; sf記錄實際結果的正負,所以sf=0
cmp ah, bh
(1)如果sf=1,而of=0 。 of=0說明沒有溢出,邏輯上真正結果的正負=實際結果的正負; sf=1,實際結果為負,所以邏輯上真正的結果為負,所以(ah)<(bh)(2)如果sf=1,而of=1: of=1,說明有溢出,邏輯上真正結果的正負≠實際結果的正負; sf=1,實際結果為負。
實際結果為負,而又有溢出,這說明是由于溢出導致了實際結果為負,,如果因為溢出導致了實際結果為負,那么邏輯上真正的結果必然為正。 這樣,sf=1,of=1,說明了(ah)>(bh)。(3)如果sf=0,而of=1。of=1,說明有溢出,邏輯上真正結果的正負≠實際結果的正負;sf=0,實際結果非負。而of=1說明有溢出,則結果非0,所以,實際結果為正。
實際結果為正,而又有溢出,這說明是由于溢出導致了實際結果非負,如果因為溢出導致了實際結果為正,那么邏輯上真正的結果必然為負。這樣,sf=0,of=1,說明了(ah)<(bh)。
(4)如果sf=0,而of=0
of=0,說明沒有溢出,邏輯上真正結果的正負=實際結果的正負;sf=0,實際結果非負,所以邏輯上真正的結果非負,所以(ah)≥(bh)。
4、檢測比較結果的條件轉移指令
可以根據某種條件,決定是否修改IP的指令
jcxz它可以檢測cx中的數值,如果(cx)=0,就修改IP,否則什么也不做。
所有條件轉移指令的轉移位移都是[-128,127]。
多數條件轉移指令都檢測標志寄存器的相關標志位,根據檢測的結果來決定是否修改IP
這些條件轉移指令通常都和cmp相配合使用,它們所檢測的標志位,都是cmp指令進行無符號數比較的時記錄比較結果的標志位
根據無符號數的比較結果進行轉移的條件轉移指令(它們檢測zf、cf的值)
指令 | 含義 | 檢測的相關標志位 |
---|---|---|
je | 等于則轉移 | zf = 1 |
jne | 不等于則轉移 | zf = 0 |
jb | 低于則轉移 | cf = 1 |
jnb | 不低于則轉移 | cf = 0 |
ja | 高于則轉移 | cf = 0 且 zf = 0 |
jna | 不高于則轉移 | cf = 1 且 zf = 1 |
j:jump,e:equal,b:below,a:above,n:not
;編程,統計data段中數值為8的字節的個數,用ax保存統計結果。
mov ax, data
mov ds, ax
mov bx, 0 ;ds:bx指向第一個字節
mov ax, 0 ;初始化累加器mov cx,8
s:
cmp byte ptr[bx], 8 ;和8進行比較
jne next ;如果不相等轉到next,繼續循環
inc ax ;如果相等就將計數值加1
next:
inc bx
loop s ;程序執行后:(ax)=3
5、DF標志和串傳送指令
方向標志位。在串處理指令中,控制每次操作后si、di的增減。
- df = 0每次操作后si、di遞增;
- df = 1每次操作后si、di遞減。
格式:movsb
功能:將ds:si指向的內存單元中的字節送入es:di中,然后根據標志寄存器df位的值,將si和di遞增或遞減
格式:movsw
功能:將ds:si指向的內存字單元中的字送入es:di中,然后根據標志寄存器df位的值,將si和di遞增2或遞減2。
格式:rep movsb
movsb和movsw進行的是串傳送操作中的一個步驟,一般來說,movsb和movsw都和rep配合使用,
功能:rep的作用是根據cx的值,重復執行后面的串傳送指令
8086CPU提供下面兩條指令對df位進行設置。
- cld指令:將標志寄存器的df位置0
- std指令:將標志寄存器的df位置1
;將data段中的第一個字符串復制到它后面的空間中。
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
mov ax, data
mov ds, ax
mov si, 0 ;ds:si 指向data:0
mov es, ax
mov di, 16 ;es:di指向data:0010
mov cx, 16 ;(cx)=16,rep循環16次
c1d ;設置df=0,正向傳送
rep movsb
6、pushf和popf
pushf的功能是將標志寄存器的值壓棧,而popf是從棧中彈出數據,送入標志寄存器中
pushf和popf,為直接訪問標志寄存器提供了一種方法。
十一、內中斷
1、內中斷的產生
任何一個通用的CPU,都具備一種能力,可以在執行完當前正在執行的指令之后,檢測到從CPU外部發送過來的或內部產生的一種特殊信息,并且可以立即對所接收到的信息進行處理。這種特殊的信息,我們可以稱其為:中斷信息。中斷的意思是指,CPU不再接著(剛執行完的指令)向下執行,而是轉去處理這個特殊信息。
中斷信息可以來自CPU的內部和外部(內中斷,外中斷)
內中斷:當CPU的內部有需要處理的事情發生的時候,將產生中斷信息,引發中斷過程。這種中斷信息來自CPU的內部
8086CPU的內中斷(下面四種情況將產生中斷信息)
- 除法錯誤,比如,執行div指令產生的除法溢出;
- 單步執行;
- 執行into指令;
- 執行int指令。
中斷信息中包含中斷類型碼,中斷類型碼為一個字節型數據,可以表示256種中斷信息的來源(中斷源)
上述的4種中斷源,在8086CPU中的中斷類型碼如下。
- 除法錯誤:0
- 單步執行:1
- 執行into指令:4
- 執行int指令,該指令的格式為int n,指令中的n為字節型立即數,是提供給CPU的中斷類型碼。
2、中斷處理程序、中斷向量表、中斷過程
中斷處理程序
用來處理中斷信息的程序被稱為中斷處理程序。
根據CPU的設計,中斷類型碼的作用就是用來定位中斷處理程序。比如CPU根據中斷類型碼4,就可以找到4號中斷的處理程序
中斷向量表
中斷向量,就是中斷處理程序的入口地址。中斷向量表,就是中斷處理程序入口地址的列表
CPU用8位的中斷類型碼通過中斷向量表找到相應的中斷處理程序的入口地址
中斷過程
中斷過程的主要任務就是用中斷類型碼在中斷向量表中找到中斷處理程序的入口地址,設置CS和IP
簡要描述如下
- 取得中斷類型碼N;
- pushf
- TF=0,IF=0 (為什么這樣參考單步中斷)
- push CS , push IP
- (IP)=(N * 4),(CS)=(N * 4 + 2)
硬件在完成中斷過程后,CS:IP將指向中斷處理程序的入口,CPU開始執行中斷處理程序。
3、iret指令
CPU隨時都可能執行中斷處理程序,中斷處理程序必須一直存儲在內存某段空間之中
而中斷處理程序的入口地址,即中斷向量,必須存儲在對應的中斷向量表表項中。
中斷處理程序的常規編寫步驟:
- 保存用到的寄存器;
- 處理中斷;
- 恢復用到的寄存器;
- 用iret指令返回。
iret 指令描述為:pop IP pop CS popf
iret指令執行后,CPU回到執行中斷處理程序前的執行點繼續執行程序
4、除法錯誤中斷的處理
mov ax, 1000h
mov bh, 1
div bh ;除法溢出錯誤
1、當CPU執行div bh時,發生了除法溢出錯誤,產生0號中斷信息,從而引發中斷過程,
2、CPU執行0號中斷處理程序
3、系統中的0號中斷處理程序的功能:顯示提示信息“Divide overflow”后,返回到操作系統中。
編程實驗
編程:編寫0號中斷處理程序do0,當發生除法溢出時,在屏幕中間顯示“overflow!”,返回DOS。
1、0000:0200至0000:02FF的256個字節的空間所對應的中斷向量表項都是空的,可以將中斷處理程序do0傳送到內存0000:0200處。
2、中斷處理程序do0放到0000:0200,再將其地址登記在中斷向量表對應表項
- 0號表項的地址0:0。0:0字單元存放偏移地址,0:2字單元存放段地址
- 將do0的段地址0存放在0000:0002字單元中,將偏移地址200H存放在0000:0000字單元
assume cs:code
code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0 ;設置ds:si指向源地址
mov ax, 0
mov es, ax
mov di, 200h ;設置es:di指向目的地址0000:0200
mov cx, offset do0end - offset do0 ;設置cx為傳輸長度 編譯時給出do0部分代碼長度
cld ;設置傳輸方向為正
rep movsb ;將do0的代碼送入0:200處
mov ax, 0 ;設置中斷向量表
mov es, ax
mov word ptr es:[0*4], 200h
mov word ptr es:[0*4+2], 0
mov ax,4c00h
int 21h
;do0程序的主要任務是顯示字符串
do0: jmp short do0 start
db "overflow!"
do0start:
mov ax, cs
mov ds, ax
mov si, 202h ;設置ds:si指向字符串
mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2 ;設置es:di指向顯存空間的中間位置
mov cx, 9 ;設置cx為字符串長度
s: mov al, [si]
mov es:[di], al
inc si
add di, 1
mov al, 02h ;設置顏色
mov es:[di], al
add di, 1
loop s
mov ax, 4c00h
int 21h
do0end: nop
code ends
end start
5、單步中斷
CPU在執行完一條指令之后,如果檢測到標志寄存器的TF位為1,則產生單步中斷,引發中斷過程。單步中斷的中斷類型碼為1
Debug是如何利用CPU所提供的單步中斷的功能進行調試?如使用t命令查看寄存器狀態
Debug提供了單步中斷的中斷處理程序,功能為顯示所有寄存器中的內容后等待輸入命令
在使用t命令執行指令時,Debug將TF設置為1,在CPU執行完這條指令后就引發單步中斷,執行單步中斷的中斷處理程序,所有寄存器中的內容被顯示在屏幕上,并且等待輸入命令。
在進入中斷處理程序之前,設置TF=0。從而避免CPU在執行中斷處理程序的時候發生單步中斷
6、int指令
int指令的格式為:int n ,n為中斷類型碼,它的功能是引發中斷過程。
CPU執行int n指令,相當于引發一個n號中斷的中斷過程
在程序中使用int指令調用任何一個中斷的中斷處理程序(中斷例程)
編寫供應用程序調用的中斷例程
實驗1
;求2 * 3456^2
assume cs:code
code segment
start:
mov ax, 3456 ;(ax)=3456
int 7ch ; 調用中斷7ch的中斷例程,計算ax中的數據的平方
add ax, ax
adc dx, dx ;存放結果,將結果乘以2
mov ax,4c00h
int 21h
code ends
end start
;編程:安裝中斷7ch的中斷例程
;功能:求一word型數據的平方。
;參數:(ax) = 要計算的數據。
;返回值:dx、ax中存放結果的高16位和低16位。
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset sqr ;設置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200h ;設置es:di指向目的地址
mov cx,offset sqrend - offset sqr ;設置cx為傳輸長度
cld ;設置傳輸方向為正
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
sqr:
mul ax
iret ;CPU執行int 7ch指令進入中斷例程之前,標志寄存器、當前的CS和IP被壓入棧
;在執行完中斷例程后,應該用iret 指令恢復int 7ch執行前的標志寄存器和CS、IP的
sqrend: nop
code ends
end start
實驗2
;功能:將一個全是字母,以0結尾的字符串,轉化為大寫。
;參數:ds:si指向字符串的首地址。
;應用舉例:將data段中的字符串轉化為大寫。
assume cs:code
data segment
db 'conversation',0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
int 7ch
mov ax,4c00h
int 21h
code ends
end start
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset capital
mov ax,0
mov es,ax
mov di,200h
mov cx,offset capitalend - offset capital
cld
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
capital:
push cx
push si
change:
mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok:
pop si
pop cx
iret
capitalend:nop
code ends
end start
7、BIOS和DOS所提供的中斷例程
在系統板的ROM中存放著一套程序,稱為BIOS(基本輸入輸出系統)
BIOS中主要包含以下幾部分內容
- 硬件系統的檢測和初始化程序;
- 外部中斷和內部中斷的中斷例程;
- 用于對硬件設備進行I/O操作的中斷例程;
- 其他和硬件系統相關的中斷例程。
程序員在編程的時候,可以用int 指令直接調用BIOS和DOS系統提供的中斷例程,來完成某些工作。
和硬件設備相關的DOS中斷例程中,一般都調用了BIOS的中斷例程。
BIOS和DOS中斷例程的安裝過程
BIOS和DOS提供的中斷例程是如何安裝到內存中的呢?
1、開機后,CPU一加電,初始化(CS)= 0FFFFH,(IP)= 0,自動從FFFF:0單元開始執行程序。FFFF:0處有一條轉跳指令,CPU執行該指令后,轉去執行BIOS中的硬件系統檢測和初始化程序。
2、初始化程序將建立BIOS所支持的中斷向量,即將BIOS提供的中斷例程的入口地址登記在中斷向量表中。
注意,對于BIOS所提供的中斷例程,只需將入口地址登記在中斷向量表中即可,因為它們是固化到ROM中的程序,一直在內存中存在。
3、硬件系統檢測和初始化完成后,調用int 19h進行操作系統的引導。從此將計算機交由操作系統控制。
4、DOS啟動后,除完成其他工作外,還將它所提供的中斷例程裝入內存,并建立相應的中斷向量。
BIOS中斷例程應用
一般來說,一個供程序員調用的中斷例程中往往包括多個子程序,中斷例程內部用傳遞進來的參數來決定執行哪一個子程序。
BIOS和DOS提供的中斷例程,都用ah來傳遞內部子程序的編號。
編程:在屏幕的5行12列顯示3個紅底高亮閃爍綠色的“al。
assume cs:code
code segment
;int 10h中斷例程的"設置光標位置"功能
mov ah, 2;設置光標調用第10h號中斷例程的2號子程序,功能為設置光標位置(可以提供光標所在的行號、列號和頁號作為參數)
;設置光標到第0頁,第5行,第12列
mov bh, 0;第0頁
mov dh, 5;dh中放行號
mov dl, 12;dl中放列號
int 10h
;int10h中斷例程的"在光標位置顯示字符"功能。
mov ah,9 ;調用第10h號中斷例程的9號子程序,功能為在光標位置顯示字符
;提供要顯示的字符、顏色屬性、頁號、字符重復個數作為參數
mov al,'a' ;字符
mov b1,11001010b ;顏色屬性
mov bh,0 ;第0頁
mov cx,3 ;字符重復個數
int 10h
code ends
end
bh中頁號的含義:內存地址空間中,B8000H~BFFFFH共32kB的空間,為80*25彩色字符模式的顯示緩沖區。
一屏的內容在顯示緩沖區中共占4000個字節。顯示緩沖區分為8頁,每頁4KB(約4000B),顯示器可以顯示任意一頁的內容。一般情況下,顯示第0頁的內容。也就是說,通常情況下,B8000H~B8F9FH中的4000個字節的內容將出現在顯示器上。
DOS中斷例程應用
int 21h中斷例程是DOS提供的中斷例程,4ch號功能,即程序返回功能
mov ah, 4ch ;調用第21h號中斷例程的4ch號子程序,功能為程序返回,可以提供返回值作為參數
mov al, 0 ;返回值
int 21h
編程:在屏幕的5行12列顯示字符串“Welcome to masm!”。
assume cs:code
data segment
db 'Welcome to masm', '$' ;“$”本身并不顯示,只起到邊界的作用
data ends
code segment
start: mov ah, 2 ;10號中斷設置光標位置功能
mov bh, 0 ;第0頁
mov dh, 5;dh中放行號
mov dl, 12 ;dl中放列號
int 10h
mov ax, data
mov ds, ax
mov dx, 0 ;ds:dx指向字符串的首地址data:0 (參數)
mov ah, 9 ;調用第21h號中斷例程的9號子程序,功能為在光標位置顯示字符串,可以提供要顯示字符串的地址作為參數
int 21h
mov ax, 4c00h ;21號中斷程序返回功能
int 21h
code ends
end start
十二、端口
在PC機系統中,和CPU通過總線相連的芯片除各種存儲器外,還有以下3種芯片。
- 各種接口卡(比如,網卡、顯卡)上的接口芯片,它們控制接口卡進行工作;
- 主板上的接口芯片,CPU通過它們對部分外設進行訪問;
- 其他芯片,用來存儲相關的系統信息,或進行相關的輸入輸出處理。
在這些芯片中,都有一組可以由CPU讀寫的寄存器。這些寄存器,它們在物理上可能處于不同的芯片中,
但是它們在以下兩點上相同。
- 都和CPU的總線相連,這種連接是通過它們所在的芯片進行的;
- CPU對它們進行讀或寫的時候都通過控制線向它們所在的芯片發出端口讀寫命令。
從CPU的角度,將這些寄存器都當作端口,對它們進行統一編址,從而建立了一個統一的端口地址空間。
每一個端口在地址空間中都有一個地址。在訪問端口的時候,CPU通過端口地址來定位端口。因為端口所在的芯片和CPU通過總線相連,
CPU可以直接讀寫以下3個地方的數據。
CPU內部的寄存器;
內存單元;
端口。
1、端口的讀寫
端口地址和內存地址一樣,通過地址總線來傳送。在PC系統中,CPU最多可以定位64KB個不同的端口。則端口地址的范圍為0-65535。
端口的讀寫指令只有兩條:in和out,分別用于從端口讀取數據和往端口寫入數據。
在in和out指令中,只能使用ax或al來存放從端口中讀入的數據或要發送到端口中的數據。
;對0~255以內的端口進行讀寫時:
in al, 20h ;從20h端口讀入一個字節
out 20h, al ;往20h端口寫入一個字節
;對256~65535的端口進行讀寫時,端口號放在dx中:
mov dx, 3f8h ;將端口號3f8h送入dx
in al, dx ;從3f8h端口讀入一個字節
out dx, al ;向3f8h端口寫入一個字節
2、CMOS RAM芯片
PC機中,有一個CMOSRAM芯片,一般簡稱為CMOS。此芯片的特征如下
1、包含一個實時鐘和一個有128個存儲單元的RAM存儲器
2、該芯片靠電池供電。關機后內部的實時鐘正常工作,RAM中的信息不丟失
3、128個字節的RAM中,內部實時鐘占用0~0dh單元來保存時間信息,其余大部分單元用于保存系統配置信息,供系統啟動時BIOS程序讀取。BIOS也提供了相關的程序,使我們可以在開機的時候配置CMOSRAM中的系統信息。
該芯片內部有兩個端口,端口地址為70h和71h。CPU通過這兩個端口來讀寫CMOS RAM
4、70h為地址端口,存放要訪問的CMOSRAM單元的地址;71h為數據端口,存放從選定的CMOSRAM單元中讀取的數據,或要寫入到其中的數據。
可見,CPU對CMOS RAM的讀寫分兩步進行,比如,讀CMOS RAM的2號單元:
①將2送入端口70h;
②從端口71h讀出2號單元的內容。
CMOSRAM中存儲的時間信息
在CMOS RAM中,存放著當前的時間:年、月、日、時、分、秒。長度都為1個字節,
存放單元為:
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
年 | 月 | 日 | 時 | 分 | 秒 |
BCD碼是以4位二進制數表示十進制數碼的編碼方法 4 == 0100B
一個字節可表示兩個BCD碼。則CMOS RAM存儲時間信息的單元中,存儲了用兩個BCD碼表示的兩位十進制數,高4位的BCD碼表示十位,低4位的BCD碼表示個位。比如,00010100b表示14。
;編程,在屏幕中間顯示當前的月份。
assume cs:code
code segment
start: mov al,8 ;從CMOS RAM的8號單元讀出當前月份的BCD碼。
out 70h,al
in al, 71h ;從數據端口71h中取得指定單元中的數據:
mov ah, al ;al中為從CMOSRAM的8號單元中讀出的數據
mov cl, 4
shr ah, cl ;ah中為月份的十位數碼值,左移四位空出四位
and al, 00001111b ;al中為月份的個位數碼值
add ah, 30h ;BCD碼值+30h=十進制數對應的ASCII
add al, 30h
mov bx, 0b800h
mov es, bx
mov byte ptr es:[160*12+40*2], ah ;顯示月份的十位數碼
mov byte ptr es:[160*12+40*2+2], al ;接著顯示月份的個位數碼
mov ax,4c00h
int 21h
code ends
end start
3、shl和shr指令
shl和shr是邏輯移位指令
shl是邏輯左移指令,它的功能為:
- 將一個寄存器或內存單元中的數據向左移位;
- 將最后移出的一位寫入CF中;
- 最低位用0補充。
shr是邏輯右移指令,同理
mov al, 01001000b
shl al, 1 ;將a1中的數據左移一位執行后(al)=10010000b,CF=0。
mov al, 01010001b
mov cl, 3 ;如果移動位數大于1時,必須將移動位數放在cl中
shl al, c1
mov al, 10000001b
shr al, 1 ;將al中的數據右移一位執行后(al)=01000000b,CF=1。
將X邏輯左移一位,相當于執行X=X*2。
將X邏輯右移一位,相當于執行X=X/2
十三、外中斷
1、外中斷
PU在計算機系統中,除了能夠執行指令,進行運算以外,還應該能夠對外部設備進行控制,接收它們的輸入,向它們進行輸出(I/O能力)
PC系統的接口卡和主板上,裝有各種接口芯片。這些外設接口芯片的內部有若干寄存器,CPU將這些寄存器當作端口來訪問
外設的輸入不直接送入內存和CPU,而是送入相關的接口芯片的端口中;
CPU向外設的輸出也不是直接送入外設,而是先送入端口中,再由相關的芯片送到外設。
CPU還可以向外設輸出控制命令,而這些控制命令也是先送到相關芯片的端口中,然后再由相關的芯片根據命令對外設實施控制。
即:CPU通過端口和外部設備進行聯系
當CPU外部有需要處理的事情發生的時候,比如說,外設的輸入到達,相關芯片將向CPU發出相應的中斷信息。CPU在執行完當前指令后,可以檢測到發送過來的中斷信息,引發中斷過程,處理外設的輸入。
PC系統中,外中斷源有兩類
1、可屏蔽中斷
可屏蔽中斷是CPU可以不響應的外中斷。CPU是否響應可屏蔽中斷,要看標志寄存器的IF位的設置。
當CPU檢測到可屏蔽中斷信息時,如果IF=1,則CPU在執行完當前指令后響應中斷,引發中斷過程;如果IF=0,則不響應可屏蔽中斷。
可屏蔽中斷信息來自于CPU外部,中斷類型碼是通過數據總線送入CPU的;而內中斷的中斷類型碼是在CPU內部產生的。
中斷過程中將IF置0的原因就是,在進入中斷處理程序后,禁止其他的可屏蔽中斷。
如果在中斷處理程序中需要處理可屏蔽中斷,可以用指令將IF置1。
8086CPU提供的設置IF的指令:sti,設置IF=1;cli,設置IF=0。
2、不可屏蔽中斷
不可屏蔽中斷是CPU必須響應的外中斷。當CPU檢測到不可屏蔽中斷信息時,則在執行完當前指令后,立即響應,引發中斷過程。
對于8086CPU,不可屏蔽中斷的中斷類型碼固定為2,所以中斷過程中,不需要取中斷類型碼。則不可屏蔽中斷的中斷過程為:①標志寄存器入棧,IF=0,TF=0;②CS、IP入棧;③(IP)=(8),(CS)=(0AH)。
幾乎所有由外設引發的外中斷,都是可屏蔽中斷。當外設有需要處理的事件(比如說鍵盤輸入)發生時,相關芯片向CPU發出可屏蔽中斷信息。不可屏蔽中斷是在系統中有必須處理的緊急情況發生時用來通知CPU的中斷信息。
2、PC機鍵盤的處理過程
鍵盤中有一個芯片對鍵盤上的每一個鍵的開關狀態進行掃描。按下一個鍵時,開關接通,該芯片就產生一個掃描碼,掃描碼說明了按下的鍵在鍵盤上的位置。掃描碼被送入主板上的相關接口芯片的寄存器中,該寄存器的端口地址為60h。松開按下的鍵時,也產生一個掃描碼,掃描碼說明了松開的鍵在鍵盤上的位置。松開按鍵時產生的掃描碼也被送入60h端口中。
一般將按下一個鍵時產生的掃描碼稱為通碼,松開一個鍵產生的掃描碼稱為斷碼。
掃描碼長度為一個字節,通碼的第7位為0,斷碼的第7位為1
即:斷碼 = 通碼 + 80h。比如,g鍵的通碼為22h,斷碼為a2h
鍵盤的輸入到達60h端口時,相關的芯片就會向CPU發出中斷類型碼為9的可屏蔽中斷信息。CPU檢測到該中斷信息后,如果IF=1,則響應中斷,引發中斷過程,轉去執行int 9中斷例程。
BIOS提供了int9中斷例程,用來進行基本的鍵盤輸入處理,主要的工作如下:
(1)讀出60h端口中的掃描碼;
(2)如果是字符鍵的掃描碼,將該掃描碼和它所對應的字符碼(即ASCII碼)送入內存中的BIOS鍵盤緩沖區; 如果是控制鍵(比如Ctrl)和切換鍵(比如CapsLock)的掃描碼,則將其轉變為狀態字節寫入內存中存儲狀態字節的單元;
(3)對鍵盤系統進行相關的控制,比如說,向相關芯片發出應答信息。
BIOS鍵盤緩沖區可以存儲15個鍵盤輸入,一個鍵盤輸入用一個字單元存放,高位字節存放掃描碼,低位字節存放字符碼。
0040:17單元存儲鍵盤狀態字節,該字節記錄了控制鍵和切換鍵的狀態。鍵盤狀態字節各位記錄的信息如下。
0 | 右shift狀態 | 置1表示按下右shift鍵 |
---|---|---|
1 | 左shift狀態 | 置1表示按下左shift鍵 |
2 | Ctrl狀態 | 置1表示按下Ctrl鍵 |
3 | Alt狀態 | 置1表示按下Alt鍵 |
4 | ScrollLock狀態 | 置1表示Scroll指示燈亮 |
5 | NumLock狀態 | 置1表示小鍵盤輸入的是數字 |
6 | CapsLock狀態 | 置1表示輸入大寫字母 |
7 | Insert狀態 | 置1表示處于刪除態 |
編寫int 9中斷例程
;編程:在屏幕中間依次顯示“a”~“z”,并可以讓人看清。在顯示的過程中,按下'Esc'鍵后,改變顯示的顏色。
;完整功能代碼:
assume cs:code
stack segment
db 128 dup (0)
stack ends
data segment
dw 0,0
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,128
mov ax,data
mov ds,ax
mov ax,0
mov es,ax
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2] ;將原來的int 9中斷例程的入口地址保存在ds:0、ds:2單元中
mov word ptr es:[9*4], offset int9
mov es:[9*4+2], cs ;在中斷向量表中設置新的int 9中斷例程的入口地址
;顯示字符串
mov ax, 0b800h
mov es, ax
mov ah, 'a'
s:
mov es:[160*12+40*2], ah
call delay
inc ah
cmp ah, 'z'
jna s
mov ax,0
mov es,ax
push ds:[0]
pop es:[9*4]
push ds;[2]
pop es;[9*4+2] ;將中斷向量表中int 9中斷例程的入口恢復為原來的地址
mov ax,4c00h
int 21h
;將循環延時的程序段寫為一個子程序
delay:
push ax
push dx
mov dx, 2000h ;用兩個16位寄存器來存放32位的循環次數
mov ax, 0
s1:
sub ax, 1
sbb dx, 0
cmp ax, 0
jne s1
cmp dx, 0
jne s1
pop dx
pop ax
ret
;------以下為新的int 9中斷例程--------------------
int9:
push ax
push bx
push es
in al, 60h;從端口60h讀出鍵盤的輸入
pushf ;標志寄存器入棧
pushf
pop bx
and bh,11111100b
push bx
popf ;TF=0,IF=0
call dword ptr ds:[0] ;對int指令進行模擬,調用原來的int 9中斷例程
cmp al,1
jne int9ret
mov ax,0b800h
mov es,ax
inc byte ptr es:[160*12+40*2+1] ;屬性增加1,改變顏色
int9ret:
pop es
pop bx
pop ax
iret
code ends
end start
CPU對外設輸入的通常處理方法
(1)外設的輸入送入端口;
(2)向CPU發出外中斷(可屏蔽中斷)信息;
(3)CPU檢測到可屏蔽中斷信息,如果IF=1,CPU在執行完當前指令后響應中斷,執行相應的中斷例程;
(4)可在中斷例程中實現對外設輸入的處理。
端口和中斷機制,是CPU進行I/O的基礎。
十四、直接定址表
assume cs:code
code segment
a : db 1,2,3,4,5,6,7,8 ;在后面加有“:”的地址標號,只能在代碼段中使用,不能在其他段中使用。
b : dw 0
start :mov si,offset a
mov bx,offset b
mov cx,8
s : mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
程序中,code、a、b、start、s都是標號。這些標號僅僅表示了內存單元的地址
描述了單位長度的標號
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8 ;標號a、b后面沒有":",因此它們是可以同時描述內存地址和單元長度的標號。
;標號a,描述了地址code:0,和從這個地址開始,以后的內存單元都是字節單元
b dw 0 ;標號b描述了地址code:8,和從這個地址開始,以后的內存單元都是字單元。
start : mov si,0
mov cx,8
s : mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
使用數據標號來描述存儲數據的單元的地址和長度。
assume cs:code,ds:data ;用偽指令assume將標號所在的段和一個段寄存器聯系起來(編譯器需要)
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start: mov ax,data
mov ds,ax ;真正確定ds寄存器
mov si,0
mov cx,8
s: mov al,a[si] ;編譯為:mov al,[si+0] 默認所訪問單元的段地址在ds
mov ah,0
add b,ax ;編譯為:add [8],ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a, b ;等價于c dw offset a, offset b
;數據標號c處存儲的兩個字型數據為標號a、b 的偏移地址
data ends
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b ;等價于c dw offset a, seg a, offset b, seg b
;數據標號c處存儲的兩個雙字型數據為標號a的偏移地址和段地址、標號b 的偏移地址和段地址
data ends
seg操作符,功能為取得某一標號的段地址
建立一張表,表中依次存儲字符“0”~“F”,我們可以通過數值0 ~ 15直接查找到對應的字符
assume cs:code
code segment
start:
mov al,0eh
call showbyte
mov ax,4c00h
int 21h
;子程序:
;用al傳送要顯示的數據
showbyte:
jmp short show
table db '0123456789ABCDEF' ;字符表
show: push bx
push es
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1 ;右移4位,ah中得到高4位的值
and al,00001111b ;al中為低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;用高4位的值作為相對于table的偏移,取得對應的字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov bl,al
mov bh,0
mov al,table[bx] ;用低4位的值作為相對于table的偏移,取得對應的字符
mov es:[160*12+40*2+2],al
pop es
pop bx
ret
code ends
end start
十五、 指令系統總結
我們對8086CPU的指令系統進行一下總結。讀者若要詳細了解8086指令系統中的各個指令的用,可以查看有關的指令手冊。
8086CPU提供以下幾大類指令。
1、數據傳送指令
mov、push、pop、pushf、popf、xchg 等都是數據傳送指令,這些指令實現寄存器和內存、寄器和寄存器之間的單個數據傳送。
2、算術運算指令
add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa等都是算術運算指令,這些指令實現存器和內存中的數據的算數運算。它們的執行結果影響標志寄存器的sf、zf、of、cf、pf、af位。
3、邏輯指令
and、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr等都是邏輯指令。除了not指外,它們的執行結果都影響標志寄存器的相關標志位。
4、轉移指令
可以修改IP,或同時修改CS和IP的指令統稱為轉移指令。轉移指令分為以下幾類。
(1)無條件轉移指令,比如,jmp;
(2)條件轉移指令,比如,jcxz、je、jb、ja、jnb、jna等;
(3)循環指令,比如,loop;
(4)過程,比如,call、ret、retf;
(5)中斷,比如,int、iret。
5、處理機控制指令
對標志寄存器或其他處理機狀態進行設置,cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等都是處理機控制指令。
6、串處理指令
對內存中的批量數據進行處理,movsb、movsw、cmps、scas、lods、stos等。若要使用這些指令方便地進行批量數據的處理,則需要和rep、repe、repne 等前綴指令配合使用。
文中大部分的圖片來自王爽《匯編語言》個別圖片來自劉宏偉·計算機組成原理課件
博主靠這本書入門匯編,只是匆匆看了一遍,很多地方理解片面甚至錯誤,將來發現一定修正