《一個操作系統的實現》筆記(7)--輸入/輸出系統(I/O)


鍵盤

很簡單,只要設置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對應的屏幕畫面可能是迥然不同的,因爲顯示了顯存的不同位置。
TTY

我們操作的對象可能是顯卡,或者僅僅是顯存。
在實模式下,我們通過BIOS中斷來實現打印字符。
在保護模式下,我們在GDT中建立了一個段,它的開始地址0xB8000,通過段寄存器gs對它進行寫操作,從而實現數據的顯示。
目前,我們對於視頻模塊的操作也僅限於此,想顯示什麼就mov而已。
實際上視頻是一個很複雜的部分,顯示適配器可以被設置成不同模式,用來顯示更多的色彩圖像動畫。
我們就用開機默認的80x25文本模式,佔用範圍爲0xB8000~0XBFFFF,顯存大小爲32KB,每2個字節代表一個字符。

VGA視頻系統的寄存器

如何讓系統顯示指定位置的內容?
通過端口操作設置相應的寄存器就可以了。


TTY任務

在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任務代碼示意:
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調用過程示意圖:
printf調用過程示意圖


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章