鍵盤--掃描碼--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_shiftright_shift
  3. 設置column的值是1。
  4. 接收到第二個掃描碼是Make Code,值是N
  5. 從映射數組中查詢這個掃描碼對應的鍵值是:keyboardMap[N * 3 + column]

注意,查詢到的鍵值是keyboardMap[N * 3 + column]。這就是鍵盤上的1A這類按鍵與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 + f1shift + alt + f2鍵在不同的tty之間切換。下面是我在虛擬機上切換tty的效果,一個是圖形界面,一個是純命令行界面。

image-20210312211841834.png

image-20210312212013507.png

VGA

我只講述VGA模式下的tty。

如上圖所示,不同的tty窗口展示的內容完全不相同,在tty0窗口敲擊鍵盤,數據只會出現在tty0的顯示器;切換到tty1后,顯示器上不會出現剛剛敲擊鍵盤的數據;反過來也是一樣。

不同tty之間,是完全隔離的,只是公用一個鍵盤。

VGA是什么呢?我覺得弄清楚這個概念的方方面面沒有更多的作用。對VGA,了解有限的下面這些有限的知識就夠了。

字符顯示

在這種模式下,顯示器一共能顯示25行字符,每行80個字符。

每個字符占用2個字節,高字節是字符的顏色,低字節是字符的ASCII碼。例如

mov		ah,		0Ah
mov		al,		'A'

mov ah, 0Ah 中的 0Ah 的高4位和低4位分別是: 0000b1010b。它們分別設置字符的背景色和前景色。

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

直接看偽代碼吧。

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步的流程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM