應用和硬體的關係
我們作為程式設計師一般很少直接操控硬體,我們一般通過 C、Java 等高階語言編寫的程式起到間接控制硬體的作用。所以大家很少直接接觸到硬體的指令,硬體的控制是由 Windows 作業系統
全權負責的。
你一定猜到我要說什麼了,沒錯,我會說但是,任何事情沒有絕對性,環境的不同會造成結果的偏差。雖然程式設計師沒法直接控制硬體,並且 Windows 遮蔽了控制硬體的細節,但是 Windows 卻為你開放了 系統呼叫
功能來實現對硬體的控制。在 Windows 中,系統呼叫稱為 API
,API 就是應用呼叫的函式,這些函式的實體被存放在 DLL
檔案中。
下面我們來看一個通過系統呼叫來間接控制硬體的例項
假如要在視窗中顯示字串,就可以使用 Windows API 中的 TextOut
函式。TextOut 函式的語法(C 語言)如下
BOOL TextOut{
HDC hdc, // 裝置描述表的控制程式碼
int nXStart, // 顯示字串的 x 座標
int nYStart, // 顯示字串的 y 座標
LPCTSTR lpString, // 指向字串的指標
int cbString // 字串的文字數
}
複製程式碼
那麼,在處理 TextOut 函式的內容時,Windows 做了些什麼呢?從結果來看,Windows 直接控制了作為硬體的顯示器。但 Windows 本身也是軟體,由此可見,Windows 應該向 CPU 傳遞了某種指令,從而通過軟體控制了硬體。
Windows 提供的 TextOut 函式 API 可以向視窗和印表機輸出字元。C 語言提供的 printf 函式,是用來在命令提示符中顯示字串的函式。使用 printf 函式是無法向印表機輸出字元的。
支援硬體輸入輸出的 IN 指令和 OUT 指令
Windows 控制硬體藉助的是輸入和輸出指令。其中具有代表性的兩個輸入輸出指令就是 IN
和 OUT
指令。這些指令也是組合語言的助記符。
可以通過 IN 和 OUT 指令來實現對資料的讀入和輸出,如下圖所示
也就是說,IN 指令通過指定的埠號輸入資料,OUT 指令則是把 CPU 暫存器中儲存的資料輸出到指定埠號的埠。
那麼這個埠號
和 埠
是什麼呢?你感覺它像不像港口一樣?通過標註哪個港口然後進行貨物的運送和運出?
下面我們來看一下官方是如何定義埠號和埠的
還記得計算機組成原理中計算機的五大組成部分嗎,再來回顧一下:運算器、控制器、儲存器、輸入裝置和輸出裝置。我們今天不談前三個,就說說後面兩個輸入裝置和輸出裝置,這兩個與我們本節主題息息相關。
那麼問題來了,IO裝置如何實現輸入和輸出的呢?計算機主機中,附帶了用來連線顯示器以及鍵盤等外圍裝置的聯結器
。 而聯結器的內部,都連線有用來交換計算機主機同外圍裝置之間電流特性的 IC。如果 IC 你不明白是什麼的話,可以參考作者的文章 程式設計師需要了解的硬核知識之記憶體 進行了解。這些 IC 統稱為 IO 控制器
。
IO 是 Input/Output 的縮寫。顯示器、鍵盤等外圍裝置都有各自專用的 I/O 控制器。I/O 控制器中有用於臨時儲存輸入輸出資料的記憶體。這個記憶體就是 埠(port)
。埠你就可以把它理解為我們上述說的 港口。IO 控制器內部的記憶體,也被稱為暫存器
,不要慌,這個暫存器和記憶體中的暫存器不一樣。CPU 記憶體的暫存器是用於進行資料運算處理的,而IO中的暫存器是用於臨時儲存資料的。
在 I/O 裝置內部的 IC 中,有多個埠。由於計算機中連線著很多外圍裝置,因此也就有很多 I/O 控制器。當然也會有多個埠,一個 I/O 控制器可以控制多個裝置,不僅僅只能控制一個。各埠之間通過 埠號
進行區分。
埠號也被稱為 I/O地址
。IN 指令和 OUT 指令在埠號指定的埠和 CPU 之間進行資料的輸入和輸出。這跟通過記憶體的地址來對記憶體進行讀寫是一樣的道理。
測試輸入和輸出程式
首先讓我們利用 IN 指令和 OUT 指令,來進行一個直接控制硬體的實驗。假如試驗的目的是讓一個計算機內建的喇叭(蜂鳴器)發出聲音。蜂鳴器封裝在計算機內部,但它也是外圍裝置的一種。
用匯編語言比較繁瑣,這次我們用 C 語言來實現。在大部分 C 語言的處理(編譯器的種類)中,只要使用 _asm{ 和 }
括起來,就可以在其中記述助記符。也就是說,採用這種方式就能夠使用 C 語言和組合語言混合的原始碼。
在 AT 相容機中,蜂鳴器的預設埠號是 61H ,末尾的 H 表示的是十六進位制數的意思。用 IN 指令通過該埠號輸入資料,並將資料的低2位設定為 ON,然後再通過該埠號用 OUT 指令輸出資料,這時蜂鳴器就會發出聲音。同樣的方法,將資料的低2位設定為 OFF 並輸出後,蜂鳴器就停止工作。
位設定為 ON 指的是將該位設定為1,位設定為 OFF 指的是將該位設定為0 。把位設定為 ON,只需要把想要設定為 ON 的位設定為1,其他位設定為0後進行 OR 運算即可。由於這裡需要把低2位置為1,因此就是和 03H 進行 OR 運算。03H 用8為二進位制來表示的話是 00000011。由於即便高6位存在著具體意義。和0進行OR運算後也不會發生變化,因而就和 03H 進行 OR 運算。把位設定為 OFF,只需要把想要置 OFF 的位設定為0,其他位設定為1後進行 AND 運算即可。由於這裡需要把低2位設定為0,因此就要和 FCH 進行 AND 運算。在原始碼中,FCH 是用 0FCH 來記述的。在前面加 0 是組合語言的規定,表示的是以 A - F 這些字元開頭的十六進位制數是數值的意思。0FCH 用8位二進位制數來表示的話是 11111100。由於即便高6位存在著具體意義,和1進行 AND 運算後也不會產生變化,因而就是同 0FCH 進行 OR 運算。
void main(){
// 計數器
int i;
// 蜂鳴器發聲
_asm{
IN EAX, 61H
OR EAX, 03H
OUT 61H, EAX
}
// 等待一段時間
for(i = 0;i < 1000000;i++);
// 蜂鳴器停止發生
_asm{
IN EAX, 61H
AND EAX, 0FCH
OUT 61H, EAX
}
}
複製程式碼
我們對上面的程式碼進行說明,main 是 C 語言程式起始位置的函式。在該函式中,有兩個用 _asm{}
圍起來的部分,它們中間有一個使用 for 迴圈的空迴圈
首先是蜂鳴器發聲的部分,通過 IN EAX,61H(助記符不區分大小寫)指令,把埠 61H 的資料儲存到 CPU 的 EAX 暫存器中。接下來,通過 OR EAX,03H 指令,把 EAX 暫存器的低2位設定成 ON。最後,通過 OUT 61H,EAX 指令,把 EAX 暫存器的內容輸出到61埠。使蜂鳴器開始發音。雖然 EAX 暫存器的長度是 32 位,不過由於蜂鳴器埠是8位,所以只需對下8位進行OR運算和AND運算就可以正常工作了。
其次是一個重複100次的空迴圈,主要是為了在蜂鳴器開始發音和停止發音之間稍微加上一些時間間隔。因為現在計算機器的執行速度非常快,哪怕是 100 萬次迴圈,也幾乎是瞬時間完成的。
然後是用來控制器蜂鳴器停止發聲的部分。首先,通過 IN EAX,61H 指令,把埠 61H 的資料儲存到 CPU 的 EAX 暫存器中。接下來,通過 AND EAX,0FCH 指令,把 EAX 暫存器的低2位設定為 OFF。最後,通過 OUT 61H,EAX 指令,把暫存器的 EAX 內容輸出到61號埠,使蜂鳴器停止發音。
外圍裝置的中斷請求
IRQ(Interrupt Request)
代表的就是中斷請求。IRQ 用來暫停當前正在執行的程式,並跳轉到其他程式執行的必要機制。該機制被稱為 處理中斷
。中斷處理在硬體控制中擔當著重要的角色。因為如果沒有中斷處理,就有可能無法順暢進行處理的情況。
從中斷處理開始到請求中斷的程式(中斷處理程式)執行結束之前,被中斷的程式(主程式)的處理是停止的。這種情況就類似於在處理文件的過程中有電話打進來,電話就相當於是中斷處理。假如沒有中斷處理的發生,就必須等到文件處理完成後才能夠接聽電話。由此可見,中斷處理有著巨大的價值,就像是接聽完電話後會返回原來的文件作業一樣,中斷程式處理完成後,也會返回到主程式中繼續。
實施中斷請求的是連線外圍裝置的 I/O 控制器,負責實施中斷處理的是 CPU,外圍裝置的中斷請求會使用不同於 I/O 埠的其他編號,該編號稱為中斷編號
。在控制皮膚中檢視軟盤驅動器的屬性時,IRQ處現實的數值是 06,表示的就是用06號來識別軟盤驅動器發出的請求。還有就是作業系統以及 BIOS
則會提供響應中斷編號的中斷處理程式。
BIOS(Basic Input Output System): 位於計算機主機板或者擴張卡上內建的 ROM 中,裡面記錄了用來控制外圍裝置的程式和資料。
假如有多個外圍裝置進行中斷請求的話, CPU 需要做出選擇進行處理,為此,我們可以在 I/O 控制器和 CPU 中間加入名為中斷控制器
的 IC 來進行緩衝。中斷控制器會把從多個外圍裝置發出的中斷請求有序的傳遞給 CPU。中斷控制器的功能相當於就是緩衝。下面是中斷控制器功能的示意圖
CPU 在接受到中斷請求後,會把當前正在執行的任務中斷,並切換到中斷處理程式。中斷處理程式的第一步處理,就是把 CPU 所有暫存器的數值儲存到記憶體的棧中。在中斷處理程式中完成外圍裝置的輸入和輸出後,把棧中儲存的數值還原到 CPU 暫存器中,然後再繼續進行對主程式的處理。
假如 CPU 暫存器數值還沒有還原的話,就會影響到主程式的執行,甚至還有可能會使程式意外停止或發生執行時異常。這是因為主程式在執行過程中,會用到 CPU 暫存器進行處理,這時候如果突然插入其他程式的執行結果,此時 CPU 必然會受到影響。所以,在處理完中斷請求後,各個暫存器的值必須要還原。只要暫存器的值保持不變,主程式就可以像沒有發生過任何事情一樣繼續處理。
用中斷來實現實時處理
中斷是指計算機執行過程中,出現某些意外情況需主機干預時,機器能自動停止正在執行的程式並轉入處理新情況的程式,處理完畢後又返回原被暫停的程式繼續執行。
在程式的執行過程中,幾乎無時無刻都會發生中斷,其原因就是為了實時處理外部輸入的資料,雖然程式也可以在不會中斷的基礎上處理外部資料,但是那種情況下,主程式就會頻繁的檢查外圍裝置是否會有資料輸入。由於外圍裝置會有很多個,因此有必要按照順序來調查。按照順序檢查多個外圍裝置的狀態稱為 輪詢
。對於計算機來說,這種採用輪詢的方式不是很合理,如果你正在檢查是否有滑鼠輸入,這時候發生了鍵盤輸入該如何處理呢?結果必定會導致文字的實時處理效率。所以即時的中斷能夠提高程式的執行效率。
上面只是中斷的一種好處,下面彙總一下利用中斷能夠帶來的正面影響
- 提高計算機系統效率。計算機系統中處理機的工作速度遠高於外圍裝置的工作速度。通過中斷可以協調它們之間的工作。當外圍裝置需要與處理機交換資訊時,由外圍裝置向處理機發出中斷請求,處理機及時響應並作相應處理。不交換資訊時,處理機和外圍裝置處於各自獨立的並行工作狀態。
- 維持系統可靠正常工作。現代計算機中,程式設計師不能直接干預和操縱機器,必須通過中斷系統向作業系統發出請求,由作業系統來實現人為干預。主儲存器中往往有多道程式和各自的儲存空間。在程式執行過程中,如出現越界訪問,有可能引起程式混亂或相互破壞資訊。為避免這類事件的發生,由儲存管理部件進行監測,一旦發生越界訪問,向處理機發出中斷請求,處理機立即採取保護措施。
- 滿足實時處理要求。在實時系統中,各種監測和控制裝置隨機地向處理機發出中斷請求,處理機隨時響應並進行處理。
- 提供故障現場處理手段。處理機中設有各種故障檢測和錯誤診斷的部件,一旦發現故障或錯誤,立即發出中斷請求,進行故障現場記錄和隔離,為進一步處理提供必要的依據。
利用 DMA 實現短時間內大量資料傳輸
上面我們介紹了 I/O 處理和中斷的關係,下面我們來介紹一下另外一個機制,這個機制就是 DMA(Direct Memory Access)
。DMA 是指在不通過 CPU 的情況下,外圍裝置直接和主存進行資料傳輸。磁碟等硬體裝置都用到了 DMA 機制,通過 DMA,大量資料可以在短時間內實現傳輸,之所以這麼快,是因為 CPU 作為中介的時間被節省了,下面是 DMA 的傳輸過程
I/O 埠號、IRQ、DMA 通道可以說是識別外圍裝置的3點組合。不過,IRQ、DMA 通道並不是所有外圍裝置都具備的。計算機主機通過軟體控制硬體時所需要的資訊的最低限,是外圍裝置的 I/O 埠號。IRQ 只對需要中斷處理的外圍裝置來說是必須的,DMA 通道則只對需要 DMA 機制的外圍裝置來說是必須的。假如多個外圍裝置都設定成相同的埠號、IRQ 和 DMA 通道的話,計算機就無法正常工作,會提示 裝置衝突
。
文字和圖片的顯示機制
你知道文字和圖片是如何顯示出來的嗎?事實上,如果用一句話來簡單的概括一下該機制,那就是顯示器中顯示的資訊一直儲存在某記憶體中。該記憶體稱為VRAM(Video RAM)
。在程式中,只要往 VRAM 中寫入資料,該資料就會在顯示器中顯示出來。實現該功能的程式,是由作業系統或者 BIOS 提供,並藉助中斷來進行處理。
在 MS-DOS
時代,對於大部分計算機來說,VRAM 都是主記憶體的一部分。在現代計算機中,顯示卡
等專用硬體中一般都配置有與主記憶體相獨立的 VRAM 和 GPU(Graphics Processing Unit),也叫做圖形處理器或者圖形晶片。這是因為,對經常描繪圖形的 windows 來說,數百兆的 VRAM 都是必需的。
用軟體來控制硬體聽起來好像很難,但實際上只是利用輸入輸出指令同外圍裝置進行輸入輸出而已。中斷處理是根據需要來使用的功能選項。DMA 則直接交給對應的外圍裝置即可。
雖然計算機領域新技術在不斷湧現,但是計算機所能處理的事情,始終只是對輸入的資料進行運算,並把結果輸出,這一點是永遠不會發生變化的。
文章參考:
《程式是怎樣跑起來的》
關注公眾號後臺回覆 191106 即可獲得《程式是怎樣跑起來的》電子書