程序員需要了解的硬核知識之控制硬件
應用和硬件的關系
我們作為程序員一般很少直接操控硬件,我們一般通過 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 // 字符串的文字數 }
1
2
3
4
5
6
7
那么,在處理 TextOut 函數的內容時,Windows 做了些什么呢?從結果來看,Windows 直接控制了作為硬件的顯示器。但 Windows 本身也是軟件,由此可見,Windows 應該向 CPU 傳遞了某種指令,從而通過軟件控制了硬件。
Windows 提供的 TextOut 函數 API 可以向窗口和打印機輸出字符。C 語言提供的 printf 函數,是用來在命令提示符中顯示字符串的函數。使用 printf 函數是無法向打印機輸出字符的。
支持硬件輸入輸出的 IN 指令和 OUT 指令
Windows 控制硬件借助的是輸入和輸出指令。其中具有代表性的兩個輸入輸出指令就是 IN 和 OUT指令。這些指令也是匯編語言的助記符。
可以通過 IN 和 OUT 指令來實現對數據的讀入和輸出,如下圖所示
也就是說,IN 指令通過指定的端口號輸入數據,OUT 指令則是把 CPU 寄存器中存儲的數據輸出到指定端口號的端口。
那么這個端口號 和 端口是什么呢?你感覺它像不像港口一樣?通過標注哪個港口然后進行貨物的運送和運出?
下面我們來看一下官方是如何定義端口號和端口的
還記得計算機組成原理中計算機的五大組成部分嗎,再來回顧一下:運算器、控制器、存儲器、輸入設備和輸出設備。我們今天不談前三個,就說說后面兩個輸入設備和輸出設備,這兩個與我們本節主題息息相關。
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 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
我們對上面的代碼進行說明,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 則直接交給對應的外圍設備即可。
雖然計算機領域新技術在不斷涌現,但是計算機所能處理的事情,始終只是對輸入的數據進行運算,并把結果輸出,這一點是永遠不會發生變化的。
文章參考:
《程序是怎樣跑起來的》
https://baike.baidu.com/item/中斷控制器/15732992?fr=aladdin
https://baike.baidu.com/item/中斷/3933007#viewPageContent
關注公眾號后臺回復 191106 即可獲得《程序是怎樣跑起來的》電子書
Windows 開發者
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。