1. 程式人生 > >GeekOS project0 -- 接收鍵盤輸入並在螢幕回顯

GeekOS project0 -- 接收鍵盤輸入並在螢幕回顯

原始碼包中的geekos-0.3.0/doc/hacking.pdf中有GeekOS的簡略介紹,專案概覽等等,同時在程式碼包中中給出了project0到project6等幾個專案,這幾個專案是部分程式碼缺失的,需要根據專案的要求填充缺失的程式碼實現所需要的功能,同時在這個過程中瞭解這個小型作業系統的工作原理。

首先從project0開始。project0的目標很簡單,首先建立一個核心模式執行緒,該執行緒要能夠列印"Hello from xxx",xxx是你的名字,而且還能重複呼叫Wait_For_Key接收鍵盤輸入並且在螢幕上回顯該字元,直到接收到終止符(control-d)子程式。
首先不要太緊張,這個任務所需要的程式碼量很少,只是為了熟悉環境而準備。該修改的程式碼都放在src/geekos/main.c目錄下的Main()函式中。
建立核心執行緒的函式是Start_Kernel_Thread(),該函式的定義在src/geekos/kthread.c中。

首先開啟main.c,可以看到Main函式的程式碼為:
/*
 * Kernel C code entry point.
 * Initializes kernel subsystems, mounts filesystems,
 * and spawns init process.
 */
void Main(struct Boot_Info* bootInfo)
{
    Init_BSS();
    Init_Screen();
    Init_Mem(bootInfo);
    Init_CRC32();
    Init_TSS();
    Init_Interrupts();
    Init_Scheduler();
    Init_Traps();
    Init_Timer();
    Init_Keyboard();
    Set_Current_Attr(ATTRIB(BLACK, GREEN|BRIGHT));
    Print("Welcome to GeekOS!\n");
    Set_Current_Attr(ATTRIB(BLACK, GRAY));
    TODO("Start a kernel thread to echo pressed keys and print counts");
    /* Now this thread is done. */
    Exit(0);
}

很明顯前面的一系列Init都是初始化系統的各個元件,接下來列印了之前搭建除錯平臺看到的"Welcome to GeekOS!",Print函式就是用來在螢幕上列印字元的,功能大致類似於C語言標準庫中的printf。
再往下是一條TODO語句,該語句的定義在src/project0/include/geekos/kassert.h中,內容如下:
#define TODO(message)					\
do {							\
    Set_Current_Attr(ATTRIB(BLUE, GRAY|BRIGHT));	\
    Print("Unimplemented feature: %s\n", (message));	\
    while (1)						\
	;						\
} while (0)


可以看到,這是一個巨集,而且這個巨集在設定終端顏色屬性,列印錯誤提示後,就直接進入一個死迴圈中,也就是執行到TODO之後程式就不會繼續往下運行了,所以要繼續進行除錯project0就必須刪除或者註釋掉那條TODO。接下來我們就在TODO的位置按照project0的要求填充我們的程式碼。

首先是建立核心模式的執行緒,核心模式執行緒相關的程式碼都在src/project0/src/geekos/kthread.c中,找到我們需要的Start_Kernel_Thread函式:
/*
 * Start a kernel-mode-only thread, using given function as its body
 * and passing given argument as its parameter.  Returns pointer
 * to the new thread if successful, null otherwise.
 *
 * startFunc - is the function to be called by the new thread
 * arg - is a paramter to pass to the new function
 * priority - the priority of this thread (use PRIORITY_NORMAL) for
 *    most things
 * detached - use false for kernel threads
 */
struct Kernel_Thread* Start_Kernel_Thread(
    Thread_Start_Func startFunc,
    ulong_t arg,
    int priority,
    bool detached
)
{
    struct Kernel_Thread* kthread = Create_Thread(priority, detached);
    if (kthread != 0) {
	/*
	 * Create the initial context for the thread to make
	 * it schedulable.
	 */
	Setup_Kernel_Thread(kthread, startFunc, arg);
	/* Atomically put the thread on the run queue. */
	Make_Runnable_Atomic(kthread);
    }
    return kthread;
}


它完成了幾個功能,建立執行緒,假如建立成功就進行一些設定並加入執行佇列中。
我們就需要呼叫這個函式建立我們的核心執行緒。
struct Kernel_Thread* Start_Kernel_Thread(
    Thread_Start_Func startFunc,
    ulong_t arg,
    int priority,
    bool detached
)

這個函式有4個引數,第一個是該執行緒需要呼叫的函式,這個函式是我們自己寫的,第二個是引數,如果沒有引數就是0,第三個是執行緒的優先順序,執行緒的優先順序定義都在src/project0/include/geekos/kthread.h中:
/*
 * Thread priorities
 */
#define PRIORITY_IDLE    0
#define PRIORITY_USER    1
#define PRIORITY_LOW     2
#define PRIORITY_NORMAL  5
#define PRIORITY_HIGH   10

我們需要普通的優先順序就可以了,也就是PRIORITY_NORMAL,接下來是detached引數,核心程序設定為false。
我們建立一個函式,用來顯示提示資訊,下面接收按鍵的內容先不填,內容如下:
void foo(void)
{
	Print("Hello from Cheryl\n");
	while (1)
	{
	}
}

Main()函式中加上建立核心執行緒的部分,內容如下:
struct KThread *kthread = Start_Kernel_Thread(&foo, 0, PRIORITY_NORMAL, false);
啟動核心,就能看到我們需要列印的這行字“Hello from Cheryl”。
接下來的問題是,如何接收並且回顯按鍵。有關按鍵的原始碼都在/src/geekos/keyboard.c中,可以看到這麼個函式:
Keycode Wait_For_Key(void)
{
    bool gotKey, iflag;
    Keycode keycode = KEY_UNKNOWN;
    iflag = Begin_Int_Atomic();
    do {
	gotKey = !Is_Queue_Empty();
	if (gotKey)
	    keycode = Dequeue_Keycode();
	else
	    Wait(&s_waitQueue);
    }
    while (!gotKey);
    End_Int_Atomic(iflag);
    return keycode;
}

每當核心接收到一個按鍵的時候,就會將這個按鍵放入一個佇列,接收到多個按鍵佇列中就有多個按鍵。Wait_For_Key的功能就是從佇列中取出一個按鍵,如果佇列中沒有按鍵那就等待鍵盤輸入一個按鍵。該函式返回值為Keycode,這種型別是一個16位的整數,定義都在include/geekos/keyboard.h裡。
/*
 * Flags
 */
#define KEY_SPECIAL_FLAG 0x0100
#define KEY_KEYPAD_FLAG  0x0200
#define KEY_SHIFT_FLAG   0x1000
#define KEY_ALT_FLAG     0x2000
#define KEY_CTRL_FLAG    0x4000
#define KEY_RELEASE_FLAG 0x8000

可以看到控制Control、Alt、Shift的位都在高8位中,而低8位則儲存的是ASCII碼,所以foo函式程式碼為:
void foo(void)
{
	Keycode key;
	Print("Hello from cheryl\n");
	while (1)
	{
		key = Wait_For_Key();
		if (!(key & KEY_RELEASE_FLAG))
		{
			if ((key & KEY_CTRL_FLAG) && (key & 0xFF) == 'd')
			{
				Print("\nreceived control-d\n");
				break;
			}
			else Print("%c", key);
		}
	}
}

程式效果: