鍵盤
很簡單,只要設置8259A芯片的鍵盤端口的handler處理函數就可以了。
鍵盤敲擊的過程
鍵盤編碼器,用於監視鍵盤的輸入,並把適當的數據傳送給計算機。
鍵盤控制器,用來接受和解碼來自鍵盤的數據,並與8259A以及軟件等通信。
敲擊鍵盤包含兩個含義:動作和內容。
敲擊鍵盤所產生的編碼被稱作掃描碼。
當8048檢測到一個鍵的動作後,會把相應的掃描碼發送給8042,8042會把它轉換成相應的Scan code 1掃描碼,並將其放置在輸入緩衝區,然後8042告訴8059A產生中斷(IRQ1)。一直到緩衝區的內容被讀出清空,8042纔會收到更多的掃描碼。
建立輸入緩衝區
建立一個緩衝區,讓keyboard_handler
將每次收到的掃描碼放入這個緩衝區,然後建立一個新的任務專門用來解析它們並做相應處理。
#define KB_IN_BYTES 32 /* size of keyboard input buffer */
/* Keyboard structure, 1 per console. */
typedef struct s_kb {
char* p_head; /* 指向緩衝區中下一個空閒位置 */
char* p_tail; /* 指向鍵盤任務應處理的字節 */
int count; /* 緩衝區中共有多少字節 */
char buf[KB_IN_BYTES]; /* 緩衝區 */
}KB_INPUT;
鍵盤緩衝區示意圖:
對緩衝區進行添加操作,如果緩衝區已滿,這裏使用的策略是直接就把收到的字節丟棄。
PUBLIC void keyboard_handler(int irq)
{
u8 scan_code = in_byte(KB_DATA);
if (kb_in.count < KB_IN_BYTES) {
*(kb_in.p_head) = scan_code;
kb_in.p_head++;
if (kb_in.p_head == kb_in.buf + KB_IN_BYTES) {
kb_in.p_head = kb_in.buf;
}
kb_in.count++;
}
}
用新加的任務處理鍵盤操作
讀緩衝區時關閉了中斷,到結束時纔打開。
PUBLIC void keyboard_read()
{
u8 scan_code;
if(kb_in.count > 0){
disable_int();
scan_code = *(kb_in.p_tail);
kb_in.p_tail++;
if (kb_in.p_tail == kb_in.buf + KB_IN_BYTES) {
kb_in.p_tail = kb_in.buf;
}
kb_in.count--;
enable_int();
disp_int(scan_code);
}
}
解析掃描碼
詳見keyboard_read()
中巨長的if-else。
顯示器
初識TTY(終端)
不同的TTY對應的屏幕畫面可能是迥然不同的,因爲顯示了顯存的不同位置。
我們操作的對象可能是顯卡,或者僅僅是顯存。
在實模式下,我們通過BIOS中斷來實現打印字符。
在保護模式下,我們在GDT中建立了一個段,它的開始地址0xB8000,通過段寄存器gs對它進行寫操作,從而實現數據的顯示。
目前,我們對於視頻模塊的操作也僅限於此,想顯示什麼就mov而已。
實際上視頻是一個很複雜的部分,顯示適配器可以被設置成不同模式,用來顯示更多的色彩圖像動畫。
我們就用開機默認的80x25文本模式,佔用範圍爲0xB8000~0XBFFFF,顯存大小爲32KB,每2個字節代表一個字符。
VGA視頻系統的寄存器
如何讓系統顯示指定位置的內容?
通過端口操作設置相應的寄存器就可以了。
TTY任務
在TTY任務中執行一個循環,這個循環將輪訓每一個TTY,處理它的事件。
#define TTY_IN_BYTES 256 /* tty input queue size */
struct s_console;
/* TTY */
typedef struct s_tty
{
u32 in_buf[TTY_IN_BYTES]; /* TTY 輸入緩衝區 */
u32* p_inbuf_head; /* 指向緩衝區中下一個空閒位置 */
u32* p_inbuf_tail; /* 指向鍵盤任務應處理的鍵值 */
int inbuf_count; /* 緩衝區中已經填充了多少 */
struct s_console * p_console;
}TTY;
/* CONSOLE */
typedef struct s_console
{
unsigned int current_start_addr; /* 當前顯示到了什麼位置 */
unsigned int original_addr; /* 當前控制檯對應顯存位置 */
unsigned int v_mem_limit; /* 當前控制檯佔的顯存大小 */
unsigned int cursor; /* 當前光標位置 */
}CONSOLE;
TTY任務代碼示意:
多控制檯
區分任務和用戶進程
printf
printf()要完成屏幕輸出的功能,需要控制檯模塊中的相應代碼,所以,它必須通過系統調用才能完成。
printf()的實現–可變參數
C調用約定,後面的參數先入棧,並且由調用者清理堆棧。
假設我們調用printf(fmt, i, j); 則堆棧情況將如下圖:
va_list
其實是個char*
,雖然用...
表示了可變參數,不知道有幾個參數,但其實vsprintf
會根據cahr *fmt
中的內容推算出有幾個參數。
typedef char * va_list;
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4); /*4是參數fmt所佔堆棧中的大小*/
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
int vsprintf(char *buf, const char *fmt, va_list args)
{
char* p;
char tmp[256];
va_list p_next_arg = args;
for (p=buf;*fmt;fmt++) {
if (*fmt != '%') {
*p++ = *fmt;
continue;
}
fmt++;
switch (*fmt) {
case 'x':
itoa(tmp, *((int*)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case 's':
break;
default:
break;
}
}
return (p - buf);
}
系統調用write()
系統調用也就是觸發一個自定義的中斷,然後指定一個索引,執行sys_table相應的函數就可以了。
這裏函數參數的傳遞需要一些寄存器做一下中轉。。。。
printf調用過程示意圖: