1. 程式人生 > >鍵盤--掃描碼--ASCII碼--顯示器上的字元

鍵盤--掃描碼--ASCII碼--顯示器上的字元

在上一篇,我講了鍵盤操作會產生掃描碼以及如何解析`Pause`鍵和`Print Screen`鍵的掃描碼。 在這一篇,我會說清楚”鍵盤上的輸入為什麼會出現在顯示器上“。 ## 極簡版 1. 我們敲擊鍵盤,產生掃描碼。 2. 作業系統獲取掃描碼,把掃描碼解析成ASCII碼。 3. 作業系統把ASCII碼寫入視訊記憶體,顯示器上就會打印出視訊記憶體中的可列印字元。 ## 解析掃描碼 在上一篇,我建立了一個Make Code和按鍵ASCII碼(部分按鍵如Enter是我自己設定的值)的對映陣列`keyboardMap`。 按鍵不是`Pause`,也不是`Print Screen`,就進入下面的解析流程。 ### 預設鍵值 1. 當接收到的掃描碼只有一個, 2. 掃描碼的型別是Make Code, 3. 這個Make Code的值是`V`, 4. 那麼鍵值是`keyboardMap[V *3]`。 ### 另一個鍵值 1. 接收到的第一個掃描碼是Make Code,值是`V`。 2. 從`keyboardMap`中查詢出被按下的鍵是`left_shift`或`right_shift`。 3. 設定`column`的值是1。 4. 接收到第二個掃描碼是Make Code,值是`N`。 5. 從對映陣列中查詢這個掃描碼對應的鍵值是:`keyboardMap[N * 3 + column]`。 注意,查詢到的鍵值是`keyboardMap[N * 3 + column]`。這就是鍵盤上的`1`、`A`這類按鍵與`shift`鍵組合時的值,即預設值外的另一個值。 #### 提問 請想一想,點選`shift + A`鍵,讀取掃描碼的過程是什麼樣的? ### Enter Enter鍵和Backspace鍵很容易解析。獲取掃描碼(Make Code)S,獲取鍵值`keyboardMap[N * 3]`。 根據鍵值識別出是Enter鍵後就把游標設定到下一行。 識別出是Backspace就這樣處理最新的兩個位元組:將高位元組設定成`0Fh`,將低位元組設定成空字元的ASCII碼;另外,將游標的位置後退兩個字元。 ### Caps Lock等鍵 沒有弄明白。 ## 清屏 使用linux的命令列、顯示器上列印滿了字元,我們可以使用`clear`讓命令列終端的字元全部消失。讓螢幕上的字元全部消失,這就是清屏。 `clear`命令只是移動了游標的位置,並沒有清除螢幕上的字元。按下方向鍵中的`Up`能定位到已經消失的字元。 我們的清屏,是讓字元徹底從螢幕上消失,無論怎麼按`Up`鍵都不會再看到字元。 清屏其實很簡單,就是往寫滿字元的視訊記憶體區域寫入空字元。 ## tty ### 什麼是tty 我覺得`tty`是一個比較過時的功能,我幾乎沒用過。 想體驗一番`tty`是什麼的同學,可以在安裝了linux系統的電腦或虛擬機器上按下`shift + alt + f1`或`shift + alt + f2`鍵在不同的tty之間切換。下面是我在虛擬機器上切換tty的效果,一個是圖形介面,一個是純命令列介面。 ![image-20210312211841834.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a55c972687bb45cb8fed117887206c6a~tplv-k3u1fbpfcp-watermark.image) ![image-20210312212013507.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/503e1480f2c24f939134254fb8632030~tplv-k3u1fbpfcp-watermark.image) ### VGA 我只講述`VGA`模式下的tty。 如上圖所示,不同的tty視窗展示的內容完全不相同,在tty0視窗敲擊鍵盤,資料只會出現在tty0的顯示器;切換到tty1後,顯示器上不會出現剛剛敲擊鍵盤的資料;反過來也是一樣。 不同tty之間,是完全隔離的,只是公用一個鍵盤。 `VGA`是什麼呢?我覺得弄清楚這個概念的方方面面沒有更多的作用。對`VGA`,瞭解有限的下面這些有限的知識就夠了。 #### 字元顯示 在這種模式下,顯示器一共能顯示25行字元,每行80個字元。 每個字元佔用2個位元組,高位元組是字元的顏色,低位元組是字元的ASCII碼。例如 ```assembly mov ah, 0Ah mov al, 'A' ``` `mov ah, 0Ah` 中的 `0Ah` 的高4位和低4位分別是: `0000b` 和 `1010b`。它們分別設定字元的背景色和前景色。 `mov al, 'A'` 中的 `A` 是要列印的字元。 #### 暫存器 `VGA`模式下的顯示器是一個硬體,提供了多組暫存器。 讀寫這些暫存器,能設定游標的位置、點選`Up`等方向鍵能滾屏。 讀寫這類暫存器,需先往一類暫存器中寫入另一類暫存器的索引,即往另一類暫存器的第N個暫存器中寫入資料;然後往第二類暫存器寫入資料,不需要再指定具體是哪個暫存器。 ### tty的實現 顯示器上的內容是視訊記憶體中的資料的對映。要讓不同的tty視窗列印不同的內容,只需把視訊記憶體分割成若干塊,每個tty分配一塊。 顯示器一滿屏所需空間是`80 * 25 * 2 = 4000`個位元組。視訊記憶體的記憶體地址是`0xB8000~~~0xBFFFF`,總計`0xBFFFF - 0xB8000 + 1 = 0x8000 = 32768`位元組。如果實現3個tty,每個tty對應的視訊記憶體大小大概是`32768 / 3 = 10922`個位元組,能儲存兩”滿屏“資料。 #### 虛擬碼 每個tty設計一個緩衝區C1,資料從鍵盤緩衝區C2到這個緩衝區,然後再從C1讀取資料寫入對應tty的視訊記憶體區域。 tty對應的視訊記憶體區域,也設計一個結構來儲存,叫`Console`。 直接看虛擬碼吧。 ````c typedef struct{ // tty使用的視訊記憶體的開始位置 int original_address; // tty使用的視訊記憶體的大小 int limit; // tty使用視訊記憶體的當前位置 int current_address; // tty的游標位置 int cursor_address; }Console; typedef struct{ // 緩衝區下一個要處理的字元的 int tail; // 緩衝區下一個空閒位置 int head; // 緩衝區儲存的資料的數量 int count; // 儲存資料的陣列 int buff[256]; Console * console; }TTY; ```` #### 全域性視角下的流程 1. tty任務和其他使用者程序(TestA等)一起初始化,並使用`restart`執行tty任務。 2. tty任務的流程如下: 1. tty任務遍歷所有的tty視窗。 2. 如果被遍歷到的tty視窗是當前tty,從C1緩衝區讀取資料D。 3. 把D轉換成ASCII碼,然後交給寫視訊記憶體模組列印到顯示器。 3. 發生時鐘中斷,給tty任務建立快照,排程模組讓使用者程序上CPU執行。 4. 使用者敲擊鍵盤,8048監測到鍵盤操作,讀取掃描碼(第2套),傳輸給8042。 5. 8042把掃描碼(第2套)轉換成第1套,放入鍵盤緩衝區C2,通知8259A發生鍵盤中斷。 1. 在C2中的資料被取走前,8042不再接收新資料。 6. 鍵盤中斷例程`save`當前程序,從C2讀取資料,然後放入C1。 7. tty任務在某個時刻獲得CPU控制權,執行第2步的