目錄
王爽《彙編語言》第四版 超級筆記
第15章 外中斷
15.1 接口晶片和端口
15.2 外中斷資訊
15.3 PC機鍵盤的處理過程
15.4 編寫int 9中斷例程
15.5 安裝新的int 9中斷例程
以前我們讨論的都是CPU對指令的執行。我們知道CPU在計算機系統中,除了能夠執行指令,進行運算以外,還應該能夠對外部裝置進行控制,接收它們的輸入,向它們進行輸出。
CPU除了有運算能力外,還要有I/O(Input/Output,輸入/輸出)能力。比如,我們按下鍵盤上的一個鍵,CPU最終要能夠處理這個鍵。在使用文本編輯器時,按下a鍵後,我們可以看到螢幕上出現“a”,是CPU将從鍵盤上輸入的鍵所對應的字元送到顯示器上的。
要及時處理外設的輸入,顯然需要解決兩個問題:
① 外設的輸入随時可能發生,CPU如何得知?
② CPU從何處得到外設的輸入?
這一章中,我們以鍵盤輸入為例,讨論這兩個問題。
PC系統的接口卡和主機闆上,裝有各種接口晶片。這些外設接口晶片的内部有若幹寄存器,CPU将這些寄存器當作端口來通路。
外設的輸入不直接送入記憶體和CPU,而是送入相關的接口晶片的端口中;CPU向外設的輸出也不是直接送入外設,而是先送入端口中,再由相關的晶片送到外設。
CPU還可以向外設輸出控制指令,而這些控制指令也是先送到相關晶片的端口中,然後再由相關的晶片根據指令對外設實施控制。
可見,CPU通過端口和外部裝置進行聯系。
現在我們知道了外設的輸入被存放在端口中,可是外設的輸入随時都有可能到達, CPU如何及時地知道,并進行處理呢?
更一般地講,就是外設随時都可能發生需要CPU及時處理的事件,CPU如何及時得知并進行處理?
CPU提供中斷機制來滿足這種需要。前面講過,當CPU的内部有需要處理的事情發生的時候,将産生中斷資訊,引發中斷過程。這種中斷資訊來自CPU的内部。
還有一種中斷資訊,來自于CPU外部,當CPU外部有需要處理的事情發生的時候,比如說外設的輸入到達,相關晶片将向CPU發出相應的中斷資訊。
CPU在執行完目前指令後,可以檢測到發送過來的中斷資訊,引發中斷過程,處理外設的輸入。
在PC系統中,外中斷源一共有以下兩類。
1、可屏蔽中斷
可屏蔽中斷是CPU可以不響應的外中斷。CPU是否響應可屏蔽中斷,要看标志寄存器的IF位的設定。當CPU檢測到可屏蔽中斷資訊時,如果IF=1,則CPU在執行完目前指令後響應中斷,引發中斷過程;如果IF=0,則不響應可屏蔽中斷。
我們回憶一下内中斷所引發的中斷過程:
(1)取中斷類型碼n;
(2)标志寄存器入棧,IF=0,TF=0;
(3)CS、IP入棧;
(4)(IP)=(n4),(CS)=(n4+2)
由此轉去執行中斷處理程式。
可屏蔽中斷所引發的中斷過程,除在第1步的實作上有所不同外,基本上和内中斷的中斷過程相同。
因為可屏蔽中斷資訊來自于CPU外部,中斷類型碼是通過資料總線送入CPU的;而内中斷的中斷類型碼是在CPU内部産生的。
現在我們可以解釋中斷過程中将IF置為0的原因了。将IF置0的原因就是,在進入中斷處理程式後,禁止其他的可屏蔽中斷。
如果在中斷處理程式中需要處理可屏蔽中斷,可以用指令将IF置1。8086CPU提供的設定IF的指令如下:
sti,設定 IF=1; cli,設定 IF=0。
2、不可屏蔽中斷
不可屏蔽中斷是CPU必須響應的外中斷。當CPU檢測到不可屏蔽中斷資訊時,則在執行完目前指令後,立即響應引發中斷過程。
對于8086CPU,不可屏蔽中斷的中斷類型碼固定為2,是以中斷過程中不需要取中斷類型碼。則不可屏蔽中斷的中斷過程為:
(1)标志寄存器入棧,IF=0,TF=0;
(2)CS、IP入棧;
(3)(IP)=(8),(CS)=(0AH)。
幾乎所有由外設引發的外中斷,都是可屏蔽中斷。當外設有需要處理的事件(比如說鍵盤輸入)發生時,相關晶片向CPU發出可屏蔽中斷資訊。
不可屏蔽中斷是在系統中有必須處理的緊急情況發生時用來通知CPU的中斷資訊。在我們的課程中主要讨論可屏蔽中斷。
下面我們看一下鍵盤輸入的處理過程,并以此來體會一下pc機處理外設輸入的基本方法。
1. 鍵盤輸入
鍵盤上的每一個鍵相當于一個開關,鍵盤中有一個晶片對鍵盤上的每一個鍵的開關狀态進行掃描。
按下一個鍵時,開關接通該晶片就産生一個掃描碼,掃描碼說明了按下的鍵在鍵盤上的位置。
掃描碼被送入主機闆上的相關接口晶片的寄存器中,該寄存器的端口位址為60h。
松開按下的鍵時,也産生一個掃描碼,掃描碼說明了松開的鍵在鍵盤上的位置。松開按鍵時産生的掃描碼也被送入60h端口中。
一般将按下一個鍵時産生的掃描碼稱為通碼,松開一個鍵産生的掃描碼稱為斷碼。掃描碼長度為一個位元組,通碼的第7位為0,斷碼的第7位為1,即:
斷碼=通碼+80h
比如,g鍵的通碼為22h,斷碼為a2h。
表15.1是鍵盤上部分鍵的掃描碼,隻列出通碼。斷碼=通碼+80h。

2. 引發9号中斷
鍵盤的輸入到達60h端口時,相關的晶片就會向CPU發出中斷類型碼為9的可屏蔽中斷資訊。
CPU檢測到該中斷資訊後,如果IF=1,則響應中斷,引發中斷過程轉去執行int9中斷例程。
3. 執行int9中斷例程
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表示Scroll訓示燈亮;
5:NumLock狀态,置1表示小鍵盤輸入的是數字;
6:CapsLock狀态,置1表示輸入大寫字母;
7:Insert狀态,置1表示處于删除态。
從上面的内容中,可以看出鍵盤輸入的處理過程:
①鍵盤産生掃描碼;
②掃描碼送入60h端口;
③引發9号中斷;
④CPU執行int 9中斷例程處理鍵盤輸入。
上面的過程中,第1、2、3步都是由硬體系統完成的。我們能夠改變的隻有int 9中斷處理程式。
我們可以重新編寫int 9中斷例程,按照自己的意圖來處理鍵盤的輸入。但是在課程中,我們不準備完整地編寫一個鍵盤中斷的處理程式,因為要涉及一些硬體細節,而這些内容脫離了我們的内容主線。
但是我們卻還要編寫新的鍵盤中斷處理程式,來進行一些特殊的工作,那麼這些硬體細節如何處理呢?
這點比較簡單,因為BIOS提供的int 9中斷例程已經對這些硬體細節進行了處理。我們隻要在自己編寫的中斷例程中調用BIOS的int 9中斷例程就可以了。
程式設計:在螢幕中間依次顯示"a"~"z",并可以讓人看清。在顯示的過程中,按下Esc鍵後改變顯示的顔色。
我們先來看一下如何依次顯示"a"~"z"。
在上面的程式的執行過程中,我們無法看清螢幕上的顯示。因為一個字母剛顯示到螢幕上,CPU執行幾條指令後,就又變成了另一個字母,字母之間切換得太快無法看清。
應該在每顯示一個字母後,延時一段時間讓人看清後,再顯示下一個字母。那麼如何延時呢?
我們讓CPU執行一段時間的空循環。因為現在CPU的速度都非常快,是以循環的次數一定要大,用兩個16位寄存器來存放32位的循環次數。如下:
上面的程式,循環100000h次。我們可以将循環延時的程式段寫為一個子程式。 現在我們的程式如下:
顯示“a”~“z”,并可以讓人看清,這個任務己經實作。那麼如何實作,按下Esc鍵後改變顯示的顔色呢?
鍵盤輸入到達60h端口後,就會引發9号中斷,CPU則轉去執行int 9中斷例程。我們可以編寫int 9中斷例程,功能如下。
(1)從60h端口讀出鍵盤的輸入;
(2)調用BIOS的int 9中斷例程,處理其他硬體細節;
(3)判斷是否為Esc的掃描碼,如果是改變顯示的顔色後傳回;如果不是則直接傳回。
下面對這些功能的實作一一進行分析。
1. 從端口 60h讀出鍵盤的輸入
in al,60h
2. 調用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進行下面的工作。
(2)标志寄存器入棧;
(3)IF=0,TF=0;
(4)CS、IP入棧;
(5)(IP)=(n4), (CS)=(n4+2)。
取中斷類型碼是為了定位中斷例程的入口位址,在我們的問題中,中斷例程的入口位址己經知道。是以我們用别的指令模拟int指令時,不需要做第(1)步。
在假設要調用的中斷例程的入口位址在ds:0和ds:2單元中的前提下,我們将int過程用下面幾步模拟。
(1)标志寄存器入棧;
(2)IF=0,TF=0;
(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)。
是以int過程的模拟過程變為:
(3)call dword ptr ds:[0]。
對于(1),可用pushf實作;
對于(2),可用下面的指令實作:
pushf pop ax and ah,11111100b ;IF和TF為标志寄存器的第9位和第8位 push ax popf
則模拟int指令的調用功能,調用入口位址在ds:0、ds:2中的中斷例程的程式為:
pushf ;标志寄存器入棧 and ah,11111100b popf ;IF=0, TF=0 call dword ptr ds:[0] ;CS、IP入棧;(IP)=((ds)x16+0),(CS)=((ds)x16+2)
3. 如果是Esc的掃描碼,改變顯示的顔色後傳回
如何改變顯示的顔色?
顯示的位置是螢幕的中間,即第12行40列,顯存中的偏移位址為:16012+402。
是以字元的ASCII碼要送入段位址b800h,偏移位址16012+402處。而段位址b800h,偏移位址16012+402+1處是字元的屬性,隻要改變此處的資料就可以改變在段位址b800h,偏移位址16012+402處顯示的字元的顔色了。
該程式的最後一個問題是,要在程式傳回前,将中斷向量表中的int 9中斷例程的入口位址恢複為原來的位址。否則程式傳回後,别的程式将無法使用鍵盤。
經過分析,完整的程式如下。
注意,本章中所有關于鍵盤的程式,因要直接通路真實的硬體,則必須在DOS實模式下運作。在Windows 2000的DOS方式下運作,會岀現一些和硬體工作原理不符合的現象。
我們安裝一個新的int 9中斷例程,使得原int 9中斷例程的功能得到擴充。
任務:安裝一個新的int 9中斷例程。
功能:在DOS下,按F1鍵後改變目前螢幕的顯示顔色,其他的鍵照常處理。
我們進行一下分析。
(1)改變螢幕的顯示顔色
改變從B8000H開始的4000個位元組中的所有奇位址單元中的内容,目前螢幕的顯示顔色即發生改變。程式如下:
(2)其他鍵照常處理
可以調用原int 9中斷處理程式,來處理其他的鍵盤輸入。
(3)原int 9中斷例程入口位址的儲存
因為在編寫的新int 9中斷例程中要調用原int 9中斷例程,是以要儲存原int 9中斷例程的入口位址。儲存在哪裡?
顯然不能儲存在安裝程式中,因為安裝程式傳回後位址将丢失。我們将位址儲存在0:200單元處。
(4)新int 9中斷例程的安裝
這個問題在前面己經詳細讨論過。我們可将新的int 9中斷例程安裝在0:204處。
完整的程式如下。
這一章中,我們通過對鍵盤輸入的處理,講解了CPU對外設輸入的通常處理方法。即:
(1) 外設的輸入送入端口;
(2) 向CPU發出外中斷(可屏蔽中斷)資訊;
(3) CPU檢測到可屏蔽中斷資訊,如果IF=1,CPU在執行完目前指令後響應中斷,執行相應的中斷例程;
(4) 可在中斷例程中實作對外設輸入的處理。
端口和中斷機制,是CPU進行I/O的基礎。
指令系統總結 我們對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、ch、sti、nop、clc、cmc、stc、hit、wait、esc、lock等都是處理機控制指令。 6. 串處理指令 這些指令對記憶體中的批量資料進行處理,比如,movsb、movsw、cmps、scas、lods、stos等。若要使用這些指令友善地進行批量資料的處理,則需要和rep、repe、repne等字首指令配合使用。