首頁>技術>

目錄

問題 1:我們的目標是什麼?

問題 2:按鍵怎麼抽象?

問題 3:如何處理按鍵?使用作業系統處理還是讓每個程式自己實現?

問題 4:程式用什麼模型響應按鍵?

問題 5:處理使用者按鍵,需不需要打斷正在執行的程式?

問題 6:作業系統如何知道使用者按了哪個鍵?

問題 7:主機板如何知道鍵盤被按下?

思路的整理:中斷的設計

中斷的型別

問題1: Java/Js 等語言為什麼可以捕獲到鍵盤輸入?

問題2:作業系統可以處理鍵盤按鍵可以理解,那麼我們開機的時候也可以使用鍵盤,但是那時候作業系統還沒有載入記憶體,這個怎麼解釋?

探索過程:如何設計響應鍵盤的整個鏈路?當你拿到一個問題時,需要冷靜下來思考和探索解決方案。你可以查資料、看影片或者諮詢專家,但是在這之前,你先要進行一定的思考和梳理,有的問題可以直接找到答案,有的問題卻需要繼續深挖尋找其背後的理論支撐。

問題 1:我們的目標是什麼?

我們的目標是在 Java/JS 中實現按鍵響應程式。這種實現有點像 Switch-Case 語句——根據不同的按鍵執行不同的程式,比如按下回車鍵可以換行,按下左右鍵可以移動游標。

問題 2:按鍵怎麼抽象?

鍵盤上一般不超過 100 個鍵。因此我們可以考慮用一個 Byte 的資料來描述使用者按下了什麼鍵。按鍵有兩個操作,一個是按下、一個是釋放,這是兩個不同的操作。對於一個 8 位的位元組,可以考慮用最高位的 1 來描述按下還是釋放的狀態,然後後面的 7 位(0~127)描述具體按了哪個鍵。這樣我們只要確定了使用者按鍵/釋放的順序,對我們的系統來說,就不會有歧義。

問題 3:如何處理按鍵?使用作業系統處理還是讓每個程式自己實現?

處理按鍵是一個通用程式,可以考慮由作業系統先進行一部分處理,比如:

使用者按下了回車鍵,先由作業系統進行統一的封裝,再把按鍵的編碼轉換為字串Enter方便各種程式使用。

處理組合鍵這種操作,由作業系統先一步進行計算比較好。因為底層只知道按鍵、釋放,組合鍵必須結合時間因素判斷。

你可以把下面這種情況看作是一個Ctrl + C組合鍵,這種行為可以由作業系統進行統一處理,如下所示:

按下 Ctrl按下 C釋放 Ctrl釋放 C
問題 4:程式用什麼模型響應按鍵?

當一個 Java 或者 JS 寫的應用程式想要響應按鍵時,應該考慮訊息模型。因為如果程式不停地掃描按鍵,會給整個系統帶來很大的負擔。比如程式寫一個while迴圈去掃描有沒有按鍵,開銷會很大。 如果程式在作業系統端註冊一個響應按鍵的函式,每次只有真的觸發按鍵時才執行這個函式,這樣就能減少開銷了。

問題 5:處理使用者按鍵,需不需要打斷正在執行的程式?

從使用者體驗上講,按鍵應該是一個高優先順序的操作,比如使用者按 Ctrl+C 或者 Esc 的時候,可能是因為使用者想要打斷當前執行的程式。即便是使用者只想要輸入,也應該儘可能地集中資源給到使用者,因為我們不希望使用者感覺到延遲。

如果需要考慮到程式隨時會被中斷,去響應其他更高優先順序的情況,那麼從程式執行的底層就應該支援這個行為,而且最好從硬體層面去支援,這樣速度最快。 這就引出了本課時的主角——中斷。具體如何處理,見下面我們關於中斷部分的分析。

問題 6:作業系統如何知道使用者按了哪個鍵?

這裡有一個和問題 5 類似的問題。作業系統是不斷主動觸發讀取鍵盤按鍵,還是每次鍵盤按鍵到來的時候都觸發一段屬於作業系統的程式呢?

顯然,後者更節省效率。

那麼誰能隨時隨地中斷作業系統的程式? 誰有這個許可權?是管理員賬號嗎? 當然不是,擁有這麼高許可權的應該是機器本身。

我們思考下這個模型,使用者每次按鍵,觸發一個 CPU 的能力,這個能力會中斷正在執行的程式,去處理按鍵。那 CPU 內部是不是應該有處理按鍵的程式呢?這肯定不行,因為我們希望 CPU 就是用來做計算的,如果 CPU 內部有自帶的程式,會把問題複雜化。這在軟體設計中,叫作耦合。CPU 的工作就是專注高效的執行指令。

因此,每次按鍵,必須有一個機制通知 CPU。我們可以考慮用匯流排去通知 CPU,也就是主機板在通知 CPU。

那麼 CPU 接收到通知後,如何通知作業系統呢?CPU 只能中斷正在執行的程式,然後切換到另一個需要執行的程式。說白了就是改變 PC 指標,CPU 只有這一種辦法切換執行的程式。這裡請你思考,是不是隻有這一種方法:CPU 中斷當前執行的程式,然後去執行另一個程式,才能改變 PC 指標?

接下來我們進一步思考,CPU 怎麼知道 PC 指標應該設定為多少呢?是不是 CPU 知道作業系統響應按鍵的程式位置呢?

答案當然是不知道。

因此,我們只能控制 CPU 跳轉到一個固定的位置。比如說 CPU 一收到主機板的資訊(某個按鍵被觸發),CPU 就馬上中斷當前執行的程式,將 PC 指標設定為 0。也就是 PC 指標下一步會從記憶體地址 0 中讀取下一條指令。當然這只是我們的一個思路,具體還需要進一步考慮。而作業系統要做的就是在這之前往記憶體地址 0 中寫一條指令,比如說讓 PC 指標跳轉到自己處理按鍵程式的位置。

講到這裡,我們總結一下,CPU 要做的就是一看到中斷,就改變 PC 指標(相當於中斷正在執行的程式),而 PC 改變成多少,可以根據不同的型別來判斷,比如按鍵就到 0。作業系統就要向這些具體的位置寫入指令,當中斷髮生時,接管程式的控制權,也就是讓 PC 指標指向作業系統處理按鍵的程式。

問題 7:主機板如何知道鍵盤被按下?

經過一層一層地深挖“如何設計響應鍵盤的整個鏈路?”這個問題,目前作業系統已經能接管按鍵,接下來,我們還需要思考主機板如何知道有按鍵,並且通知 CPU。

你可以把鍵盤按鍵看作按下了某個開關,我們需要一個晶片將按鍵資訊轉換成具體按鍵的值。比如使用者按下 A 鍵,A 鍵在第幾行、第幾列,可以看作一個電學訊號。接著我們需要晶片把這個電學訊號轉化為具體的一個數字(一個 Byte)。轉化完成後,主機板就可以接收到這個數字(按鍵碼),然後將數字寫入自己的一個暫存器中,並通知 CPU。

為了方便 CPU 計算,CPU 接收到主機板通知後,按鍵碼會被存到一個暫存器裡,這樣方便處理按鍵的程式執行。

思路的整理:中斷的設計

整體設計分成了 3 層,第一層是硬體設計、第二層是作業系統設計、第三層是程式語言的設計。

按鍵碼的收集,是鍵盤晶片和主機板的能力。主機板知道有新的按鍵後,通知 CPU,CPU 要中斷當前執行的程式,將 PC 指標跳轉到一個固定的位置,我們稱為一次中斷(interrupt)。

考慮到系統中會出現各種各樣的事件,我們需要根據中斷型別來判斷PC 指標跳轉的位置,中斷型別不同,PC 指標跳轉的位置也可能會不同。比如按鍵程式、印表機就緒程式、系統異常等都需要中斷,例如系統呼叫,也需要中斷正在執行的程式,切換到核心態執行核心程式。

因此我們需要把不同的中斷型別進行分類,這個型別叫作中斷識別碼。比如按鍵,我們可以考慮用編號 16,數字 16 就是按鍵中斷型別的識別碼。不同型別的中斷髮生時,CPU 需要知道 PC 指標該跳轉到哪個地址,這個地址,稱為中斷向量(Interupt Vector)

你可以考慮這樣的實現:當編號 16 的中斷髮生時,32 位機器的 PC 指標直接跳轉到記憶體地址 16*4 的記憶體位置。如果設計最多有 255 箇中斷,編號就是從 0~255,剛好需要 1K 的記憶體地址儲存中斷向量——這個 1K 的空間,稱為中斷向量表。

32位剛好是4個位元組,也就是說255個4位元組就可以儲存整個向量表.

因此 CPU 接收到中斷後,CPU 根據中斷型別操作 PC 指標,找到中斷向量。作業系統必須在這之前,修改中斷向量,插入一條指令。比如作業系統在這裡寫一條Jump指令,將 PC 指標再次跳轉到自己處理對應中斷型別的程式。

作業系統接管之後,以按鍵程式為例,作業系統會進行一些處理,包括下面的幾件事情:

將按鍵放入一個佇列,儲存下來。這是因為,作業系統不能保證及時處理所有的按鍵,比如當按鍵過快時,需要先儲存下來,再分時慢慢處理。

計算組合鍵。可以利用按下、釋放之間的時間關係。

經過一定計算將按鍵抽象成訊息(事件結構或物件)。

提供 API 給應用程式,讓應用程式可以監聽作業系統處理後的訊息。

分發按鍵訊息給監聽按鍵的程式。

所以程式在語言層面,比如像 Java/Node.js 這種擁有虛擬機器的語言,只需要對接作業系統 API 就可以了。

中斷的型別

按照中斷的觸發方分成同步中斷非同步中斷

根據中斷是否強制觸發分成可遮蔽中斷不可遮蔽中斷

中斷可以由 CPU 指令直接觸發,這種主動觸發的中斷,叫作同步中斷。同步中斷有幾種情況。

系統呼叫,需要從使用者態切換核心態,這種情況需要程式觸發一箇中斷,叫作陷阱(Trap),中斷觸發後需要繼續執行系統呼叫。還有一種同步中斷情況是錯誤(Fault),通常是因為檢測到某種錯誤,需要觸發一箇中斷,中斷響應結束後,會重新執行觸發錯誤的地方,比如缺頁中斷 pagefault。程式的異常,這種情況和 Trap 類似,用於實現程式丟擲的異常。

另一部分中斷不是由 CPU 直接觸發,是因為需要響應外部的通知,比如響應鍵盤、滑鼠等裝置而觸發的中斷。這種中斷我們稱為非同步中斷。

CPU 通常都支援設定一個中斷遮蔽位(一個暫存器),設定為 1 之後 CPU 暫時就不再響應中斷。對於鍵盤滑鼠輸入,比如陷阱、錯誤、異常等情況,會被臨時遮蔽。但是對於一些特別重要的中斷,比如 CPU 故障導致的掉電中斷,還是會正常觸發。可以被遮蔽的中斷我們稱為可遮蔽中斷,多數中斷都是可遮蔽中斷。

這也可以解釋為什麼有的程式不能被ctrl + c終結.

問題1: Java/Js 等語言為什麼可以捕獲到鍵盤輸入?

為了捕獲到鍵盤輸入,硬體層面需要把按鍵抽象成中斷,中斷 CPU 執行。CPU 根據中斷型別找到對應的中斷向量。作業系統預置了中斷向量,因此發生中斷後作業系統接管了程式。作業系統實現了基本解析按鍵的演算法,將按鍵抽象成鍵盤事件,並且提供了佇列儲存多個按鍵,還提供了監聽按鍵的 API。因此應用程式,比如 Java/Node.js 虛擬機器,就可以透過呼叫作業系統的 API 使用鍵盤事件。

問題2:作業系統可以處理鍵盤按鍵可以理解,那麼我們開機的時候也可以使用鍵盤,但是那時候作業系統還沒有載入記憶體,這個怎麼解釋?

主機板的一塊 ROM 上往往還有一個簡化版的作業系統,叫 BIOS(Basic Input/Ouput System)。在 OS 還沒有接管計算機前,先由 BIOS 管理機器,並協助載入 OS 到記憶體。早期的 OS 還會利用 BIOS 的能力,現代的 OS 接管後,就會替換掉 BIOS 的中斷向量。

5
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 程式設計閣樓:一道redis經典面試題