🚩 核心目標

今天的目標是賦予 SmallOS 「溝通的能力」。我們不僅要處理更複雜的鍵盤狀態(如 Shift 與 Caps Lock),還要在此基礎上建立一個簡單的命令解釋器(Shell)。此外,我們優化了自動化構建流程,讓 Makefile 能夠根據核心大小自動調整磁碟鏡像結構。


1. 鍵盤處理增強:狀態機與組合鍵

在之前的開發中,我們只能識別單個按鍵。今天我們引入了狀態紀錄,讓內核能夠正確處理以下邏輯:

void keyboard_callback(u32) {
    // 讀取掃描碼
    u8 scancode = port_byte_in(0x60);

    if (scancode == 0x3A)
    {
        capslock_pressed = capslock_pressed ^ 1;
        return;
    }

    // 檢查是否為 Shift 鍵的掃描碼
    // 左 Shift: 0x2A, 右 Shift: 0x36
    if (scancode == 0x2A || scancode == 0x36) 
    {
        shift_pressed = 1;
        return;
    }

    // 放開 Shift (掃描碼 = 按下碼 + 0x80)
    // 左放開: 0xAA, 右放開: 0xB6
    if (scancode == 0xAA || scancode == 0xB6) 
    {
        shift_pressed = 0;
        return;
    }    

    // 如果掃描碼最高位是 1 (>= 0x80),代表按鍵放開 (Key Released)
    if (scancode & 0x80) 
    {
        // 目前暫不處理放開事件
        return;
    }

    if (scancode == 0x0E) 
    { // Backspace
        if (buffer_idx > 0)
        {
            buffer_idx --;
            perform_backspace();
        }
    } else if (scancode == 0x1C) 
    { // Enter
        // a command is ready
        key_buffer[buffer_idx] = '\\0';
        command_ready = 1;
        buffer_idx = 0;
        kprint("\\n");
        set_backspace_min();
    } else if (scancode <= 0x39) 
    {
        char letter = shift_pressed ? ascii_shift[(int)scancode] : ascii_nomod[(int)scancode];

        // for letter, we need to combin with capslock_pressed
        if (('a' <= letter && letter <= 'z') || ('A' <= letter && letter <= 'Z'))
        {
            if (capslock_pressed == 1)
            {
                letter = (shift_pressed) ? ascii_nomod[(int)scancode] : ascii_shift[(int)scancode];
            }else
            {
                letter = (shift_pressed) ? ascii_shift[(int)scancode] : ascii_nomod[(int)scancode];
            }
        }
        
        if (letter != 0 && buffer_idx < 255 && command_ready == 0) {
            key_buffer[buffer_idx++] = letter;
            char str[2] = {letter, '\\0'};
            kprint(str);
        }
    }
}

2. 第一個 Shell:命令解釋器

我們實作了一個簡單的 Shell 循環,它會接收鍵盤緩衝區的輸入,並在按下 Enter 時進行字串比對。目前支持的命令:

#include "string.h"
#include "display.h"
#include "types.h"
#include "shell.h"
#include "io.h"

void shell_print_prompt() 
{
    kprint("\\nSmallOS > ");
}

/**
 * 曾對QEMU的shutdown指令,不一定適合真實機器
 */
void shutdown() {
    kprint("Shutting down Small OS...\\n");
    
    // 方法 A: QEMU 專用的 ACPI 關機 (適用於多數 QEMU 預設配置)
    // 寫入 0x2000 到 0x604 連接埠
    port_word_out(0x604, 0x2000);

    // 方法 B: 較舊版本或特定 BIOS 的關機
    port_word_out(0xB004, 0x2000);

    // 方法 C: 如果是在真實機器上,這通常會觸發「三倍故障 (Triple Fault)」
    // 雖然不是優雅的關機,但在沒實作 ACPI 的情況下,這是讓機器停止運行的最後手段
    kprint("Shutdown failed. Halting CPU.\\n");
    __asm__ __volatile__("cli");
    while(1) { __asm__ __volatile__("hlt"); }
}

/**
 * 這個是熱啟動,只有CPU被復位
 */
void reboot() 
{
    kprint("Rebooting system...\\n");

    // 1. 關閉所有中斷,防止在重啟過程中被干擾
    __asm__ __volatile__("cli");

    // 2. 向鍵盤控制器發送重啟命令
    u8 temp;
    // 等待鍵盤控制器緩衝區清空
    do {
        temp = port_byte_in(0x64);
    } while (temp & 0x02);
    
    port_byte_out(0x64, 0xFE);

    // 3. 關鍵:如果硬體重啟慢了,我們絕對不能讓 CPU 執行後面的代碼
    // 進入一個死循環,原地等待硬體重置
    while(1) {
        __asm__ __volatile__("hlt");
    }    
}

/**
 * 執行shell指令
 */
void shell_execute(char *input)
{
    if (k_str_len(input) == 0) return;

    char *args = k_str_split_first(input, ' ');

    input = k_to_lowercase(input);

    if (k_str_cmp(input, "help") == 0)
    {
        kprint("SmallOS Shell. Available commands: help, echo, clear, reboot, shutdown");
    }else if (k_str_cmp(input, "echo") == 0)
    {
        if (args != NULL)
        {
            kprint(args);
        }
    }else if (k_str_cmp(input, "clear") == 0)
    {
        clear_screen();
        set_cursor_offset(get_screen_offset(0, 0));
    }else if (k_str_cmp(input, "reboot") == 0) 
    {
        reboot();
    }else if (k_str_cmp(input, "shutdown") == 0)
    {
        shutdown();
    }
    else
    {
        kprintf("Unknow command: %s\\n", input);        
    }
}

3. Makefile 進化:自動化磁碟計算

隨著 Shell 相關代碼的加入,內核體積(kernel.bin)迅速增長。手動修改磁區數量(Sectors)既麻煩又危險。我們更新了 Makefile,讓它自動計算所需的磁區數,確保磁碟鏡像(os-image.bin)永遠結構正確。