第一章基礎知識html
地址加法器合成物理地址的方法:物理地址=段地址×16+偏移地址程序員
各個寄存器都是16位的,因此還須要經過「加工」進位配夠5位的地址。8086內部爲16位結構,它只能傳送16位的地址,內部偏移地址也爲16位,表現出的尋址能力卻只有2^16次方,也就是64K大小,因此每段大小不超過64k的尋址。編程
1B = 8b 1KB = 1024B 1MB = 1024KB 1GB = 1024MB安全
地址0~7FFFH的32KB空間爲主隨機存儲器的地址空間;架構
地址8000H~9FFFH的8KB空間爲顯存地址空間;oop
地址A000H~FFFFH的24KB空間爲各個ROM的地址空間。學習
第二章寄存器測試
AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。spa
內存並無分段,而是人爲的定義段,段的劃分來自於CPU,因爲8086CPU用「(段地址×16)+偏移地址=物理地址」的方式給出內存單元的物理地址,使得咱們能夠用分段的方式來管理內存。將若干地址連續的內存單元看做一個段,用段地址×16定位段的起始地址(基礎地址),用偏移地址定位段中的內存單元。操作系統
8086CPU有4個段寄存器:
CS、DS、SS、ES
當8086CPU要訪問內存時,由這4個段寄存器提供內存單元的段地址。
CS和IP是8086CPU中最關鍵的寄存器,它們指示了CPU當前要讀取指令的地址。
CS爲代碼段寄存器;
IP爲指令指針寄存器。
jmp 段地址:偏移地址
如:jmp 13BC:0160
功能:用指令中給出的段地址修改CS,偏移地址修改IP。
僅修改IP的內容:
jmp 某一合法寄存器或數字
jmp ax (相似於mov IP,ax)
jmp 300
功能:用寄存器中的值修改IP。
第三章寄存器(內存訪問)
在8086PC中,內存地址由段地址和偏移地址組成。
DS寄存器,一般用來存放要訪問的數據的段地址。
例如:mov指令
mov指令的格式:mov 寄存器名,內存單元地址
「[…]」表示一個內存單元,「[…]」中的數字表示內存單元的偏移地址。
執行指令時,8086CPU自動讀取DS中的數據爲內存單元的數據段地址,而後根據偏移地址讀取數據。
再例如用mov al,[0]完成傳送(mov指令中的[]說明操做對象是一個內存單元,[]中的0說明這個內存單元的偏移地址是0,它的段地址默認放在ds中)
ss,表示棧基址,sp表示當前棧指針位置。
使用push入棧,pop進行出棧
棧空時:sp爲棧長度+1
棧滿時:sp=ss
第四章第一個程序
編譯:使用masm命令進行編譯,默認會生成一個obj文件
連接:使用Link命令進行連接,將編譯生成的obj文件連接成exe文件
簡化的編譯連接在每一個命令的後面加上分號「;」,也可以使用ML命令直接生成exe文件
第五章 [bx]和loop指令
Bx通常用於數據地址的偏移量
Cx通常和loop聯合使用,到0時結束loop的循環
安全退出程序必須使用以下兩句
mov ax,4c00h
int 21h
驗證:編譯連接後生成exe程序,而後用debug載入,當運行到mov ax,4c00H的時候按p命令運行完結束程序,則會在命令提示符下顯示「Program terminated normally」
G命令:直接執行到指定的IP地址,可用於跳出循環
P命令:Debug就會自動重複執行循環中的指令,直到(cx)=0爲止。跳出循環執行到循環後的一條命令
在使用mams編譯咱們的彙編源程序的時候,咱們知道大於9FFFH的十六進制數據A000H、A001H、C000H、FFFEH、FFFFH等,在書寫的時候都是以字母開頭的。而在彙編源程序中,數據不能以字母開頭,因此要在前面加0。
在通常的PC機中,DOS方式下,DOS和其餘合法的程序通常都不會使用0:200~0:2FF的256 個字節的空間。因此,咱們使用這段空間是安全的。
例如「mov 0:26H,ax」語句會將內存0:26H的內容改寫,則會引發程序崩潰。
第六章包含多個段的程序
程序中咱們通常會定義多個段來存放咱們的數據、代碼和棧。而「代碼段」、「數據段」、「棧段」徹底是咱們本身的安排。例如:咱們在源程序中用僞指令「assume cs:code,ds:data,ss:stack」將cs、ds和ss分別和code、data、stack段相連。這樣作了以後,CPU是否就會將cs指向code,ds 指向data,ss 指向stack,從而按照咱們的意圖來分別不一樣的方式處理這些段中的數據。
第七章靈活的尋址方法
ASCII字母的大小寫之間相差20H,十進制也就是32,例如
大寫字母A(01000001)小寫字母a(01100001),二進制只有第五位有區別。因此只要第五位置0就是大寫字母,置1就是小寫字母,因此配合or和and命令就可完成操做。
and al,11011111B 結果爲小寫
or al,00100000B 結果爲大寫
SI和DI寄存器和BX功能相近,可是SI和DI不可以分紅兩個8位的寄存器來使用。
幾種定位內存地址的方法(可稱爲尋址方式),有如下幾種方式:
(1)[idata] 用一個常量來表示地址,可用於直接定位一個內存單元;
(2)[bx]用一個變量來表示內存地址,可用於間接定位一個內存單元;
(3)[bx+idata] 用一個變量和常量表示地址,可用於間接定位一個內存單元;
(4)[bx+si]用兩個變量表示地址;
(5)[bx+si+idata] 用兩個變量和一個常量表示地址。
例如指令mov ax,[bx+200]也能夠寫成以下格式(經常使用):
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
指令mov ax,[bx+si+idata]也能夠寫成以下格式(經常使用):
mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200
第八章數據處理的兩個基本問題
1)在8086CPU 中,只有這4個寄存器(bx、bp、si、di)能夠用在「[…]」中來進行內存單元的尋址。若是在「[…]」中程序ax、cx、dx、ds等都是錯誤的方式。
2)在「[…]」中,這4個寄存器(bx、bp、si、di)能夠單個出現,或只能以四種組合出現:(bx和si、bx和di、bp和si、bp和di) 錯誤的組合:(bx和bp、si和di)
3)只要在[…]中使用寄存器bp,而指令中沒有顯性的給出段地址,段地址就默認在ss中。好比:
mov ax,[bp] 含義:(ax)=((ss)*16+(bp))
mov ax,[bp+idata] 含義:(ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si] 含義:(ax)=((ss)*16+(bp)+(si))
mov ax,[bp+si+idata] 含義:(ax)=((ss)*16+(bp)+(si)+idata)
經常使用的幾種尋址方式:
彙編語言中用三個概念來表達數據的位置。
一、當即數(idata)
二、寄存器
三、段地址(SA)和偏移地址(EA)
1) 經過寄存器名指明要處理的數據的尺寸。例如:
mov ax,ds:[0]
mov ax,[bx]
mov ah,[2+bx]
mov al,ds:[2]
2) 在沒有寄存器名存在的狀況下,用操做符X ptr指明內存單元的長度,X在彙編指令中能夠爲word或byte。
例如:使用word ptr指明瞭指令訪問的內存單元是一個字單元
mov word ptr ds:[0],1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx],2
例如:使用byte ptr指明瞭指令訪問的內存單元是一個字節單元
mov byte ptr ds:[0],1
inc byte ptr [bx]
inc byte ptr ds:[0]
add byte ptr [bx],2
3) 其餘方法
例如:push和pop操做,都是對字單元操做,好比sp+2或sp-2等
div是除法指令,咱們知道除法的數學表達式:被除數÷除數=商……餘數
div指令格式:div 除數
除數能夠是寄存器或內存單元,例如
div 寄存器
div 內存單元
被除數:(默認)放在AX 或DX和AX中
除數:8位或16位,在寄存器或內存單元中
結果:
除數位數 |
8位 |
16位 |
被除數 |
AX |
AX+DX |
除數 |
寄存器或內存單元 |
寄存器或內存單元 |
商 |
AL |
AX |
餘數 |
AH |
DX |
|
|
|
例div byte ptr ds:[0]
商數爲:(al)=(ax)/((ds)*16+0)
餘數爲:(ah)=(ax)/((ds)*16+0)
例div word ptr es:[0]
商數爲:(ax)=[(dx)*10000H+(ax)]/((ds)*16+0)的商;
餘數爲:(dx)=[(dx)*10000H+(ax)]/((ds)*16+0)的餘數
說明:當除數爲16位時,被除數須要用dx和ax存放,dx存放的是數字的高16位,而ax存放的是數字的低16位,例如:0c85 6ef7H÷1c3dH=7183H……0ec0H (數字都是用16進製表示),則計算前默認0c85存儲在dx中,6ef7存儲在ax中,計算後則商7183H存儲在ax中,餘數0ec0H存儲在dx中。
前面咱們用db定義字節型數據、用dw定義字型數據。
dd是用來定義dword (double word雙字)型數據的。
例如:在data段中定義了三個數據
示例(1):
data segment
db 1;第一個數據爲01H,在data:0處,佔1個字節;
dw 1;第二個數據爲0001H,在data:1處,佔1個字;
dd 1;第三個數據爲00000001H,在data:3處,佔2個字節;
data ends
示例(2):
code segment
dd 12345678h ;定義一個十六進制的數12345678
mov ax,bx
code ends
咱們編譯、連接後,用debug載入,查看當前代碼段定義的數據,以下:
-d 145b:0
145B:0000 78 56 34 12 8B C3 00 00-00 00 00 00 00 00 00 00
從上面的代碼咱們能夠看出使用dd定義的數據在內存中的存儲形式:78 56 34 12
dup是一個重複操做符,在彙編語言中同db、dw、dd 等同樣,也是由編譯器識別處理的符號。它是和db、dw、dd 等數據定義僞指令配合使用的,用來進行數據的重複。
dup示例
db 3 dup (0)
定義了3個字節,它們的值是0、0、0,至關於db 0,0,0
db 3 dup (0,1,2)
定義了9個字節,它們是0、一、二、0、一、二、0、一、2,至關於db 0,1,2,0,1,2,0,1,2
db 3 dup (「abc」,」ABC」)
定義了18個字節,它們是」abcABCabcABCabcABC」,至關於db 「abcABCabcABCabcABC」
dup的使用格式以下:
db 重複的次數dup (重複的字節型數據)
dw 重複的次數dup (重複的字型數據)
dd 重複的次數dup (重複的雙字數據)
好比咱們要定義一個容量爲200 個字節的棧段,若是不用dup,則必須用這樣的格式:
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
固然,讀者能夠用dd,使程序變得簡短一些,可是若是要求定義一個容量爲1000字節或10000字節的呢?若是沒有dup,定義部分的程序就變得太長了;有了dup就能夠輕鬆解決。以下:
stack segment
db 200 dup (0)
stack ends
第九章轉移指令的原理
操做符offset在彙編語言中是由編譯器處理的符號,它的功能是取得標號的偏移地址。
例如:將s處的一條指令複製到s0處
s: mov ax,bx ;(mov ax,bx 的機器碼佔兩個字節)
mov si,offset s
mov di,offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0: nop ;(nop的機器碼佔一個字節)
nop
jmp爲無條件轉移,能夠只修改IP,也能夠同時修改CS和IP;
jmp指令要給出兩種信息:
1)轉移的目的地址
2)轉移的距離(段間轉移、段內短轉移,段內近轉移)
1) jmp short 標號(轉到標號處執行指令,是段內短轉移)
指令「jmp short 標號」的功能爲:(IP)=(IP)+8位偏移。
這種格式的jmp 指令實現的是段內短轉移,它對IP的修改範圍爲-128~127,也就是說,它向前轉移時能夠最多越過128個字節,向後轉移能夠最多越過127個字節。
說明:jmp的機器碼指令是距離當前指令的偏移地址,例如
程序源碼(1):
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
使用-u命令查看對應的機器碼以下:
-u
145B:0000 B80000 MOV AX,0000
145B:0003 EB03 JMP 0008
145B:0005 83C001 ADD AX,+01
145B:0008 40 INC AX
程序源碼(2):
assume cs:codesg
codesg segment
s:inc ax
start:mov ax,0
jmp short s
add ax,1
codesg ends
end start
使用-u命令查看對應的機器碼以下:
-u 0
145B:0000 40 INC AX
145B:0001 B80000 MOV AX,0000
145B:0004 EBFA JMP 0000
145B:0006 83C001 ADD AX,+01
上面的兩段源碼只是修改了s標號的位置,其餘的代碼徹底相同,經過源碼和對應的機器碼查看jmp命令,能夠看出EB是jmp命令的機器碼,然後面的數字03和FA是對應跳轉的當前指令的偏移地址,經過這個數字能夠看出FA應該是負數(-10:負數用補碼形式表示:即正數源碼取反加1),也就是對應的當前指令的偏移地址,因爲是使用一個字節表示,因此數據大小隻能是一個字節內的跳轉,範圍爲-128~127之間。
2)jmp near ptr 標號 (段內近轉移)
它實現的時段內近轉移。
指令「jmp near ptr 標號」的功能爲:(IP)=(IP)+16位偏移。
3)jmp far ptr 標號(段間轉移)
實現的是段間轉移,又稱爲遠轉移。例如:
程序源碼:
jmp far ptr s
db 256 dup (0)
s: add ax,1
使用-u命令查看對應的機器碼以下:
145B:0004 EA0B01BD0B JMP 0BBD:010B
總結:
1)指令「jmp short 標號」的功能爲(IP)=(IP)+8位位移。
(1)8位位移=「標號」處的地址-jmp指令後的第一個字節的地址;
(2)short指明此處的位移爲8位位移;
(3)8位位移的範圍爲-128~127,用補碼錶示
(4)8位位移由編譯程序在編譯時算出。
2)指令「jmp near ptr 標號」的說明:
(1)16位位移=「標號」處的地址-jmp指令後的第一個字節的地址;
(2)near ptr指明此處的位移爲16位位移,進行的是段內近轉移;
(3)16位位移的範圍爲-32769~32767,用補碼錶示;
(4)16位位移由編譯程序在編譯時算出。
3)指令「jmp far ptr 標號」功能以下:
(CS)=標號所在段的段地址;
(IP)=標號所在段中的偏移地址。
far ptr指明瞭指令用標號的段地址和偏移地址修改CS和IP。
4)jmp 16位寄存器
功能:IP =(16位寄存器)
Jmp 內存單元地址
5)jmp word ptr 內存單元地址(段內轉移)
功能:從內存單元地址處開始存放着一個字,是轉移的目的偏移地址。
6)jmp dword ptr 內存單元地址(段間轉移)
功能:從內存單元地址處開始存放着兩個字,高地址處的字是轉移的目的段地址,低地址處是轉移的目的偏移地址。
(CS)=(內存單元地址+2)
(IP)=(內存單元地址)
內存單元地址可用尋址方式的任一格式給出。
例如:程序源碼
assume cs:codesg
data segment
db 16 dup(0)
data ends
codesg segment
start:mov ax,data
mov ds,ax
mov ax,1234h
mov bx,5678h
mov ds:[0],ax
mov ds:[2],bx
jmp dword ptr ds:[0]
mov ax,1
codesg ends
end start
代碼執行到跳轉指令以前使用-d和-u命令查看對應的機器碼以下:
-d 0 2f
145B:0000 34 12 78 56 00 00 00 00 00-00 00 00 00 00 00 00 00
145B:0010 B8 5B 14 8E D8 B8 34 12-BB 78 56 A3 00 00 89 1E
145B:0020 02 00 FF 2E 00 00 B8 01-00 00 00 00 00 00 00 00
-u 0 19
145C:0000 B85B14 MOV AX,145B
145C:0003 8ED8 MOV DS,AX
145C:0005 B83412 MOV AX,1234
145C:0008 BB7856 MOV BX,5678
145C:000B A30000 MOV [0000],AX
145C:000E 891E0200 MOV [0002],BX
145C:0012 FF2E0000 JMP FAR [0000]
145C:0016 B80100 MOV AX,0001
145C:0019 0000 ADD [BX+SI],AL
執行跳轉命令後CS和IP的值分別是:
145C:0012 FF2E0000 JMP FAR [0000]
CS=5678 IP=1234
經過以上程序能夠說明:
(IP)=(內存單元地址)
(CS)=(內存單元地址+2)
負數(補碼) =正數原碼取反加1
jcxz指令爲有條件轉移指令,全部的有條件轉移指令都是短轉移,在對應的機器碼中包含轉移的位移,而不是目的地址。對IP的修改範圍都爲-128~127。
指令格式:jcxz 標號(若是(cx)=0,則轉移到標號處執行。)
loop指令爲循環指令,全部的循環指令都是短轉移,在對應的機器碼中包含轉移的位移,而不是目的地址。對IP的修改範圍都爲-128~127。
指令格式:loop 標號 ((cx))=(cx)-1,若是(cx)≠0,轉移到標號處執行。
loop 標號指令操做:
(1)(cx)=(cx)-1;
(2)若是(cx)≠0,(IP)=(IP)+8位位移。
8位位移=「標號」處的地址-loop指令後的第一個字節的地址;
8位位移的範圍爲-128~127,用補碼錶示;
8位位移由編譯程序在編譯時算出。
當(cx)=0,什麼也不作(程序向下執行)。
第十章 call和ret指令
CPU執行call指令,進行兩步操做:
(1)將當前的IP 或CS和IP 壓入棧中;
(2)轉移。
語法格式:call 標號(實現的是段內轉移)
16位位移=「標號」處的地址-call指令後的第一個字節的地址;
16位位移的範圍爲-32768~32767,用補碼錶示;
16位位移由編譯程序在編譯時算出。
CPU 執行指令「call 標號」時,至關於進行:
push IP
jmp near ptr 標號
語法格式:call far ptr 標號(實現的是段間轉移)
CPU 執行指令「call far ptr 標號」時,至關於進行:
push CS
push IP
jmp far ptr 標號
說明:call 指令不能實現短轉移,除此以外,call指令實現轉移的方法和jmp 指令的原理相同。
ret指令用棧中的數據,修改IP的內容,從而實現近轉移;
CPU執行ret指令時,至關於進行:pop IP
retf指令用棧中的數據,修改CS和IP的內容,從而實現遠轉移;
CPU執行retf指令時,至關於進行:pop IP再pop CS
指令格式:call 16位寄存器
說明:因爲寄存器爲16位,使用這種方式只能是段內轉移。
CPU執行call 16位reg時,至關於進行:
push IP
jmp 16位寄存器
有兩種格式:
1)call word ptr 內存單元地址
CPU執行call word ptr時,至關於進行:
push IP
jmp dword ptr 內存單元地址
2) call dword ptr 內存單元地址
CPU執行call dword ptr時,至關於進行:
push CS
push IP
jmp dword ptr 內存單元地址
mul是乘法指令,使用mul 作乘法的時候:
(1)相乘的兩個數:要麼都是8位,要麼都是16位,不能一個8位和一個16位相乘。
8 位乘法:默認使用AL中乘於8位寄存器或內存字節單元;
16 位乘法:默認使用AX中乘於16 位寄存器或內存字單元中。
(2)相乘的結果
8位乘法:結果保存在AX中;
16位乘法:結果保存在DX(高位)和AX(低位)中。
格式以下:
Mul reg
Mul 內存單元
內存單元能夠用不一樣的尋址方式給出
1)能夠用寄存器來存儲參數和結果
2) 可使用內存單元(各棧段)來存儲參數和結果(通常只傳首地址)
第十一章標誌寄存器
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
|
|
|
OF |
DF |
IF |
TF |
SF |
ZF |
|
AF |
|
PF |
|
CF |
flag的一、三、五、十二、1三、1四、15位在8086CPU中沒有使用,不具備任何含義。而0、二、四、六、七、八、九、十、11位都具備特殊的含義。
標誌位在內存中的符號表示:
標誌 |
含義說明 |
值爲1的標記 |
值爲0的標記 |
第11位OF |
溢出標誌,有是1,無是0 |
OV |
NV |
第10位DF |
SI和DI方向標誌,遞增0,遞減1 |
DN |
UP |
第9位IF |
單步調試標誌,爲1進行單步調試 |
EI |
DI |
第7位SF |
符號標誌,正數是0,負數是1 |
NG |
PL |
第6位ZF |
操做結果爲零標誌 |
ZR |
NZ |
第4位AF |
|
AC |
NA |
第2位PF |
奇偶標誌,偶是1,奇是0 |
PE |
PO |
第0位CF |
進借位標誌,有是1,無是0 |
CY |
NC |
它記錄相關指令執行後,的結果的值。
結果爲0 ,ZF = 1
結果不爲0,ZF = 0
它記錄指令執行後,結果的全部二進制位中1的個數:
爲偶數,PF = 1;
爲奇數,PF = 0。
它記錄指令執行後,結果的正負數狀況。
結果爲負,SF = 1;
結果爲正,SF = 0。
它記錄相關指令執行的過程當中是否有進位或借位的狀況。
有進位或借位,CF=1;
無進位或借位,CF=0;
通常狀況下,在進行無符號數運算的時候,它記錄了運算結果的最高有效位向更高位的進位值,或從更高位的借位值。
通常狀況下,OF記錄了有符號數運算的結果是否發生了溢出。
若是發生溢出,OF=1,
若是沒有,OF=0。
若是運算結果超出了機器所能表達的範圍,將產生溢出。這裏所講的溢出,只是對有符號數運算而言。
CF是對無符號數運算有意義的標誌位;
OF是對有符號數運算有意義的標誌位。
對於無符號數運算,CPU用CF位來記錄是否產生了進位;
對於有符號數運算,CPU 用OF 位來記錄是否產生了溢出,固然,還要用SF位來記錄結果的符號。
CF 和OF 所表示的進位和溢出,是分別對無符號數和有符號數運算而言的,它們之間沒有任何關係。
adc是帶進位加法指令,它利用了CF位上記錄的進位值。
格式:adc 操做對象1,操做對象2
功能:操做對象1=操做對象1+操做對象2+CF
好比:adc ax,bx,實現的功能是:(ax)=(ax)+(bx)+CF
能夠看出,adc指令比add指令多加了一個CF位的值。
adc指令執行後,將對CF進行設置。
在執行adc 指令的時候加上的CF 的值的含義,由adc指令前面的指令決定的,也就是說,關鍵在於所加上的CF值是被什麼指令設置的。
利用adc指令咱們能夠對任意大的數據進行加法運算,先對低位運算,而後再對高位運算。
sbb是帶錯位減法指令,它利用了CF位上記錄的借位值。
格式:sbb 操做對象1,操做對象2
功能:操做對象1=操做對象1–操做對象2–CF
好比:sbb ax,bx,實現功能:(ax) = (ax) –(bx) –CF
能夠看出,sbb指令比sub指令多減了一個CF位的值。
sbb指令執行後,將對CF進行設置。
利用sbb指令咱們能夠對任意大的數據進行減法運算,先對低位運算,而後再對高位運算。
顯然,若是CF 的值是被sub指令設置的,那麼它的含義就是借位值;若是是被add指令設置的,那麼它的含義就是進位值。
cmp 是比較指令,功能至關於減法指令,只是不保存結果。
cmp 指令執行後,將對標誌寄存器產生影響。
其餘相關指令經過識別這些被影響的標誌寄存器位來得知比較結果。
格式:cmp 操做對象1,操做對象2
功能:計算操做對象1–操做對象2 但並不保存結果,僅僅根據計算結果對標誌寄存器進行相應的設置。
好比:cmp ax,ax
作(ax)–(ax)的運算,結果爲0,但並不在ax中保存,僅影響flag的相關各位。
指令執行後:
ZF=1,
PF=1,
SF=0,
CF=0,
OF=0。
咱們經過cmp 指令執行後,相關標誌位的值就能夠看出比較的結果。
cmp ax,bx
若是(ax)=(bx),則(ax)-(bx)=0,因此:ZF=1
若是(ax)≠(bx),則(ax)-(bx)≠0,因此:ZF=0
若是(ax)<(bx),則(ax)-(bx)將產生借位,因此:ZF=1
若是(ax)≥(bx),則(ax)-(bx)將沒必要借位,因此:ZF=0
若是(ax)>(bx),則(ax)-(bx)既沒必要借位,結果又不爲0,因此:CF=0而且ZF=0
若是(ax)≤(bx),則(ax)-(bx)既可能借位,結果可能爲0,因此:CF=1或者ZF=1
反過來推理上面的例子cmp ax,ax
指令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,說明:(ax) ≤ (bx)
從上面的咱們也能夠看出,CPU利用cmp指令能夠對無符號數進行比較,也能夠對有符號數進行比較,好比進位、借位、溢出等。因此cmp 指令所做的比較結果,不是僅僅靠SF就能記錄的,由於它只能記錄實際結果的正負,咱們應該在考察SF(得知實際結果的正負)的同時考察OF(得知有沒有溢出),就能夠得知邏輯上真正結果的正負,同時就能夠知道比較的結果。
咱們以cmp ah,bh爲例,總結CPU執行cmp指令後,SF和OF的值是如何來講明比較的結果的。
(1)若是SF=1,而OF=0
因OF=0,說明沒有溢出,邏輯上真正結果的正負=實際結果的正負;
因SF=1,實際結果爲負,因此邏輯上真正的結果爲負。經過:SF=1,OF = 0,說明了(ah)<(bh)。
(2)若是SF=1,而OF=1
因OF=1 ,說明有溢出,邏輯上真正結果的正負≠實際結果的正負;
因SF=1 ,實際結果爲負,而又有溢出且結果非0,這說明是因爲溢出致使了實際結果爲負,
簡單分析一下,就能夠看出,若是由於溢出致使了實際結果爲負,那麼邏輯上真正的結果必然爲正。經過:SF=1,OF = 1 ,說明了(ah)>(bh)。
(3)若是SF=0,而OF=1
因OF=1 ,說明有溢出,邏輯上真正結果的正負≠實際結果的正負;
因SF=0,實際結果非負,而又有溢出且結果非0,這說明是因爲溢出致使了實際結果非負,
簡單分析一下,就能夠看出,若是由於溢出致使了實際結果爲正,那麼邏輯上真正的結果必然爲負。經過:SF=0,OF = 1 ,說明了(ah)<(bh)。
(4)若是SF=0,而OF=0
因OF=0,說明沒有溢出,邏輯上真正結果的正負=實際結果的正負;
因SF=0,實際結果非負,因此邏輯上真正的結果必然非負。經過:SF=0,OF = 0,說明了(ah)≥(bh)。
「轉移」指的是它可以修改IP,而「條件」指的是它能夠根據某種條件,決定是否修改IP。
全部條件轉移指令的轉移位移都是:[-128,127]。
好比:jcxz就是一個條件轉移指令,它能夠檢測cx 中的數值,若是(cx)=0,就修改IP,不然什麼也不作。
除了jcxz 以外,CPU還提供了其餘條件轉移指令,大多數條件轉移指令都檢測標誌寄存器都是根據cmp指令影響的相關標誌位,根據檢測的結果來決定是否修改IP。條件轉移指令一般都和cmp相配合使用,就好像call 和ret 指令一般相配合使用同樣。
cmp 指令能夠進行無符號數比較和有符號數比較,因此根據cmp 指令的比較結果進行轉移的指令也分爲兩種,即:
(1)根據無符號數的比較結果進行轉移的條件轉移指令,它們檢測ZF、CF的值;
(2)和根據有符號數的比較結果進行轉移的條件轉移指令,它們檢測SF、OF和ZF的值。
無符號轉移的條件轉移指令
指令 |
含義 |
檢測的標準位 |
je |
等於則轉移 |
ZF=1 |
jne |
不等於則轉移 |
ZF=0 |
jb |
低於則轉移 |
CF=1 |
jnb |
不低於則轉移 |
CF=0 |
ja |
高於則轉移 |
ZF=0,CF=0 |
jna |
不高於則轉移 |
ZF=1或CF=1 |
這些指令比較經常使用,它們都很好記憶,它們的每個字母的含義以下:
j:表示jump
e:表示equal;
ne:表示not equal;
b:表示below;
nb:表示not below;
a:表示above;
na:表示not above。
經過上面說明條件跳轉指令是根據判斷不一樣的標誌位進行跳轉的,這也說明跳轉前是否使用cmp指令在於咱們本身的安排,無論前面是什麼語句,只要影響力對應的標誌位,那麼條件跳轉就會發生轉移。
在串處理指令中,控制每次操做後si,di的增減。
DF = 0:每次操做後si,di遞增;
DF = 1:每次操做後si,di遞減。
格式1:rep movsb
功能:(以字節爲單位傳送)
(1) ((es)×16 + (di)) = ((ds) ×16 + (si))
(2) 若是DF = 0則:(si) = (si) + 1 , (di) = (di) + 1
若是DF = 0則:(si) = (si) -1 , (di) = (di) -1
咱們用匯編語法描述movsb的功能,8086CPU並不支持這樣的指令,這裏只是個描述。以下:
mov es:[di],byte ptr ds:[si];
若是DF=0:
inc si
inc di
若是DF=1:
dec si
dec di
能夠看出,movsb 的功能是將ds:si 指向的內存單元中的字節送入es:di中,而後根據標誌寄存器DF位的值,將si和di遞增或遞減。
格式2:rep movsw
功能:(以字爲單位傳送)
將ds:si指向的內存字單元中word送入es:di中,而後根據標誌寄存器DF位的值,將si和di遞增2或遞減2。
以用匯編語法描述movsw的功能以下:
mov es:[di],word ptr ds:[si]; 8086 CPU並不支持這樣的指令,這裏只是個描述。
若是DF=0:
add si,2
add di,2
若是DF=1:
sub si,2
sub di,2
rep指令是根據cx循環的指令,movsb和movsw進行的是串傳送操做中的一個步驟,通常來講,movsb 和movsw 都和rep配合使用。
格式以下:
rep movsb
用匯編語法來描述rep movsb的功能就是:
s : movsb loop s
rep movsw
用匯編語法來描述rep movsw的功能就是:s : movsw loop s
可見,rep的做用是根據cx的值,重複執行後面的串傳送指令。
因爲每執行一次movsb指令si和di都會遞增或遞減指向後一個單元或前個單元,則rep movsb就能夠循環實現(cx)個字符的傳送。
因爲flag的DF位決定着串傳送指令執行後,si和di改變的方向,因此CPU應該提供相應的指令來對DF位進行設置,從而使程序員可以決定傳送的方向。8086CPU提供下而兩條指令對DF位進行設置:
cld指令:將標誌寄存器的DF位置0
std指令:將標誌寄存器的DF位置1
指令助記:cl可看做clear—清除;st表示set—設置,d表示DF標誌位
第十二章 內中斷
中斷的意思是指,CPU再也不接着(剛執行完的指令)向下執行,而是轉去處理這個特殊信息。
根據中斷的類型可分爲:
內中斷:中斷信息來自CPU 的內部,當CPU 的內部有須要處理的事情發生的時候,將產生須要立刻處理的中斷信息,引起中斷過程。
外中斷:中斷信息來自CPU 的外部,當CPU 的內部有須要處理的事情發生的時候,將產生須要立刻處理的中斷信息,引起中斷過程。
對於8086CPU,當內部有下面狀況發生的時候,將產生中斷信息:
一、除法錯誤,好比:除法溢出;à對應CPU的0號中斷
二、單步執行; à對應CPU的1號中斷
三、執行int0指令; à對應CPU的4號中斷
四、執行int 指令。 à把指令提供給CPU
說明:int 指令的格式爲:int n,指令中的n爲字節型當即數,是提供給CPU的中斷類型碼。
CPU用8 位的中斷類型碼經過中斷向量表找到相應的中斷處理程序的入口地址。
中斷向量表在內存中保存,其中存放着256箇中斷源所對應的中斷處理程序的入口,對於8086PC機,中斷向量表指定放在內存地址0處。從內存0000:0000到0000:03FF的1024個單元中存放着中斷向量表。可是通常0000:0200到0000:02FF這裏的256 個字節的空間沒有使用。因此,咱們使用這段空間是安全的。
用中斷類型碼找到中斷向量,並用它設置CS和IP,這個工做是由CPU的硬件自動完成的。完成這個工做的過程被稱爲中斷過程。
8086CPU的中斷過程:
(1)(從中斷信息中)取得中斷類型碼;
(2)標誌寄存器的值入棧(由於在中斷過程當中要改變標誌寄存器的值,因此先將其保存在棧中。);
(3)設置標誌寄存器的第8位TF 和第9位IF的值爲0;(這一步的目的後面的單步中斷將介紹)
(4)CS的內容入棧;
(5)IP的內容入棧;
(6)從內存地址爲中斷類型碼*4 和中斷類型碼*4+2 的兩個字單元中讀取中斷處理程序的入口地址設置IP和CS。
咱們用匯編的形式描述中斷過程,以下:
(1)取得中斷類型碼N;
(2)pushf
(3)TF = 0,IF = 0
(4)push CS
(5)push IP
(6)(IP) = (N*4),(CS) = (N*4+2)
在最後一步完成後,CPU 開始執行由程序員編寫的中斷處理程序。
中斷處理程序的編寫方法和子程序的比較類似,下面是常規的步驟:
(1)保存用到的寄存器。
(2)處理中斷。
(3)恢復用到的寄存器。
(4)用iret 指令返回。
iret指令的功能是恢復中斷指令保存的標準寄存器、CS、IP的狀態,iret一般和硬件自動完成的中斷過程配合使用。
用匯編語法描述爲:
pop IP
pop CS
popf
能夠看出,在中斷過程當中,寄存器入棧的順序是:標誌寄存器、CS、IP ,而iret的出棧順序是IP、CS、標誌寄存器,恰好和其對應,實現了用執行中斷處理程序前的CPU現場恢復標誌寄存器、CS、IP的工做。
分析(1):
當發生除法溢出的時候,產生0號中斷信息,從而引起中斷過程。此時,CPU將進行如下工做:
①取得中斷類型碼0;
②標誌寄存器入棧,TF、IF設置爲0;
③CS、IP入棧;
④(IP) = (0*4),(CS) = (0*4+2)
分析(2):
可見,當中斷0 發生時,CPU將轉去執行中斷處理程序。只要按以下步驟編寫中斷處理程序,當中斷0發生時,便可顯示「overflow!」。
①相關處理。
②向顯示緩衝區送字符串「overflow!」。
③返回DOS
咱們將這段程序稱爲do0。
分析(3):
如今的問題是:do0 應放在內存中。由於除法溢出隨時可能發生,CPU隨時均可能將CS:IP指向do0的入口,執行程序。
分析(4):
根據前面咱們能夠直接本身寫個子程序來處理中斷,此時咱們要作如下幾件事情:
①編寫能夠處理0號中斷的處理程序:do0;
②將do0送入內存0000:0200處;
③將do0的入口地址0000:0200存儲在中斷向量表0號表項中。
說明:中斷處理的子程序所用到的數據等所有信息要同時保存到內存0000:0200處;
基本上,CPU在執行完一條指令以後,若是檢測到標誌寄存器的TF位爲1,則產生單步中斷,引起中斷過程。單步中斷的中斷類型碼爲1,則它所引起的中斷過程以下:
(1)取得中斷類型碼1;
(2)標誌寄存器入棧,TF、IF設置爲0;
(3)CS、IP入棧;
(4)(IP)=(1*4),(CS)=(1*4+2)。
如上所述,若是TF=1,則執行一條指令後,CPU就要轉去執行1號中斷處理程序。
咱們來簡要地考慮一下Debug是如何利用CPU所提供的單步中斷的功能的。
①Debug提供了單步中斷的中斷處理程序。中斷程序功能:顯示全部寄存器中的內容後等待輸入命令。
②在使用T 命令執行指令時,Debug 將TF設置爲1,使得CPU在工做於單步中斷方式下,則在CPU執行完這條指令後就引起單步中斷,執行單步中斷的中斷處理程序。
說明:在進入中斷處理程序以前,設置TF=0。這就是爲何在中斷過程當中有TF=0這個步驟。
咱們再來看一下通常通用的中斷過程:
(1)取得中斷類型碼N;
(2)標誌寄存器入棧,TF=0、IF=0;
(3)CS、IP入棧;
(4)(IP) = (N*4),(CS) = (N*4+2)
它記錄相關指令執行的過程當中是否發生單步中斷,單步中斷的中斷類型碼爲1。
單步中斷,TF=1;
無單步中斷,TF=0;
通常狀況下,CPU在執行完一條指令以後,若是檢測到標誌寄存器的TF位爲1,則產生單步中斷,引起中斷過程,轉入中斷處理程序,在進入中斷處理程序以前,設置TF=0。
通常狀況下,CPU在執行完當前指令後,若是檢測到中斷信息,就響應中斷,引起中斷過程。但是,在有些狀況下,CPU 在執行完當前指令後,即使是發生中斷,也不會響應。
例如:在執行完向ss寄存器傳送數據的指令後,即使是發生中斷,CPU 也不會響應。這樣作的主要緣由是,ss:sp聯合指向棧頂,而對它們的設置應該連續完成。若是在執行完設置ss的指令後,CPU響應中斷,引起中斷過程,在中斷處理程序中還要在棧中壓入標誌寄存器、CS和IP的值。而中斷返回後ss:sp指向的不是正確的棧頂,將引發錯誤。因此CPU在執行完設置ss的指令後,不響應中斷。這給連續設置ss和sp,指向正確的棧頂提供了一個時機。
例如,咱們要將棧頂設爲1000:0,程序代碼以下:
mov ax,1000h
mov ss,ax
mov bx,1234h
mov sp,0
咱們使用debug單步執行,發現mov bx,1234h是沒有暫停的,而是直接暫停在mov sp,0這裏,充分說明設置ss的指令後不會暫停,會再執行一條語句後暫停,也就是執行設置ss指令會連續執行兩條命令後才暫停。
咱們應該利用這個特性,將設置ss和sp的指令連續存放,使得設置ss的指令後緊接着設置sp的指令執行,而在此之間,CPU不會引起中斷過程,其中也包括單步中斷,因此Debug設置的單步中斷程序(用來顯示寄存器狀態和等待輸入命令的中斷處理程序)根本沒有獲得執行,因此咱們看不到預期的結果。
第十三章 int指令
CPU 執行int n指令,至關於引起一個n號中斷的中斷過程,程序轉而執行中斷處理程序。
int格式:int n,n爲中斷類型碼。它的功能是引起中斷過程。
int n指令執行過程以下:
(1)取中斷類型碼n;
(2)標誌寄存器入棧,IF = 0,TF = 0;
(3)CS、IP入棧;
(4)(IP) = (n*4),(CS) = (n*4+2)。
今後處轉去執行n號中斷的中斷處理程序。
能夠在程序中使用int指令調用任何一箇中斷的中斷處理程序。例如在程序中調用0號中斷:int 0
先看一下int 10h中斷例程的設置光標位置功能。int 10h中斷例程是BIOS提供的中斷例程,其中包含了多個和屏幕輸出相關的子程序。通常來講,一個供程序員調用的中斷例程中每每包括多個子程序,中斷例程內部用傳遞進來的參數來決定執行哪一個子程序。BIOS 和DOS 提供的中斷例程,都用ah來傳遞內部子程序的編號。
mov ah,2 ; 表示調用第10h號中斷例程的2號子程序
mov bh,0 ; 表示設置光標到顯存的第0頁
mov dh,5 ;表示行號
mov dl,12 ;表示列號
int 10h
(ah)=2表示調用第10h號中斷例程的2號子程序,功能爲設置光標位置,能夠提供光標所在的行號(80*25字符模式下:0~24)、列號(80*25字符模式下:0~79),和頁號做爲參數。
(bh)=0,(dh)=5,(dl)=12,設置光標到第0頁,第5行,第12列。
bh中頁號的含義:
內存地址空間中,B8000h~BFFFFh共32K的空間,爲80*25 彩色字符模式的顯示緩衝區。一屏的內容在顯示緩衝區中共佔4000個字節。顯示緩衝區分爲8頁,每頁4K(≈4000),顯示器能夠顯示任意一頁的內容。通常狀況下,顯示第0 頁的內容。也就是說,一般狀況下,B8000~B8F9F中的4000個字節的內容將出如今顯示器上。
再看一下int 10h中斷例程的在光標位置顯示字符功能。
mov ah,9;設置光標
mov al,‟a‟;將要顯示的字符
mov bl,7;顏色屬性
mov bh,0;第0頁
mov cx,3;字符重複個數
int 10h
(ah)=9 表示調用第10h號中斷例程的9號子程序;
功能:在光標位置顯示字符,能夠提供要顯示的字符、顏色屬性、頁號、字符重複個數做爲參數。
(bh)中的顏色屬性格式以下:
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
BL |
R |
G |
B |
I |
R |
G |
B |
閃爍 |
背景色 |
高亮 |
前景色 |
能夠看出,和顯存中的屬性字節的格式相同。
int 21h 中斷例程是DOS提供的中斷例程,其中包含了DOS提供給程序員在編程時調用的子程序。咱們從前一直使用的是int 21中斷例程的4ch號功能,即程序返回功能,以下:
mov ah,4ch ;程序返回
mov al,0 ;返回值
int 21h
(ah)=4ch表示調用第21h號中斷例程的4ch號子程序,功能爲程序返回,能夠提供返回值做爲參數。
咱們前面使用這個功能的時候常常寫做:
mov ax,4c00h
int 21h
咱們看一下int 21h中斷例程的在光標位置顯示字符串的功能:
ds:dx ;指向字符串;要顯示的字符串需用「$」做爲結束符
mov ah ,9 ;表示9號子程序,表示在光標位置顯示字符串
int 21h
(ah)=9表示調用第21h號中斷例程的9號子程序,功能爲在光標位置顯示字符串,能夠提供要顯示字符串的地址做爲ds:dx參數,而且以$符號爲結束符號。
例如在屏幕的5列12行顯示字符串「Welcome to masm!」,直到碰見「$」(「$」自己並不顯示,只起到邊界的做用)。若是字符串比較長,遇到行尾,程序會自動轉到下一行開頭處繼續顯示;若是到了最後一行,還能自動上卷一行。DOS爲程序員提供了許多能夠調用的子程序,都包含在int 21h 中斷例程中。咱們這裏只對原理進行了講解,對於DOS提供的全部可調用子程序的狀況,讀者能夠參考相關的書籍。
lea(Load Effective Address:載入有效地址),lea指令的功能是將源操做數、即存儲單元的有效地址(偏移地址)傳送到目的操做數,將一個近地址指針寫入到指定的寄存器,內存單元可使用多種尋址方式。
格式:LEA reg16,mem16
lea還能夠做簡單的算術計算,特別是有了32位指令的加強尋址方式,更是「如虎添翼」, 好比你要算EAX*4+EBX+3,結果放入EDX,怎麼辦?
mov edx, eax
shl edx, 2
add edx, ebx
add edx, 3
如今用lea一條指令搞定: lea edx, [ebx+eax*4+3]
LEA與MOV傳送指令的區別:MOV傳送的是地址所指的內容,而LEA只是地址。
第十四章 端口
CPU能夠直接讀寫3 個地方的數據:
(1)CPU 內部的寄存器;
(2)內存單元;
(3)端口。
端口的讀寫指令只有兩條:in和out,
分別用於從端口讀取數據和往端口寫入數據。
in al,60h;從60h號端口讀入一個字節,執行時與總線相關的操做過程:
①CPU經過地址線將地址信息60h發出;
②CPU經過控制線發出端口讀命令,選中端口所在的芯片,並通知它,將要從中讀取數據;
③端口所在的芯片將60h端口中的數據經過數據線送入CPU。
注意:在in和out 指令中,只能使用ax 或al 來存放從端口中讀入的數據或要發送到端口中的數據。訪問8 位端口時用al ,訪問16 位端口時用ax 。in或out會同時執行兩條語句,也就是緊跟在in或out後面的語句也會執行,沒法單步跟蹤。
(1)對0~255之內的端口進行讀寫:
in al,20h;從20h端口讀入一個字節
out 20h,al ;往20h端口寫入一個字節
(2)對256~65535的端口進行讀寫時,端口號放在dx中:
mov dx,3f8h ;將端口號3f8送入dx
in al,dx ;從3f8h端口讀入一個字節
out dx,al ;向3f8h端口寫入一個字節
PC機中有一個CMOS RAM芯片,其有以下特徵:
(1)包含一個實時鐘和一個有128個存儲單元的RAM存儲器。(早期的計算機爲64個字節)
(2)該芯片靠電池供電。因此,關機後其內部的實時鐘仍可正常工做,RAM 中的信息不丟失。
(3)128 個字節的RAM 中,內部實時鐘佔用0~0dh單元來保存時間信息,其他大部分分單元用於保存系統配置信息,供系統啓動時BIOS程序讀取。BIOS也提供了相關的程序,使咱們能夠在開機的時候配置CMOS RAM 中的系統信息。
(4)該芯片內部有兩個端口,端口地址爲70h和71h。CPU 經過這兩個端口讀寫CMOS RAM。
(5)70h爲地址端口,存放要訪問的CMOS RAM單元的地址;71h爲數據端口,存放從選定的CMOS RAM 單元中讀取的數據,或要寫入到其中的數據。
可見,CPU對CMOS RAM的讀寫分兩步進行。好比:讀CMOS RAM的2號單元:
一、將2送入端口70h
二、從71h讀出2號單元的內容
shl和shr 是邏輯移位指令。
shl邏輯左移指令,功能爲:
(1)將一個寄存器或內存單元中的數據向左移位;
(2)將最後移出的一位寫入CF中;
(3)最低位用0補充。
能夠看出,將X邏輯左移一位,至關於執行X=X*2。
shr邏輯右移指令,它和shl所進行的操做恰好相反:
(1)將一個寄存器或內存單元中的數據向右移位;
(2)將最後移出的一位寫入CF中;
(3)最高位用0補充。
能夠看出,將X邏輯右移一位,至關於執行X=X/2
若是移動位數大於1時,必須將移動位數放在cl中。
在CMOS RAM中,存放着當前時間:
秒:00H
分:02H
時:04H
日:07H
月:08H
年:09H
這6個信息的長度長度都爲1個字節。
這些數據以BCD碼的方式存放:
數碼 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
BCD碼 |
0000 |
0001 |
0010 |
0011 |
0100 |
0101 |
0110 |
0111 |
1000 |
1001 |
好比:數值26,用BCD碼錶示爲:0010 0110。可見,一個字節可表示兩個BCD碼。則CMOS RAM存儲時間信息的單元中,存儲了用兩個BCD碼錶示的兩位十進制數,高4 位的BCD碼錶示十位,低4 位的BCD 碼錶示個位。
好比:00010100b表示14。
第十五章 外中斷
在PC 系統中,由外部硬件等引起的中斷稱爲外中斷,外中斷源一共有兩類:
一、可屏蔽中斷
二、不可屏蔽中斷
可屏蔽中斷:是CPU 能夠不響應的外中斷。
CPU 是否響應可屏蔽中斷,要看標誌寄存器的IF 位的設置。
當CPU 檢測到可屏蔽中斷信息時:
若是IF=1,則CPU 在執行完當前指令後響應中斷,引起中斷過程;
若是IF=0,則不響應可屏蔽中斷。
回憶一下內中斷所引起的中斷過程:
(1)取中斷類型碼n;
(2)標誌寄存器入棧,設置IF=0,TF=0;
(3)CS 、IP 入棧;
(4)(IP)=(n*4),(CS)=(n*4+2)
由此轉去執行中斷處理程序。
可屏蔽中斷所引起的中斷過程,除在第1步的實現上有所不一樣外,基本上和內中斷的中斷過程相同。由於可屏蔽中斷信息來自於CPU外部,中斷類型碼是經過數據總線送入CPU 的;而內中斷的中斷類型碼是在CPU內部產生的。
它記錄相關指令執行的過程當中是否響應中斷,中斷類型碼爲1。
響應中斷,TF=1;跳轉到中斷處理程序
不響應中斷,TF=0;屏蔽中斷,不轉去處理中斷程序
通常狀況下,CPU在執行完一條指令以後,若是檢測到標誌寄存器的IF位爲1,則產生中斷,引起中斷過程,轉入中斷處理程序。
咱們能夠解釋通常中斷過程當中將IF置爲0的緣由了。將IF置0的緣由就是,在進入中斷處理程序後,禁止其餘的可屏蔽中斷。固然,若是在中斷處理程序中還須要處理其餘的中斷,如可屏蔽中斷,能夠用指令將IF 置1 ,這樣在處理中斷斷過程當中一樣也能夠被其餘中斷程序中斷,轉去執行中斷程序。
8086CPU 提供的設置IF的指令以下:
sti,用於設置IF=1;
cli,用於設置IF=0。
不可屏蔽中斷是CPU 必須響應的外中斷。當CPU 檢測到不可屏蔽中斷信息時,則在執行完當前指令後,當即響應,引起中斷過程。對於8086CPU 不可屏蔽中斷的中斷類型碼固定爲2。因此中斷過程當中,不須要取中斷類型碼。
不可屏蔽中斷的中斷過程:
一、標誌寄存器入棧,IF=0,TF=0;
二、CS、IP入棧;
三、(IP)=(8),(CS)=(0AH)。
幾乎全部由外設引起的外中斷,都是可屏蔽中斷。當外設有須要處理的事件(好比說鍵盤輸入)發生時,相關芯片向CPU 發出可屏蔽中斷信息。
不可屏蔽中斷是在系統中有必須處理的緊急狀況發生時用來通知CPU 的中斷信息。在咱們的課程中,主要討論可屏蔽中斷。
例如鍵盤輸入的處理過程,並以此來體會一下PC 機處理外設輸入的基本方法。
一、鍵盤輸入
二、引起9號中斷
三、執行int 9中斷例程
鍵盤上的每個鍵至關於一個開關,鍵盤中有一個芯片對鍵盤上的每個鍵的開關狀態進行掃描。
按下一個鍵時,開關接通,該芯片就產生一個掃描碼,掃描碼說明了按下的鍵在鍵盤上的位置。掃描碼被送入主板上的相關接口芯片的寄存器中,該寄存器的端口地址爲60H 。
鬆開按下的鍵時,也產生一個掃描碼,掃描碼說明了鬆開的鍵在鍵盤上的位置。鬆開按鍵時產生的掃描碼也被送入60H 端口中。
通常將按下一個鍵時產生的掃描碼稱爲通碼,鬆開一個鍵產生的掃描碼稱爲斷碼。掃描碼長度爲一個字節,通碼的第7 位爲0 ,斷碼的第7位爲1,即:斷碼=通碼+80H
好比:g鍵的通碼爲22H,斷碼爲a2H。
部分鍵盤上部分鍵的掃描碼:
鍵 |
掃描碼 |
鍵 |
掃描碼 |
鍵 |
掃描碼 |
鍵 |
掃描碼 |
Esc |
01 |
[ |
1A |
\ |
2B |
NumLock |
45 |
1~9 |
02~0A |
] |
1B |
Z |
2C |
ScrollLock |
46 |
0 |
0B |
Enter |
1C |
X |
2D |
Home |
47 |
- |
0C |
Ctrl |
1D |
C |
2E |
↑ |
48 |
= |
0D |
A |
1E |
V |
2F |
PgUp |
49 |
Backspace |
0E |
S |
1F |
B |
30 |
- |
4A |
Tab |
0F |
D |
20 |
N |
31 |
← |
4B |
Q |
10 |
F |
21 |
M |
32 |
→ |
4D |
W |
11 |
G |
22 |
, |
33 |
+ |
4E |
Esc |
12 |
H |
23 |
. |
34 |
End |
4F |
R |
13 |
J |
24 |
/ |
35 |
↓ |
50 |
Tab |
14 |
K |
25 |
Shift(右) |
36 |
PgDn |
51 |
Y |
15 |
L |
26 |
Prtsc |
37 |
Ins |
52 |
U |
16 |
; |
27 |
Alt |
38 |
Del |
53 |
I |
17 |
‘ |
28 |
Space |
39 |
|
|
O |
18 |
` |
29 |
CapsLock |
3A |
|
|
P |
19 |
Shift(左) |
2A |
F1~F10 |
3B~44 |
|
|
鍵盤的輸入到達60H 端口時,相關的芯片就會向CPU 發出中斷類型碼爲9 的可屏蔽中斷信息。
CPU檢測到該中斷信息後,若是IF=1,則響應中斷,引起中斷過程,轉去執行int 9中斷例程。
BIOS 提供了int 9中斷例程,用來進行基木的鍵盤輸入處理,主要的工做以下:
(1)讀出60H 端口中的掃描碼;
(2)若是是字符鍵的掃描碼,將該掃描碼和它所對應的字符碼(即ASCII碼)送入內存中的BIOS 鍵盤緩衝區;若是是控制鍵(好比Ctrl )和切換鍵(好比CapsLock)的掃描碼,則將其轉變爲狀態字節(用二進制位記錄控制鍵和切換鍵狀態的字節)寫入內存中存儲狀態字節的單元。
(3)對鍵盤系統進行相關的控制,好比說,向相關芯片發出應答信息。
BIOS鍵盤緩衝區是系統啓動後,BIOS用於存放int 9 中斷例程所接收的鍵盤輸入的內存區。該內存區能夠存儲15 個鍵盤輸入,由於int 9 中斷例程除了接收掃描碼外,還要產生和掃描碼對應的字符碼,因此在BIOS鍵盤緩衝區中,一個鍵盤輸入用一個字單元存放,高位字節存放掃描碼,低位字節存放字符碼。在內存的0040:17 字節單元中存儲鍵盤狀態字節,該字節記錄了控制鍵和切換鍵的狀態。鍵盤狀態字節各位記錄的信息以下:
0:右shift狀態,置1表示按下右shift鍵;
1:左shift狀態,置1表示按下左shift鍵;
2:Ctrl狀態,置1表示按下Ctrl鍵;
3:Alt狀態,置1表示按下Alt鍵;
4:ScrollLock狀態,置1表示按下ScrollLock鍵,Scroll指示燈亮;
5:NumLock狀態,置1表示小鍵盤輸入的是數字;
6:CapsLock狀態,置1表示輸入大寫字母;
7:Insert狀態,置1表示處於刪除狀態;
從上面的內容中,咱們能夠看出鍵盤輸入的處理過程:
(1)鍵盤產生掃描碼;
(2)掃描碼送入60h 端口;
(3)引起9 號中斷;
(4)CPU執行int 9中斷例程處理鍵盤輸入。
上面的過程當中,第(1)、(2)、(3)步都是由硬件系統完成的。咱們可以改變的只有int 9中斷處理程序。咱們能夠從新編寫int 9中斷例程,按照本身的意圖來處理鍵盤的輸入。
例如:
程序功能:編寫程序在屏幕中間顯示「a」~「z」,並可讓人看清,這個任務比較好實現。
(1)在b800:[ 160*12+40*2]處存入a的ASCII碼、(2)在循環中使用一個100000000000H次的循環空轉達到延遲效果、(3)按鍵盤引起int9中斷改變顏色
那麼如何實現,按下Esc 鍵後,改變顯示的顏色呢?
鍵盤輸入到達60h 端口後,就會引起9號中斷,CPU 則轉去執行int 9中斷例程。
咱們能夠編寫int 9中斷例程,功能以下:
(1)從60h 端口讀出鍵盤的輸入;
(2)調用BIOS 的int 9 中斷例程,處理其餘硬件細節;
(3)判斷是否爲Esc的掃描碼,若是是,改變顯示的顏色後返回;若是不是則直接返回。
咱們對這些功能的實現一一進行分析
一、從端口60h讀出鍵盤的輸入使用:in al,60h
二、調用BIOS的int 9中斷例程
有一點要注意的是,咱們寫的中斷處理程序要成爲新的int 9中斷例程,主程序必需要將中斷向量表中的int 9中斷例程的入口地址改成咱們寫的中斷處理程序的入口地址。那麼在新的中斷處理程序中調用原來的int 9中斷例程時,中斷向量表中的int 9中斷例程的入口地址卻不是原來的int 9 中斷例程的地址。因此咱們不能使用int 指令直接調用。要在咱們寫的新中斷例程中調用原來的中斷例程,就必須在將中斷向量表中的中斷例程的入口地址改成新地址以前,將原來的入口地址保存起來。這樣,在須要調用的時候,咱們才能找到原來的中斷例程的入口。對於咱們如今的問題,假設咱們將原來int 9中斷例程的偏移地址和段地址保存在ds:[0]和ds:[2]單元中。那麼咱們在須要調用原來的int 9中斷例程時候,就能夠在ds:[0]、ds:[2] 單元中找到它的入口地址。那麼,有了入口地址後,咱們如何進行調用呢?
固然不能使用指令int 9來調用。咱們能夠用別的指令來對int指令進行一些模擬,從而實現對中斷例程的調用。咱們來看,int 指令在執行的時候,CPU 進行下面的工做:
(1)取中斷類型碼n;
(2)標誌寄存器入棧;
(3)IF=0,TF=0;
(4)CS 、IP 入棧;
(5)設置(IP)=(n*4),(CS=(n*4+2)。
取中斷類型碼是爲了定位中斷例程的入口地址,在咱們的問題中,中斷例程的入口地址已經知道。因此,咱們用別的指令模擬int指令時候,不須要作第(1)步。在假設要調用的中斷例程的入口地址在ds:0和ds:2單元中的前提下,咱們將int 過程用下面幾步模擬:
(1)標誌寄存器入棧;
(2)IF=0,TF=0;
(3)CS、IP入棧;
(4)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
能夠注意到第(3)、(4)步和call dword ptr ds:[0]的功能同樣。call dword ptr ds:[0]的功能也是:(1)CS 、IP 入棧;(2)(IP)=((ds)*16+0),(CS)=((ds)*16+2)。
說明:若是還有疑問,複習10.6節的內容。
因此int 過程的模擬過程變爲:
(1)標誌寄存器入棧;
(2)IF=0,TF=0;
(3)call dword ptr ds:[0]
對於(1),可用pushf來實現。
對於(2),可用and和popf實現,以下面的指令實現。
實現IF=0,TF=0步驟:
pushf
pop ax
and ah,11111100b ;IF和OF爲標誌寄存器的第9位和第8位
push ax
popf
這樣,模擬int指令的調用功能,調用入口地址在ds:0、ds:2中的中斷例程的程序以下
pushf ;標誌寄存器入棧
pushf;實現IF=0,TF=0的功能
pop ax
and ah,11111100b ;IF和OF爲標誌寄存器的第9位和第8位
push ax
popf ;IF=0、TF=0
call dword ptr ds:[0];call功能: ①CS、IP入棧,②;(IP)=((ds)*16+0),③;(CS)=((ds)*16+2)
三、若是是Esc鍵的掃描碼,改變顯示的顏色後返回,如何改變顯示的顏色?
顯示的位置是屏幕的中問,即第12行40列,顯存中的偏移地址爲:160*12+40* 2。因此字符的ASCII碼要送入b800:160*12+40*2處。而b800:160*12+40*2+1處是字符的屬性,咱們只要改變此處的數據就能夠改變在b800:160*12+40*2處顯示的字符的顏色了。
該程序的最後一個問題是,要在程序返回前,將中斷向量表中的ini 9中斷例程的入口地址恢復爲原來的地址。不然程序返回後,別的程序將沒法使用鍵盤。
注意,本章中全部關於鍵盤的程序,因要直接訪問真實的硬件,則必須在DOS實模式下運行。在Windows 2000 的DOS 方式下運行,會出現一些和硬件工做原理不符合的現象。
程序完整代碼可參考博客:http://www.cnblogs.com/mq0036/p/5150801.html
開發int9中斷例程架構:
①在主程序中把原來的int9的原始程序入口保存到data段中,並把本身寫的int9中斷例程入口地址替換到中斷向量表的9號中斷地址,對應的是IP是0: [9*4]和CS是0[9*4+2]
②等待外部中斷自動調用int9
③程序運行完後還原int9原來的中斷例程入口
在int9中斷例程內部結構
1.保存用到的通用寄存器
2.接收60h端口的數據
3.修改IF和TF的值爲0
4.處理數據
5.使用call模擬調用int9的系統中斷例程
6.還原通用寄存器
說明:因爲中斷例程使用的是iret返回,而iret的過程是①從棧中還原IP和CS,②從棧中還原寄存器狀態;這裏使用了call的遠跳轉(地址在data段中),而用call過程是先把CS和IP保存進棧,跳轉到指定地址執行完再經過retf返回,調用完成後再從棧中還原IP和CS;而這裏咱們調用的是中斷例程,是用iret返回的,retf和iret返回的IP和CS順序相同,而iret比retf多一步還原寄存器狀態,因此咱們要構造供iret的返回的棧數據:就是在call前先保存寄存器狀態,而後就可使用iret的形式還原程序的IP,CS,標誌寄存器。咱們只要在本身編寫的中斷例程中處理完本身的數據後再調用BIOS 的int 9中斷例程就能夠了。
第十六章 直接定址表
咱們一直使用標號在咱們的代碼段中,例如標記循環或子程序等,例如:
S0:
………..
Loop s0
咱們還可使用一種標號,這種標號不但表示內存單元的地址,還表示了內存單元的長度,即表示在此標號處的單元,是一個字節單元,仍是字單元,仍是雙字單元。
在段中使用的標號a、b後面沒有「:」,它們是同時描述內存地址和單元長度的標號。
對於下面的程序片斷,在code中定義的a db 1,2,3,4,5,6,7,8 :
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start :
mov cx,8
……
指令:mov al,a [si] 至關於:mov al,cs:0[si]
指令:mov al,a[3] 至關於:mov al,cs:0[3]
指令:mov al,a[bx+si+3] 至關於:mov al,cs:0[bx+si+3]
注意,若是想在代碼段中,直接用數據標號訪問數據,則須要用僞指令assume 將標號所在的段和一個段寄存器聯繫起來。不然編譯器在編譯的時候,沒法肯定標號的段地址在哪個寄存器中。固然,這種聯繫是編譯器須要的,但絕對不是說,咱們由於編譯器的工做須要,用assume 指令將段寄存器和某個段相聯繫,段寄存器中就會真的存放該段的地址。咱們在程序中還要使用指令對段寄存器進行設置。由於這些實際編譯出的指令,都默認所訪問單元的段地址在ds中,而實際要訪問的段爲data,因此,若要訪問正確,在這些指令執行前,ds 中必須爲data 段的段地址。
咱們將這種標號稱爲數據標號。
1)它標記了存儲數據的單元的地址和長度。
2)它不一樣於僅僅表示地址的地址標號。
咱們能夠將標號看成數據來定義,此時,編譯器將標號所表示的地址看成數據的值。
好比:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a,b
data ends
數據標號c處存儲的兩個字型數據爲標號a、b 的偏移地址。
c dw a,b 至關於:c dw offset a, offset b
再好比:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends
數據標號c處存儲的兩個雙字型數據爲標號a的偏移地址和段地址、標號b 的偏移地址和段地址。
c dd a,b 至關於: c dw offset a, seg a, offset b, seg b
seg操做符功能:取得某一標號的段地址。
咱們將經過給出的數據進行計算或比較而獲得結果的問題,轉化爲用給出的數據做爲查表的依據,經過查表獲得結果的問題。
例如:根據字節單元的值,輸出16進制的字符
咱們知道4位二進制能夠用一位十六進制表示,其對應關係以下:
0000 |
0001 |
0010 |
0011 |
0100 |
0101 |
0110 |
0111 |
1000 |
1001 |
1010 |
1011 |
1100 |
1101 |
1110 |
1111 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
A |
B |
C |
D |
E |
F |
使用咱們在data段中定義table db ‘0123456789ABCDEF’,只要根據對應的數值查找table中對應的偏移就能夠找到對應的字符的數據。具體的查表方法,是用查表的依據數據,直接計算出所要查找的元素在表中的位置。像這種能夠經過依據數據,直接計算出所要找的元素的位置的表,咱們稱其爲:直接定址表。
咱們能夠在直接定址表中存儲子程序的地址,從而方便地實現不一樣子程序的調用。
例如:實現一個子程序setscreen ,爲顯示輸出提供以下功能:
(1)清屏。
(2)設置前景色。
(3)設置背景色。
(4)向上滾動一行
各個子程序入口參數說明:
1)用ah 寄存器傳遞功能號:
0 表示清屏,
1表示設置前景色,
2 表示設置背景色,
3 表示向上滾動一行;
2)對於二、3號功能,用al傳送顏色值,
咱們討論一下各類功能如何實現:
①清屏:將顯存中當前屏幕中的字符設爲空格符;
②設置前景色:設置顯存中當前屏幕中處於奇地址的屬性字節的第0、一、2位;
③設置背景色:設置顯存中當前屏幕中處於奇地址的屬性字節的第四、五、6位;
④向上滾動一行:依次將第n+1行的內容複製到第n行處:最後一行爲空。
使用根據功能號查找地址表的方法,程序的結構清晰,便於擴充。若是加入一個新的功能子程序,那麼只須要在地址表中加入它的入口地址就能夠了。
具體程序代碼前去www.cnblogs.com/mq0036/p/5163181.html查看
第十七章 使用BIOS進行鍵盤輸入和磁盤讀寫
咱們已經講過,鍵盤輸入將引起9 號中斷,BIOS 提供了int 9 中斷例程。
CPU 在9 號中斷髮生後,執行int 9中斷例程,從60h 端口讀出掃描碼,並將其轉化爲相應的ASCII 碼或狀態信息,存儲在內存的指定空間(鍵盤緩衝區或狀態字節)中。鍵盤緩衝區中有16 個字單元,能夠存儲15個按鍵的掃描碼和對應的入ASCII 碼。
BIOS提供了int 16h 中斷例程供程序員調用。
int 16h 中斷例程中包含的一個最重要的功能是從鍵盤緩衝區中讀取一個鍵盤輸入,該功能的編號爲0。下面的指令從鍵盤緩衝區中讀取一個鍵盤輸入,而且將其從緩衝區中刪除:
mov ah,0
int 16h
結果:
(ah)=掃描碼,
(al)=ASCII碼。
int 16h 中斷例程的0 號功能,進行以下的工做:
(1)檢測鍵盤緩衝區中是否有數據;
(2)沒有則繼續作第1 步;
(3)讀取緩衝區第一個字單元中的鍵盤輸入;
(4)將讀取的掃描碼送入ah,ASCII 碼送入al;
(5)將己讀取的鍵盤輸入從緩衝區中刪除。
可見,B1OS 的int 9 中斷例程和int 16h 中斷例程是一對相互配合的程序,int 9 中斷例程向鍵盤緩衝區中寫入,int 16h 中斷例程從緩衝區中讀出。它們寫入和讀出的時機不一樣,int 9 中斷例程在有鍵按下的時候向鍵盤緩衝區中寫入數據;而int 16h 中斷例程是在應用程序對其進行調用的時候,將數據從鍵盤緩衝區中讀出。
在咱們的課程中,僅在邏輯結構的基礎上,討論BIOS鍵盤緩衝區的讀寫問題。其實鍵盤緩衝區是用環形隊列結構管理的內存區,但咱們不對隊列和環形隊列的實現進行討論。
經常使用的3.5英寸軟盤的結構:
分爲上下兩面,每面有80個磁道,每一個磁道又分爲18個扇區,每一個扇區的大小爲512B。
總容量爲:2面×80磁道×18扇區×512B=1440KB≈1.44MB
磁盤的實際訪問由磁盤控制器進行,咱們能夠經過控制磁盤控制器來訪問磁盤。只能以扇區爲單位對磁盤進行讀寫。在讀寫扇區的時候,要給出面號、磁道號和扇區號。面號和磁道號從0開始,而扇區號從1開始。若是咱們經過直接控制磁盤控制器來訪問磁盤,則須要涉及許多硬件細節。BIOS提供了對扇區進行讀寫的中斷例程,這些中斷例程完成了許多複雜的和硬件相關的工做。咱們能夠經過調用BIOS中斷例程來訪問磁盤。
BIOS 提供的訪問磁盤的中斷例程爲int 13h 。以下:
1)讀取0面0道1扇區的內容到0:200:
mov ax,0
mov es,ax
mov bx,200h
mov al,1
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,2
int 13h
入口參數:
(ah)=int 13h的功能號(2表示讀扇區)
(al)=讀取的扇區數
(ch)=磁道號
(cl)=扇區號
(dh)=磁頭號(對於軟驅即面號,由於一個面用一個磁頭來讀寫)
(dl)=驅動器號
軟驅從0開始,0:軟驅A,1:軟驅B;
硬盤從80h開始,80h:硬盤C,81h:硬盤D。
es:bx指向接收此扇區讀入數據的內存區
返回參數:
操做成功:(ah)=0,(al)=讀入的扇區數
操做失敗:(ah)=出錯代碼
2)將0:200中的內容寫入0面0道1扇區:
mov ax,0
mov es,ax
mov bx,200h
mov al,1
mov ch,0
mov cl,1
mov dl,0
mov dh,0
mov ah,3
int 13h
入口參數:
(ah)=int 13h的功能號(3表示寫扇區)
(al)=寫入的扇區數
(ch)=磁道號
(cl)=扇區號
(dh)=磁頭號(面)
(dl)=驅動器號
軟驅從0開始,0:軟驅A,1:軟驅B;
硬盤從80h開始,80h:硬盤C,81h:硬盤D。
es:bx指向將寫入磁盤的數據
返回參數:
操做成功:(ah)=0,(al)=寫入的扇區數
操做失敗:(ah)=出錯代碼
注意:
下面咱們要使用int 13h 中斷例程對軟盤進行讀寫。直接向磁盤扇區寫入數據是很危險的,極可能覆蓋掉重要的數據。
若是向軟盤的0 面0 道1 扇區中寫入了數據,要使軟盤在現有的操做系統下可使用,必需要從新格式化。
①②③④⑤⑥⑦⑧⑨⑩
下面能夠學習:
羅雲斌—《win32彙編》
程序的測試代碼以及word版筆記下載地址:練習代碼和筆記