天天看點

[彙編]《彙編語言》第15章 外中斷

目錄

王爽《彙編語言》第四版 超級筆記

第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。

[彙編]《彙編語言》第15章 外中斷
[彙編]《彙編語言》第15章 外中斷

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等字首指令配合使用。

繼續閱讀