项目0要求实现一个内核进程,功能是实现从键盘接收一个按键,并在屏幕上显示。主要是让学生熟悉GeekOS的编译、运行过程,了解计算机系统的启动原理。
在这个项目里面主要用到两部分的内容:内核线程和键盘处理,相应的文件是kthread.c和keyboard.c。GeekOS 在/src/geekos/keyboard.c 和/include/geekos/keyboard.h 分别给出了从键盘获 取字符函数 Read_Key()的实现以及相应键位所对应的函数返回值,我们在实现 project0() 这个函数的功能时需要调用到 Read_Key()这个函数,然后根据其返回值分析出所输入的 字符属于什么类型,如果是特殊键就暂时不做处理,等待遇到非特殊键再进行处理。 GeekOS 的启动函数是/src/geekos/main.c 中的 Main(),我们需要在系统初始化完成之后开始执行我们想要执行的代码(即 project0()中的代码),因此需要创建一个内核线 程并将 project0()这个函数作为参数传入,随后系统会自动调度执行。
内核线程结构的定义如下:
struct Kernel_Thread {
unsigned long esp;
volatile unsigned long numTicks;
int priority;
DEFINE_LINK( Thread_Queue, Kernel_Thread );
void* stackPage;
struct User_Context* userContext;
struct Kernel_Thread* owner;
int refCount;
Boolean alive;
struct Mutex joinLock;
struct Condition joinCond;
};
esp字段用来存放一个线程挂起的堆栈指针;
stackPage字段指向内核线程的堆栈页面
numTicks和priority分别被调度程序用来实现基于先占权和基于优先权的时间片调度。
DEFINE_LINK宏定义一个内核线程在线程队列上时的前一个和后一个字段。
userContext字段如果不为空,则指向一个线程用户环境,它是一个允许线程执行用户模式的代码和数据的组合段。
内核线程有两种方式创建。在内核里独立运行的线程可通过Start_Kernel_Thread()函数来创建,该函数通过一个指针指向一个执行线程体的启动函数。线程所执行的用户模式的程序由Start_User_Thread()函数创建,并且用一指针指向一个用户环境和用户环境内存中代码入口点的地址。调用Exit()函数销毁内核线程。
首先在主程序里调用Start_Kernel_Thread 开始创建一条内核线程:Mythread=Start_Kernel_Thread (&MyFunction,0,PRIORITY_NORMAL,false);
入口参数分别为:函数地址,函数参数(无参数就写0),优先级设定,线程属性(false为内核线程,true为用户线程),返回值Mythread 的数据类型是static struct Kernel_Thread* thread
Create_Thread(priority, detached) //根据优先级创建一条线程
kthread = Alloc_Page() //为线程分配内存空间
stackPage = Alloc_Page()
Init_Thread(kthread, stackPage, priority, detached)
Add_To_Back_Of_All_Thread_List(&s_allThreadList, kthread)
Setup_Kernel_Thread(kthread, startFunc, arg)//配置内核线程的初始化
Make_Runnable_Atomic(kthread); //设置线程运行的原子性操作
Disable_Interrupts(); //禁止中断
Make_Runnable(kthread); //线程运Enable_Interrupts(); //使能中断
在keyboard.c里面提供了一个功用函数Keycode Wait_For_Key(void),循环等待一个键盘事件,然后返回一个16位的数据 Keycode型的, 在keyboard.h里定义了所有的键盘代码。Read_Key(Keycode* keycode)函数可以处理队列键盘按键,可以保存到队列中并输出。关于Keycode的定义是:
低8位用来表示键盘值,通过s_scanTableNoShift和s_scanTableWithShift这两个数组来转换相应的键盘码为所表示字符的ASCII码,高六位分别是:
KEY_SPECIAL_FLAG (特殊键,比如F1,F2) 用返回的值key&0x0100 就可以判断是否按下特殊健,1为有效,说明是特殊健,0则不是,以下的几种情况类似
KEY_KEYPAD_FLAG (小键盘键) 0x0200
KEY_SHIFT_FLAG (左,右SHIFT) 0x1000
KEY_ALT_FLAG (左,右ALT) 0x2000
KEY_CTRL_FLAG (左,右CTRL) 0x4000
KEY_RELEASE_FLAG (键弹起来标志位) 0x8000
1) 编写一个C语言函数,函数功能是:接收键盘输入的按键,并将键值在显示器显示出来,当输入ctrl+d就退出;
/project0/src/geekos/main.c:
void project0(){
Print("To Exit hit Ctrl + d.\n");
Keycode keycode;
while(1)
{
if(Read_Key(&keycode))
{
if(!((keycode & KEY_SPECIAL_FLAG) || (keycode & KEY_RELEASE_FLAG)))// 不是特殊键或者弹起
{
int asciiCode = keycode & 0xff;//d
if((keycode & KEY_CTRL_FLAG)==KEY_CTRL_FLAG && asciiCode=='d')//ctrl+d
{
Print("\n---------BYE!---------\n");
Exit(1);
}else
{
Print("%c",(asciiCode=='\r') ? '\n' : asciiCode);
}
}
}
}
}
2) 在Main函数体内调用Start_Kernel_Thread函数,将步骤1编写的函数地址传递给参数startFunc,利用Setup_Kernel_Thread函数建立一个待运行的线程。
struct Kernel_Thread *thread;
thread = Start_Kernel_Thread(&project0,0,PRIORITY_NORMAL,false);
3) 在Linux环境下编译系统得到GeekOS镜像文件。
进入Build目录下:
$ make depend
$ make
4) 编写一个相应的bochs配置文件。
cd ~/geekos-0.3.0/src/project0/build/
gedit .bochsrc
megs: 8
boot: a
floppya: 1_44=fd.img, status=inserted
Log: ./bochs.out
5) 在bochs中运行GeekOS系统显示结果。
运行Bochs模拟器,执行GeekOS内核。
$ cd ……/build
$ bochs
运行后启动界面:
此时可以从键盘向虚拟机输入一下文字,输入完毕后按“Ctrl+d”组合键可退出程序,测试结果如下: