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);
		}
	}
}

程序效果:



没有更多推荐了,返回首页