GeekOS project0 -- 接收鍵盤輸入並在螢幕回顯
阿新 • • 發佈:2019-01-23
原始碼包中的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函式的程式碼為:
很明顯前面的一系列Init都是初始化系統的各個元件,接下來列印了之前搭建除錯平臺看到的"Welcome to GeekOS!",Print函式就是用來在螢幕上列印字元的,功能大致類似於C語言標準庫中的printf。
再往下是一條TODO語句,該語句的定義在src/project0/include/geekos/kassert.h中,內容如下:
可以看到,這是一個巨集,而且這個巨集在設定終端顏色屬性,列印錯誤提示後,就直接進入一個死迴圈中,也就是執行到TODO之後程式就不會繼續往下運行了,所以要繼續進行除錯project0就必須刪除或者註釋掉那條TODO。接下來我們就在TODO的位置按照project0的要求填充我們的程式碼。
首先是建立核心模式的執行緒,核心模式執行緒相關的程式碼都在src/project0/src/geekos/kthread.c中,找到我們需要的Start_Kernel_Thread函式:
它完成了幾個功能,建立執行緒,假如建立成功就進行一些設定並加入執行佇列中。
我們就需要呼叫這個函式建立我們的核心執行緒。
這個函式有4個引數,第一個是該執行緒需要呼叫的函式,這個函式是我們自己寫的,第二個是引數,如果沒有引數就是0,第三個是執行緒的優先順序,執行緒的優先順序定義都在src/project0/include/geekos/kthread.h中:
我們需要普通的優先順序就可以了,也就是PRIORITY_NORMAL,接下來是detached引數,核心程序設定為false。
我們建立一個函式,用來顯示提示資訊,下面接收按鍵的內容先不填,內容如下:
Main()函式中加上建立核心執行緒的部分,內容如下:
struct KThread *kthread = Start_Kernel_Thread(&foo, 0, PRIORITY_NORMAL, false);
啟動核心,就能看到我們需要列印的這行字“Hello from Cheryl”。
接下來的問題是,如何接收並且回顯按鍵。有關按鍵的原始碼都在/src/geekos/keyboard.c中,可以看到這麼個函式:
每當核心接收到一個按鍵的時候,就會將這個按鍵放入一個佇列,接收到多個按鍵佇列中就有多個按鍵。Wait_For_Key的功能就是從佇列中取出一個按鍵,如果佇列中沒有按鍵那就等待鍵盤輸入一個按鍵。該函式返回值為Keycode,這種型別是一個16位的整數,定義都在include/geekos/keyboard.h裡。
可以看到控制Control、Alt、Shift的位都在高8位中,而低8位則儲存的是ASCII碼,所以foo函式程式碼為:
首先從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);
}
}
}
程式效果: