程序員需要了解的硬核知識之控制硬件

應(yīng)用和硬件的關(guān)系
我們作為程序員一般很少直接操控硬件,我們一般通過 C、Java 等高級語言編寫的程序起到間接控制硬件的作用。所以大家很少直接接觸到硬件的指令,硬件的控制是由 Windows 操作系統(tǒng) 全權(quán)負責的。
你一定猜到我要說什么了,沒錯,我會說但是,任何事情沒有絕對性,環(huán)境的不同會造成結(jié)果的偏差。雖然程序員沒法直接控制硬件,并且 Windows 屏蔽了控制硬件的細節(jié),但是 Windows 卻為你開放了 系統(tǒng)調(diào)用功能來實現(xiàn)對硬件的控制。在 Windows 中,系統(tǒng)調(diào)用稱為 API,API 就是應(yīng)用調(diào)用的函數(shù),這些函數(shù)的實體被存放在 DLL 文件中。

下面我們來看一個通過系統(tǒng)調(diào)用來間接控制硬件的實例
假如要在窗口中顯示字符串,就可以使用 Windows API 中的 TextOut 函數(shù)。TextOut 函數(shù)的語法(C 語言)如下
BOOL TextOut{
HDC hdc, // 設(shè)備描述表的句柄
int nXStart, // 顯示字符串的 x 坐標
int nYStart, // 顯示字符串的 y 坐標
LPCTSTR lpString, // 指向字符串的指針
int cbString // 字符串的文字數(shù)
}
那么,在處理 TextOut 函數(shù)的內(nèi)容時,Windows 做了些什么呢?從結(jié)果來看,Windows 直接控制了作為硬件的顯示器。但 Windows 本身也是軟件,由此可見,Windows 應(yīng)該向 CPU 傳遞了某種指令,從而通過軟件控制了硬件。
Windows 提供的 TextOut 函數(shù) API 可以向窗口和打印機輸出字符。C 語言提供的 printf 函數(shù),是用來在命令提示符中顯示字符串的函數(shù)。使用 printf 函數(shù)是無法向打印機輸出字符的。
支持硬件輸入輸出的 IN 指令和 OUT 指令
Windows 控制硬件借助的是輸入和輸出指令。其中具有代表性的兩個輸入輸出指令就是 IN 和 OUT指令。這些指令也是匯編語言的助記符。
可以通過 IN 和 OUT 指令來實現(xiàn)對數(shù)據(jù)的讀入和輸出,如下圖所示

也就是說,IN 指令通過指定的端口號輸入數(shù)據(jù),OUT 指令則是把 CPU 寄存器中存儲的數(shù)據(jù)輸出到指定端口號的端口。
那么這個端口號 和 端口是什么呢?你感覺它像不像港口一樣?通過標注哪個港口然后進行貨物的運送和運出?
下面我們來看一下官方是如何定義端口號和端口的
還記得計算機組成原理中計算機的五大組成部分嗎,再來回顧一下:運算器、控制器、存儲器、輸入設(shè)備和輸出設(shè)備。我們今天不談前三個,就說說后面兩個輸入設(shè)備和輸出設(shè)備,這兩個與我們本節(jié)主題息息相關(guān)。
那么問題來了,IO設(shè)備如何實現(xiàn)輸入和輸出的呢?計算機主機中,附帶了用來連接顯示器以及鍵盤等外圍設(shè)備的連接器。而連接器的內(nèi)部,都連接有用來交換計算機主機同外圍設(shè)備之間電流特性的 IC。如果 IC 你不明白是什么的話,可以參考作者的文章 程序員需要了解的硬核知識之內(nèi)存 進行了解。這些 IC 統(tǒng)稱為 IO 控制器。
IO 是 Input/Output 的縮寫。顯示器、鍵盤等外圍設(shè)備都有各自專用的 I/O 控制器。I/O 控制器中有用于臨時保存輸入輸出數(shù)據(jù)的內(nèi)存。這個內(nèi)存就是 端口(port)。端口你就可以把它理解為我們上述說的 港口。IO 控制器內(nèi)部的內(nèi)存,也被稱為寄存器,不要慌,這個寄存器和內(nèi)存中的寄存器不一樣。CPU 內(nèi)存的寄存器是用于進行數(shù)據(jù)運算處理的,而IO中的寄存器是用于臨時存儲數(shù)據(jù)的。
在 I/O 設(shè)備內(nèi)部的 IC 中,有多個端口。由于計算機中連接著很多外圍設(shè)備,因此也就有很多 I/O 控制器。當然也會有多個端口,一個 I/O 控制器可以控制多個設(shè)備,不僅僅只能控制一個。各端口之間通過 端口號 進行區(qū)分。
端口號也被稱為 I/O地址 。IN 指令和 OUT 指令在端口號指定的端口和 CPU 之間進行數(shù)據(jù)的輸入和輸出。這跟通過內(nèi)存的地址來對內(nèi)存進行讀寫是一樣的道理。

測試輸入和輸出程序
首先讓我們利用 IN 指令和 OUT 指令,來進行一個直接控制硬件的實驗。假如試驗的目的是讓一個計算機內(nèi)置的喇叭(蜂鳴器)發(fā)出聲音。蜂鳴器封裝在計算機內(nèi)部,但它也是外圍設(shè)備的一種。
用匯編語言比較繁瑣,這次我們用 C 語言來實現(xiàn)。在大部分 C 語言的處理(編譯器的種類)中,只要使用 _asm{ 和 }括起來,就可以在其中記述助記符。也就是說,采用這種方式就能夠使用 C 語言和匯編語言混合的源代碼。
在 AT 兼容機中,蜂鳴器的默認端口號是 61H ,末尾的 H 表示的是十六進制數(shù)的意思。用 IN 指令通過該端口號輸入數(shù)據(jù),并將數(shù)據(jù)的低2位設(shè)定為 ON,然后再通過該端口號用 OUT 指令輸出數(shù)據(jù),這時蜂鳴器就會發(fā)出聲音。同樣的方法,將數(shù)據(jù)的低2位設(shè)定為 OFF 并輸出后,蜂鳴器就停止工作。
位設(shè)定為 ON 指的是將該位設(shè)定為1,位設(shè)定為 OFF 指的是將該位設(shè)定為0 。把位設(shè)定為 ON,只需要把想要設(shè)定為 ON 的位設(shè)定為1,其他位設(shè)定為0后進行 OR 運算即可。由于這里需要把低2位置為1,因此就是和 03H 進行 OR 運算。03H 用8為二進制來表示的話是 00000011。由于即便高6位存在著具體意義。和0進行OR運算后也不會發(fā)生變化,因而就和 03H 進行 OR 運算。把位設(shè)定為 OFF,只需要把想要置 OFF 的位設(shè)定為0,其他位設(shè)定為1后進行 AND 運算即可。由于這里需要把低2位設(shè)定為0,因此就要和 FCH 進行 AND 運算。在源代碼中,F(xiàn)CH 是用 0FCH 來記述的。在前面加 0 是匯編語言的規(guī)定,表示的是以 A - F 這些字符開頭的十六進制數(shù)是數(shù)值的意思。0FCH 用8位二進制數(shù)來表示的話是 11111100。由于即便高6位存在著具體意義,和1進行 AND 運算后也不會產(chǎn)生變化,因而就是同 0FCH 進行 OR 運算。
void main(){
// 計數(shù)器
int i;
// 蜂鳴器發(fā)聲
_asm{
IN EAX, 61H
OR EAX, 03H
OUT 61H, EAX
}
// 等待一段時間
for(i = 0;i < 1000000;i++);
// 蜂鳴器停止發(fā)生
_asm{
IN EAX, 61H
AND EAX, 0FCH
OUT 61H, EAX
}
}
我們對上面的代碼進行說明,main 是 C 語言程序起始位置的函數(shù)。在該函數(shù)中,有兩個用 _asm{} 圍起來的部分,它們中間有一個使用 for 循環(huán)的空循環(huán)
首先是蜂鳴器發(fā)聲的部分,通過 IN EAX,61H(助記符不區(qū)分大小寫)指令,把端口 61H 的數(shù)據(jù)存儲到 CPU 的 EAX 寄存器中。接下來,通過 OR EAX,03H 指令,把 EAX 寄存器的低2位設(shè)定成 ON。最后,通過 OUT 61H,EAX 指令,把 EAX 寄存器的內(nèi)容輸出到61端口。使蜂鳴器開始發(fā)音。雖然 EAX 寄存器的長度是 32 位,不過由于蜂鳴器端口是8位,所以只需對下8位進行OR運算和AND運算就可以正常工作了。
其次是一個重復(fù)100次的空循環(huán),主要是為了在蜂鳴器開始發(fā)音和停止發(fā)音之間稍微加上一些時間間隔。因為現(xiàn)在計算機器的運行速度非???,哪怕是 100 萬次循環(huán),也幾乎是瞬時間完成的。
然后是用來控制器蜂鳴器停止發(fā)聲的部分。首先,通過 IN ?EAX,61H 指令,把端口 61H 的數(shù)據(jù)存儲到 CPU 的 EAX 寄存器中。接下來,通過 AND ?EAX,0FCH 指令,把 EAX 寄存器的低2位設(shè)定為 OFF。最后,通過 OUT ?61H,EAX 指令,把寄存器的 EAX 內(nèi)容輸出到61號端口,使蜂鳴器停止發(fā)音。
外圍設(shè)備的中斷請求
IRQ(Interrupt Request) 代表的就是中斷請求。IRQ 用來暫停當前正在運行的程序,并跳轉(zhuǎn)到其他程序運行的必要機制。該機制被稱為 處理中斷。中斷處理在硬件控制中擔當著重要的角色。因為如果沒有中斷處理,就有可能無法順暢進行處理的情況。
從中斷處理開始到請求中斷的程序(中斷處理程序)運行結(jié)束之前,被中斷的程序(主程序)的處理是停止的。這種情況就類似于在處理文檔的過程中有電話打進來,電話就相當于是中斷處理。假如沒有中斷處理的發(fā)生,就必須等到文檔處理完成后才能夠接聽電話。由此可見,中斷處理有著巨大的價值,就像是接聽完電話后會返回原來的文檔作業(yè)一樣,中斷程序處理完成后,也會返回到主程序中繼續(xù)。

實施中斷請求的是連接外圍設(shè)備的 I/O 控制器,負責實施中斷處理的是 CPU,外圍設(shè)備的中斷請求會使用不同于 I/O 端口的其他編號,該編號稱為中斷編號。在控制面板中查看軟盤驅(qū)動器的屬性時,IRQ處現(xiàn)實的數(shù)值是 06,表示的就是用06號來識別軟盤驅(qū)動器發(fā)出的請求。還有就是操作系統(tǒng)以及 BIOS 則會提供響應(yīng)中斷編號的中斷處理程序。
BIOS(Basic Input Output System): 位于計算機主板或者擴張卡上內(nèi)置的 ROM 中,里面記錄了用來控制外圍設(shè)備的程序和數(shù)據(jù)。
假如有多個外圍設(shè)備進行中斷請求的話, CPU 需要做出選擇進行處理,為此,我們可以在 I/O 控制器和 CPU 中間加入名為中斷控制器的 IC 來進行緩沖。中斷控制器會把從多個外圍設(shè)備發(fā)出的中斷請求有序的傳遞給 CPU。中斷控制器的功能相當于就是緩沖。下面是中斷控制器功能的示意圖

CPU 在接受到中斷請求后,會把當前正在運行的任務(wù)中斷,并切換到中斷處理程序。中斷處理程序的第一步處理,就是把 CPU 所有寄存器的數(shù)值保存到內(nèi)存的棧中。在中斷處理程序中完成外圍設(shè)備的輸入和輸出后,把棧中保存的數(shù)值還原到 CPU 寄存器中,然后再繼續(xù)進行對主程序的處理。
假如 CPU 寄存器數(shù)值還沒有還原的話,就會影響到主程序的運行,甚至還有可能會使程序意外停止或發(fā)生運行時異常。這是因為主程序在運行過程中,會用到 CPU 寄存器進行處理,這時候如果突然插入其他程序的運行結(jié)果,此時 CPU 必然會受到影響。所以,在處理完中斷請求后,各個寄存器的值必須要還原。只要寄存器的值保持不變,主程序就可以像沒有發(fā)生過任何事情一樣繼續(xù)處理。

用中斷來實現(xiàn)實時處理
中斷是指計算機運行過程中,出現(xiàn)某些意外情況需主機干預(yù)時,機器能自動停止正在運行的程序并轉(zhuǎn)入處理新情況的程序,處理完畢后又返回原被暫停的程序繼續(xù)運行。
在程序的運行過程中,幾乎無時無刻都會發(fā)生中斷,其原因就是為了實時處理外部輸入的數(shù)據(jù),雖然程序也可以在不會中斷的基礎(chǔ)上處理外部數(shù)據(jù),但是那種情況下,主程序就會頻繁的檢查外圍設(shè)備是否會有數(shù)據(jù)輸入。由于外圍設(shè)備會有很多個,因此有必要按照順序來調(diào)查。按照順序檢查多個外圍設(shè)備的狀態(tài)稱為 輪詢。對于計算機來說,這種采用輪詢的方式不是很合理,如果你正在檢查是否有鼠標輸入,這時候發(fā)生了鍵盤輸入該如何處理呢?結(jié)果必定會導(dǎo)致文字的實時處理效率。所以即時的中斷能夠提高程序的運行效率。
上面只是中斷的一種好處,下面匯總一下利用中斷能夠帶來的正面影響
提高計算機系統(tǒng)效率。計算機系統(tǒng)中處理機的工作速度遠高于外圍設(shè)備的工作速度。通過中斷可以協(xié)調(diào)它們之間的工作。當外圍設(shè)備需要與處理機交換信息時,由外圍設(shè)備向處理機發(fā)出中斷請求,處理機及時響應(yīng)并作相應(yīng)處理。不交換信息時,處理機和外圍設(shè)備處于各自獨立的并行工作狀態(tài)。 維持系統(tǒng)可靠正常工作。現(xiàn)代計算機中,程序員不能直接干預(yù)和操縱機器,必須通過中斷系統(tǒng)向操作系統(tǒng)發(fā)出請求,由操作系統(tǒng)來實現(xiàn)人為干預(yù)。主存儲器中往往有多道程序和各自的存儲空間。在程序運行過程中,如出現(xiàn)越界訪問,有可能引起程序混亂或相互破壞信息。為避免這類事件的發(fā)生,由存儲管理部件進行監(jiān)測,一旦發(fā)生越界訪問,向處理機發(fā)出中斷請求,處理機立即采取保護措施。 滿足實時處理要求。在實時系統(tǒng)中,各種監(jiān)測和控制裝置隨機地向處理機發(fā)出中斷請求,處理機隨時響應(yīng)并進行處理。 提供故障現(xiàn)場處理手段。處理機中設(shè)有各種故障檢測和錯誤診斷的部件,一旦發(fā)現(xiàn)故障或錯誤,立即發(fā)出中斷請求,進行故障現(xiàn)場記錄和隔離,為進一步處理提供必要的依據(jù)。
利用 DMA 實現(xiàn)短時間內(nèi)大量數(shù)據(jù)傳輸
上面我們介紹了 I/O 處理和中斷的關(guān)系,下面我們來介紹一下另外一個機制,這個機制就是 DMA(Direct Memory Access)。DMA 是指在不通過 CPU 的情況下,外圍設(shè)備直接和主存進行數(shù)據(jù)傳輸。磁盤等硬件設(shè)備都用到了 DMA 機制,通過 DMA,大量數(shù)據(jù)可以在短時間內(nèi)實現(xiàn)傳輸,之所以這么快,是因為 CPU 作為中介的時間被節(jié)省了,下面是 DMA 的傳輸過程

I/O 端口號、IRQ、DMA 通道可以說是識別外圍設(shè)備的3點組合。不過,IRQ、DMA 通道并不是所有外圍設(shè)備都具備的。計算機主機通過軟件控制硬件時所需要的信息的最低限,是外圍設(shè)備的 I/O 端口號。IRQ 只對需要中斷處理的外圍設(shè)備來說是必須的,DMA 通道則只對需要 DMA 機制的外圍設(shè)備來說是必須的。假如多個外圍設(shè)備都設(shè)定成相同的端口號、IRQ 和 DMA 通道的話,計算機就無法正常工作,會提示 設(shè)備沖突。
文字和圖片的顯示機制
你知道文字和圖片是如何顯示出來的嗎?事實上,如果用一句話來簡單的概括一下該機制,那就是顯示器中顯示的信息一直存儲在某內(nèi)存中。該內(nèi)存稱為VRAM(Video RAM)。在程序中,只要往 VRAM 中寫入數(shù)據(jù),該數(shù)據(jù)就會在顯示器中顯示出來。實現(xiàn)該功能的程序,是由操作系統(tǒng)或者 BIOS 提供,并借助中斷來進行處理。
在 MS-DOS 時代,對于大部分計算機來說,VRAM 都是主內(nèi)存的一部分。在現(xiàn)代計算機中,顯卡等專用硬件中一般都配置有與主內(nèi)存相獨立的 VRAM 和 GPU(Graphics Processing Unit),也叫做圖形處理器或者圖形芯片。這是因為,對經(jīng)常描繪圖形的 windows 來說,數(shù)百兆的 VRAM 都是必需的。

用軟件來控制硬件聽起來好像很難,但實際上只是利用輸入輸出指令同外圍設(shè)備進行輸入輸出而已。中斷處理是根據(jù)需要來使用的功能選項。DMA 則直接交給對應(yīng)的外圍設(shè)備即可。
雖然計算機領(lǐng)域新技術(shù)在不斷涌現(xiàn),但是計算機所能處理的事情,始終只是對輸入的數(shù)據(jù)進行運算,并把結(jié)果輸出,這一點是永遠不會發(fā)生變化的。
文章參考:
《程序是怎樣跑起來的》
https://baike.baidu.com/item/中斷控制器/15732992?fr=aladdin
https://baike.baidu.com/item/中斷/3933007#viewPageContent
