鍵盤--掃描碼--ASCII碼--顯示器上的字元
阿新 • • 發佈:2021-03-13
在上一篇,我講了鍵盤操作會產生掃描碼以及如何解析`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步的