本文正在参加「金石计划」
项目要求
project0主要是为了帮助我们熟悉GeekOS的环境。它的任务如下:
它的提示如下:
由此可知,我们的任务是在main.c中调用kthread.c中的
Start_Kernel_Thread()函数。
流程概览
先来结合英文注释看看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
//startFunc指的是新线程所调用的功能函数,也就是说我们可以实现一个读取字符并显示的函数,把它作为startFunc,传入Start_Kernel_Thread()
* arg - is a paramter to pass to the new function
//待传入功能函数中的参数,但我们这个读取字符的函数并不需要这样的参数,可以先定为默认0
* priority - the priority of this thread (use PRIORITY_NORMAL) for
* most things
//线程优先级,决定哪一个线程优先执行
* detached - use false for kernel threads
//用来区分用户线程和内核线程,false是内核线程,true是用户线程,后续被Create_Thread()调用,而Create_Thread()调用的Init_Thread()在置owner位时会区分这两者
*/
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;
}
由上述可知:
- geekos创建内核级线程的过程是:调用Create_Thread()函数,如果创建成功,就会建立内核线程,并且使得线程具有原子性。
- 而在Create_Thread()这一步中,主要是为了给线程和它的栈申请内存空间,并且要确保申请成功,否则得返回空指针。
/*
* Create a new raw thread object.
* Returns a null pointer if there isn't enough memory.
*/
static struct Kernel_Thread* Create_Thread(int priority, bool detached)
{
struct Kernel_Thread* kthread;
void* stackPage = 0;
/*
* For now, just allocate one page each for the thread context
* object and the thread's stack.
*/
kthread = Alloc_Page();
if (kthread != 0)
stackPage = Alloc_Page();
/* Make sure that the memory allocations succeeded. */
if (kthread == 0)
return 0;
if (stackPage == 0) {
Free_Page(kthread);
return 0;
}
/*Print("New thread @ %x, stack @ %x\n", kthread, stackPage); */
/*
* Initialize the stack pointer of the new thread
* and accounting info
*/
Init_Thread(kthread, stackPage, priority, detached);
/* Add to the list of all threads in the system. */
Add_To_Back_Of_All_Thread_List(&s_allThreadList, kthread);
return kthread;
}
而其中的Init_Thread(),就是在给线程这个结构体的参数赋值:
static void Init_Thread(struct Kernel_Thread* kthread, void* stackPage,
int priority, bool detached)
{
static int nextFreePid = 1;
struct Kernel_Thread* owner = detached ? (struct Kernel_Thread*)0 : g_currentThread;
memset(kthread, '\0', sizeof(*kthread));
kthread->stackPage = stackPage;
kthread->esp = ((ulong_t) kthread->stackPage) + PAGE_SIZE;
kthread->numTicks = 0;
kthread->priority = priority;
kthread->userContext = 0;
kthread->owner = owner;
/*
* The thread has an implicit self-reference.
* If the thread is not detached, then its owner
* also has a reference to it.
*/
kthread->refCount = detached ? 1 : 2;
kthread->alive = true;
Clear_Thread_Queue(&kthread->joinQueue);
kthread->pid = nextFreePid++;
}
内核线程的结构如kthread.h中所示:
struct Kernel_Thread {
ulong_t esp; /* offset 0 */
volatile ulong_t numTicks; /* offset 4 */
int priority;
DEFINE_LINK(Thread_Queue, Kernel_Thread);
void* stackPage;
struct User_Context* userContext;
struct Kernel_Thread* owner;
int refCount;
/* These fields are used to implement the Join() function */
bool alive;
struct Thread_Queue joinQueue;
int exitCode;
/* The kernel thread id; also used as process id */
int pid;
/* Link fields for list of all threads in the system. */
DEFINE_LINK(All_Thread_List, Kernel_Thread);
/* Array of MAX_TLOCAL_KEYS pointers to thread-local data. */
#define MAX_TLOCAL_KEYS 128
const void* tlocalData[MAX_TLOCAL_KEYS];
};
- 创建成功之后,是建立和原子化的步骤。 setup就是将一些功能函数相关的信息推入线程的栈中:
/*
* Set up the initial context for a kernel-mode-only thread.
*/
static void Setup_Kernel_Thread(
struct Kernel_Thread* kthread,
Thread_Start_Func startFunc,
ulong_t arg)
{
/*
* Push the argument to the thread start function, and the
* return address (the Shutdown_Thread function, so the thread will
* go away cleanly when the start function returns).
*/
Push(kthread, arg);
Push(kthread, (ulong_t) &Shutdown_Thread);
/* Push the address of the start function. */
Push(kthread, (ulong_t) startFunc);
/*
* To make the thread schedulable, we need to make it look
* like it was suspended by an interrupt. This means pushing
* an "eflags, cs, eip" sequence onto the stack,
* as well as int num, error code, saved registers, etc.
*/
/*
* The EFLAGS register will have all bits clear.
* The important constraint is that we want to have the IF
* bit clear, so that interrupts are disabled when the
* thread starts.
*/
Push(kthread, 0UL); /* EFLAGS */
/*
* As the "return address" specifying where the new thread will
* start executing, use the Launch_Thread() function.
*/
Push(kthread, KERNEL_CS);
Push(kthread, (ulong_t) &Launch_Thread);
/* Push fake error code and interrupt number. */
Push(kthread, 0);
Push(kthread, 0);
/* Push initial values for general-purpose registers. */
Push_General_Registers(kthread);
/*
* Push values for saved segment registers.
* Only the ds and es registers will contain valid selectors.
* The fs and gs registers are not used by any instruction
* generated by gcc.
*/
Push(kthread, KERNEL_DS); /* ds */
Push(kthread, KERNEL_DS); /* es */
Push(kthread, 0); /* fs */
Push(kthread, 0); /* gs */
}
从这个入栈函数的构造可以看出,栈是向下生长的(往内存地址减小的方向)
static __inline__ void Push(struct Kernel_Thread* kthread, ulong_t value)
{
kthread->esp -= 4;
*((ulong_t *) kthread->esp) = value;
}
如何保证原子性呢?关键在于将线程入队的时候要屏蔽中断。
/*
* Atomically make a thread runnable.
* Assumes interrupts are currently enabled.
*/
void Make_Runnable_Atomic(struct Kernel_Thread* kthread)
{
Disable_Interrupts();
Make_Runnable(kthread);
Enable_Interrupts();
}
assert(断言)是一种除错机制,用于验证代码是否符合预期。感觉这里的KASSERT也有强制检查的意味。
void Make_Runnable(struct Kernel_Thread* kthread)
{
KASSERT(!Interrupts_Enabled());
Enqueue_Thread(&s_runQueue, kthread);
}
功能实现思路及代码
上面过完了操作系统创建内核线程的流程,现在来实现一下要用到的功能函数,由于和project0相关,为了以示区分,我们就将其命名为project0。
实现函数的思路也很简单,就是
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);
}
}
}
}
}
- 为什么要涉及到KEY_SPECIAL_FLAG呢?
因为keyboard.h里有如下说明:
所以要保证位8(实际上是右数第9位)为0,才能是读取ascii码
代码运行
进入build目录下
make depend
make
bochs
启动后输入的字符就会被实时显示出来。