鍵盤敲入 A 字母時,作業系統期間發生了什麼
前言
鍵盤可以說是我們最常使用的輸入硬體裝置了,但身為程式設計師的你,你知道「鍵盤敲入A 字母時,作業系統期間發生了什麼嗎」?
那要想知道這個發生的過程,我們得先了解了解「作業系統是如何管理多種多樣的的輸入輸出裝置」的,等了解完這個後,我們再來看看這個問題,你就會發現問題已經被迎刃而解了。
正文
裝置控制器
我們的電腦裝置可以接非常多的輸入輸出裝置,比如鍵盤、滑鼠、顯示器、網絡卡、硬碟、印表機、音響等等,每個裝置的用法和功能都不同,那作業系統是如何把這些輸入輸出裝置統一管理的呢?
為了遮蔽裝置之間的差異,每個裝置都有一個叫裝置控制器(Device Control) 的元件,比如硬碟有硬碟控制器、顯示器有視訊控制器等。
因為這些控制器都很清楚的知道對應裝置的用法和功能,所以 CPU 是通過裝置控制器來和裝置打交道的。
裝置控制器裡有晶片,它可執行自己的邏輯,也有自己的暫存器,用來與 CPU 進行通訊,比如:
- 通過寫入這些暫存器,作業系統可以命令裝置傳送資料、接收資料、開啟或關閉,或者執行某些其他操作。
- 通過讀取這些暫存器,作業系統可以瞭解裝置的狀態,是否準備好接收一個新的命令等。
實際上,控制器是有三類暫存器,它們分別是狀態暫存器(Status Register)、 命令暫存器(Command Register)以及資料暫存器(Data Register),如下圖:
這三個暫存器的作用:
- 資料暫存器,CPU 向 I/O 裝置寫入需要傳輸的資料,比如要列印的內容是「Hello」,CPU 就要先發送一個 H 字元給到對應的 I/O 裝置。
- 命令暫存器,CPU 傳送一個命令,告訴 I/O 裝置,要進行輸入/輸出操作,於是就會交給 I/O 裝置去工作,任務完成後,會把狀態暫存器裡面的狀態標記為完成。
- 狀態暫存器,目的是告訴 CPU ,現在已經在工作或工作已經完成,如果已經在工作狀態,CPU 再發送資料或者命令過來,都是沒有用的,直到前面的工作已經完成,狀態寄存標記成已完成,CPU 才能傳送下一個字元和命令。
CPU 通過讀寫裝置控制器中的暫存器控制裝置,這可比 CPU 直接控制輸入輸出裝置,要方便和標準很多。
另外, 輸入輸出裝置可分為兩大類 :塊裝置(Block Device)和字元裝置(Character Device)。
- 塊裝置,把資料儲存在固定大小的塊中,每個塊有自己的地址,硬碟、USB 是常見的塊裝置。
- 字元裝置,以字元為單位傳送或接收一個字元流,字元裝置是不可定址的,也沒有任何尋道操作,滑鼠是常見的字元裝置。
塊裝置通常傳輸的資料量會非常大,於是控制器設立了一個可讀寫的資料緩衝區。
- CPU 寫入資料到控制器的緩衝區時,當緩衝區的資料囤夠了一部分,才會發給裝置。
- CPU 從控制器的緩衝區讀取資料時,也需要緩衝區囤夠了一部分,才拷貝到記憶體。
這樣做是為了,減少對裝置的頻繁操作。
那 CPU 是如何與裝置的控制暫存器和資料緩衝區進行通訊的?存在兩個方法:
- 埠 I/O,每個控制暫存器被分配一個 I/O 埠,可以通過特殊的彙編指令操作這些暫存器,比如
in/out
類似的指令。 - 記憶體對映 I/O,將所有控制暫存器對映到記憶體空間中,這樣就可以像讀寫記憶體一樣讀寫資料緩衝區。
I/O 控制方式
在前面我知道,每種裝置都有一個裝置控制器,控制器相當於一個小 CPU,它可以自己處理一些事情,但有個問題是,當 CPU 給裝置傳送了一個指令,讓裝置控制器去讀裝置的資料,它讀完的時候,要怎麼通知 CPU 呢?
控制器的暫存器一般會有狀態標記位,用來標識輸入或輸出操作是否完成。於是,我們想到第一種輪詢等待的方法,讓 CPU 一直查暫存器的狀態,直到狀態標記為完成,很明顯,這種方式非常的傻瓜,它會佔用 CPU 的全部時間。
那我們就想到第二種方法 —— 中斷,通知作業系統資料已經準備好了。我們一般會有一個硬體的中斷控制器,當裝置完成任務後觸發中斷到中斷控制器,中斷控制器就通知 CPU,一箇中斷產生了,CPU 需要停下當前手裡的事情來處理中斷。
另外,中斷有兩種,一種軟中斷,例如程式碼呼叫 INT
指令觸發,一種是硬體中斷,就是硬體通過中斷控制器觸發的。
但中斷的方式對於頻繁讀寫資料的磁碟,並不友好,這樣 CPU 容易經常被打斷,會佔用 CPU 大量的時間。對於這一類裝置的問題的解決方法是使用 DMA(Direct Memory Access) 功能,它可以使得裝置在 CPU 不參與的情況下,能夠自行完成把裝置 I/O 資料放入到記憶體。那要實現 DMA 功能要有 「DMA 控制器」硬體的支援。
DMA 的工作方式如下:
- CPU 需對 DMA 控制器下發指令,告訴它想讀取多少資料,讀完的資料放在記憶體的某個地方就可以了;
- 接下來,DMA 控制器會向磁碟控制器發出指令,通知它從磁碟讀資料到其內部的緩衝區中,接著磁碟控制器將緩衝區的資料傳輸到記憶體;
- 當磁碟控制器把資料傳輸到記憶體的操作完成後,磁碟控制器在總線上發出一個確認成功的訊號到 DMA 控制器;
- DMA 控制器收到訊號後,DMA 控制器發中斷通知 CPU 指令完成,CPU 就可以直接用記憶體裡面現成的資料了;
可以看到, CPU 當要讀取磁碟資料的時候,只需給 DMA 控制器傳送指令,然後返回去做其他事情,當磁碟資料拷貝到記憶體後,DMA 控制機器通過中斷的方式,告訴 CPU 資料已經準備好了,可以從記憶體讀資料了。僅僅在傳送開始和結束時需要 CPU 干預。
裝置驅動程式
雖然裝置控制器遮蔽了裝置的眾多細節,但每種裝置的控制器的暫存器、緩衝區等使用模式都是不同的,所以為了遮蔽「裝置控制器」的差異,引入了裝置驅動程式。
裝置控制器不屬於作業系統範疇,它是屬於硬體,而裝置驅動程式屬於作業系統的一部分,作業系統的核心程式碼可以像本地呼叫程式碼一樣使用裝置驅動程式的介面,而裝置驅動程式是面向裝置控制器的程式碼,它發出操控裝置控制器的指令後,才可以操作裝置控制器。
不同的裝置控制器雖然功能不同,但是裝置驅動程式會提供統一的介面給作業系統,這樣不同的裝置驅動程式,就可以以相同的方式接入作業系統。如下圖:
前面提到了不少關於中斷的事情,裝置完成了事情,則會發送中斷來通知作業系統。那作業系統就需要有一個地方來處理這個中斷,這個地方也就是在裝置驅動程式裡,它會及時響應控制器發來的中斷請求,並根據這個中斷的型別呼叫響應的中斷處理程式進行處理。
通常,裝置驅動程式初始化的時候,要先註冊一個該裝置的中斷處理函式。
我們來看看,中斷處理程式的處理流程:
- 在 I/O 時,裝置控制器如果已經準備好資料,則會通過中斷控制器向 CPU 傳送中斷請求;
- 保護被中斷程序的 CPU 上下文;
- 轉入相應的裝置中斷處理函式;
- 進行中斷處理;
- 恢復被中斷程序的上下文;
通用塊層
對於塊裝置,為了減少不同塊裝置的差異帶來的影響,Linux 通過一個統一的通用塊層,來管理不同的塊裝置。
通用塊層是處於檔案系統和磁碟驅動中間的一個塊裝置抽象層,它主要有兩個功能:
- 第一個功能,向上為檔案系統和應用程式,提供訪問塊裝置的標準介面,向下把各種不同的磁碟裝置抽象為統一的塊裝置,並在核心層面,提供一個框架來管理這些裝置的驅動程式;
- 第二功能,通用層還會給檔案系統和應用程式發來的 I/O 請求排隊,接著會對佇列重新排序、請求合併等方式,也就是 I/O 排程,主要目的是為了提高磁碟讀寫的效率。
Linux 記憶體支援 5 種 I/O 排程演算法,分別是:
- 沒有排程演算法
- 先入先出排程演算法
- 完全公平排程演算法
- 優先順序排程
- 最終期限排程演算法
第一種,沒有排程演算法,是的,你沒聽錯,它不對檔案系統和應用程式的 I/O 做任何處理,這種演算法常用在虛擬機器 I/O 中,此時磁碟 I/O 排程演算法交由物理機系統負責。
第二種,先入先出排程演算法,這是最簡單的 I/O 排程演算法,先進入 I/O 排程佇列的 I/O 請求先發生。
第三種,完全公平排程演算法,大部分系統都把這個演算法作為預設的 I/O 排程器,它為每個程序維護了一個 I/O 排程佇列,並按照時間片來均勻分佈每個程序的 I/O 請求。
第四種,優先順序排程演算法,顧名思義,優先順序高的 I/O 請求先發生, 它適用於執行大量程序的系統,像是桌面環境、多媒體應用等。
第五種,最終期限排程演算法,分別為讀、寫請求建立了不同的 I/O 佇列,這樣可以提高機械磁碟的吞吐量,並確保達到最終期限的請求被優先處理,適用於在 I/O 壓力比較大的場景,比如資料庫等。
儲存系統 I/O 軟體分層
前面說到了不少東西,裝置、裝置控制器、驅動程式、通用塊層,現在再結合檔案系統原理,我們來看看 Linux 儲存系統的 I/O 軟體分層。
可以把 Linux 儲存系統的 I/O 由上到下可以分為三個層次,分別是檔案系統層、通用塊層、裝置層。他們整個的層次關係如下圖:
這三個層次的作用是:
- 檔案系統層,包括虛擬檔案系統和其他檔案系統的具體實現,它向上為應用程式統一提供了標準的檔案訪問介面,向下會通過通用塊層來儲存和管理磁碟資料。
- 通用塊層,包括塊裝置的 I/O 佇列和 I/O 排程器,它會對檔案系統的 I/O 請求進行排隊,再通過 I/O 排程器,選擇一個 I/O 發給下一層的裝置層。
- 裝置層,包括硬體裝置、裝置控制器和驅動程式,負責最終物理裝置的 I/O 操作。
有了檔案系統介面之後,不但可以通過檔案系統的命令列操作裝置,也可以通過應用程式,呼叫 read
、write
函式,就像讀寫檔案一樣操作裝置,所以說裝置在 Linux 下,也只是一個特殊的檔案。
但是,除了讀寫操作,還需要有檢查特定於裝置的功能和屬性。於是,需要 ioctl
介面,它表示輸入輸出控制介面,是用於配置和修改特定裝置屬性的通用介面。
另外,儲存系統的 I/O 是整個系統最慢的一個環節,所以 Linux 提供了不少快取機制來提高 I/O 的效率。
- 為了提高檔案訪問的效率,會使用頁快取、索引節點快取、目錄項快取等多種快取機制,目的是為了減少對塊裝置的直接呼叫。
- 為了提高塊裝置的訪問效率, 會使用緩衝區,來快取塊裝置的資料。
鍵盤敲入字母時,期間發生了什麼?
看完前面的內容,相信你對輸入輸出裝置的管理有了一定的認識,那接下來就從作業系統的角度回答開頭的問題「鍵盤敲入字母時,作業系統期間發生了什麼?」
我們先來看看 CPU 的硬體架構圖:
CPU 的硬體架構圖CPU 裡面的記憶體介面,直接和系統匯流排通訊,然後系統匯流排再接入一個 I/O 橋接器,這個 I/O 橋接器,另一邊接入了記憶體匯流排,使得 CPU 和記憶體通訊。再另一邊,又接入了一個 I/O 匯流排,用來連線 I/O 裝置,比如鍵盤、顯示器等。
那當用戶輸入了鍵盤字元,鍵盤控制器就會產生掃描碼資料,並將其緩衝在鍵盤控制器的暫存器中,緊接著鍵盤控制器通過匯流排給 CPU 傳送中斷請求。
CPU 收到中斷請求後,作業系統會儲存被中斷程序的 CPU 上下文,然後呼叫鍵盤的中斷處理程式。
鍵盤的中斷處理程式是在鍵盤驅動程式初始化時註冊的,那鍵盤中斷處理函式的功能就是從鍵盤控制器的暫存器的緩衝區讀取掃描碼,再根據掃描碼找到使用者在鍵盤輸入的字元,如果輸入的字元是顯示字元,那就會把掃描碼翻譯成對應顯示字元的 ASCII 碼,比如使用者在鍵盤輸入的是字母 A,是顯示字元,於是就會把掃描碼翻譯成 A 字元的 ASCII 碼。
得到了顯示字元的 ASCII 碼後,就會把 ASCII 碼放到「讀緩衝區佇列」,接下來就是要把顯示字元顯示螢幕了,顯示裝置的驅動程式會定時從「讀緩衝區佇列」讀取資料放到「寫緩衝區佇列」,最後把「寫緩衝區佇列」的資料一個一個寫入到顯示裝置的控制器的暫存器中的資料緩衝區,最後將這些資料顯示在螢幕裡。
顯示出結果後,恢復被中斷程序的上下文。
大家好,我是小林,一個專為大家圖解的工具人,我們下次見!