来看Main中的下一个函数Init_Scheduler
Init_Scheduler函数开启了系统的多任务机制。
位于./src/geekos/kthread.c
void Init_Scheduler(void)
{
struct Kernel_Thread* mainThread = (struct Kernel_Thread *) KERN_THREAD_OBJ;
/*
* Create initial kernel thread context object and stack,
* and make them current.
*/
Init_Thread(mainThread, (void *) KERN_STACK, PRIORITY_NORMAL, true);
g_currentThread = mainThread;
Add_To_Back_Of_All_Thread_List(&s_allThreadList, mainThread);
/*
* Create the idle thread.
*/
/*Print("starting idle thread\n");*/
Start_Kernel_Thread(Idle, 0, PRIORITY_IDLE, true);
/*
* Create the reaper thread.
*/
/*Print("starting reaper thread\n");*/
Start_Kernel_Thread(Reaper, 0, PRIORITY_NORMAL, true);
}
粗粗的看一下,可以看到它初始化了内核线程mainThread,启动了一个Idle线程和Reaper线程。
内核线程mainThread表示的是现在运行的Main函数这个线程。
看一下struct Kernel_Thread内核线程结构
位于./include/geekos/kthread.h
/*
* Kernel thread context data structure.
* NOTE: there is assembly code in lowlevel.asm that depends
* on the offsets of the fields in this struct, so if you change
* the layout, make sure everything gets updated.
*/
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];
};
Kernel_Thread是一个内核线程的信息结构体,包括pid,优先级,堆栈指针,退出码,双向链表等。
看Init_Scheduler中的下一句Init_Thread
位于./src/geekos/kthread.c
/*
* Initialize a new Kernel_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++;
}
Init_Thread函数为线程分配了栈空间,并初始化优先级priority,线程运行滴答numticks等。
下一句
g_currentThread = mainThread
把mainThread赋给全局变量g_currentThread,表示此线程当前在运行
Add_To_Back_Of_All_Thread_List(&s_allThreadList, mainThread)将此结构体链入到结构体链表中
再看下一句
Start_Kernel_Thread(Idle, 0, PRIORITY_IDLE, true)
和Start_Kernel_Thread(Reaper, 0, PRIORITY_NORMAL, true)
启动了Idle和Reaper两个线程,这两个内核线程会在后面详细说明。
Start_Kernel_Thread位于./src/geekos/kthread.c
/*
* 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;
}
Create_Thread用来为线程分配栈空间,初始化线程结构;Setup_Kernel_Thread用来将此线程的上下文压入堆栈中,压入的上下文是一些初始值;
Make_Runnable_Atomic用来将此线程链入到运行队列中。
先看Create_Thread函数
位于/src/geekos/kthread.c
/*
* 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;
}
Create_Thread函数首先用Alloc_Page分配了一个页的线程结构体和一个页的堆栈,这两个物理页就是一个线程的家当了。
Alloc_Page会在系统可用页链表中搜索一个可用页,然后返回这个页的首地址。
然后调用Init_Thread初始化线程,并将此线程结构链入到链表s_allThreadList中,s_allThreadList是系统中所有线程的链表,包括可运行的线程和休眠线程。Init_Thread在前面初始化mainthread时已经看到了。
Setup_Kernel_Thread将创建的线程的上下文压入到堆栈中。
位于./src/geekos/kthread.c
/*
* 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);//线程被初次调度运行时,首先运行的是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);//首次这里压入的通用寄存器值全为0
/*
* 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 */
}
可以看到Setup_Kernel_Thread向堆栈中压入了线程的个方面的信息,包括线程参数,线程启动函数和退出函数,各寄存器信息等。
注意这里入栈的参数先后顺序是事先约定的。
Push(kthread,0UL)开始到最后这些压入的参数是用于还原现场的。
极其要注意首先入栈的那几个参数,这是理解线程执行的关键。
当切换到线程的时候,先还原上下文,然后跳到Launch_Thread去执行,Launch_Thread执行完后,最后一句ret指令,系统从栈中弹出函数地址startFunc继续执行,
startFunc执行完后,最后一句汇编ret再弹出函数Shutdown_Thread结束线程。
怎么理解这种用压入函数地址来调用函数的方式呢。
可以用如下代码来理解
...
call Shutdown_Thread
...
Shutdown_Thread:
call startFunc
...
...
ret
startFunc:
call Launch_Thread
...
...
ret
Launch_Thread
...
...
...
ret
这样,栈中就会连续的压入Shutdown_Thread,startFunc,Launch_Thread的入口地址,依次执行Launch_Thread,startFunc,Shutdown_Thread,然后在ret中依次返回。
执行ret,CPU就会从栈中取出4B载入到eip中执行。
/*
* Push a dword value on the stack of given thread.
* We use this to set up some context for the thread before
* we make it runnable.
*/
static __inline__ void Push(struct Kernel_Thread* kthread, ulong_t value)
{
kthread->esp -= 4;
*((ulong_t *) kthread->esp) = value;
}
kthread->esp在初始化线程的时候指向线程的堆栈底部,注意它是先减4再赋值。
回到Start_Kernel_Thread中看最后一句Make_Runnable_Atomic(kthread),将线程链路到可运行队列中,由于可运行队列是全局可见的,所以这里加了临界保护。
位于./src/geekos/kthread.c
/*
* 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();
}
看Make_Runnable函数,仍旧在kthread.c中
/*
* Add given thread to the run queue, so that it
* may be scheduled. Must be called with interrupts disabled!
*/
void Make_Runnable(struct Kernel_Thread* kthread)
{
KASSERT(!Interrupts_Enabled());
Enqueue_Thread(&s_runQueue, kthread);
}
Enqueue_Thread将线程加入到系统的运行队列中,至此线程正式开始了它的生命周期,将会被调度执行。
至此Init_Scheduler()函数结束。
可以看到它初始化了内核主线程,以及两个Idle和Reaper两个内核线程。
还有一些具体细节会在下次添加。
Idle是系统初始化时产生的线程,其优先级为PRIORITY_IDLE,一直位于系统的可运行队列中(因为是while(1)循环),是系统中最低的级别,也就是说,只有系统运行队列中没有其他的可运行进程了,才会调度Idle运行。
位于./src/geekos/kthread.c
/*
* This is the body of the idle thread. Its job is to preserve
* the invariant that a runnable thread always exists,
* i.e., the run queue is never empty.
*/
static void Idle(ulong_t arg)
{
while (true)
Yield();
}
可以看到,Idle任务循环调用Yield函数。
Yield函数用于调度系统中的线程运行。
位于./src/geekos/kthread.c,因为会操作到全局的运行队列,所以要进行临界区保护。
/*
* Voluntarily give up the CPU to another thread.
* Does nothing if no other threads are ready to run.
*/
void Yield(void)
{
Disable_Interrupts();
Make_Runnable(g_currentThread);
Schedule();
Enable_Interrupts();
}
Yield中主要有Make_Runnable和Schedule两个函数,Make_Runnable将当前进程进入运行队列,Schedule调度运行队列中的线程。
看Make_Runnable()是做什么的
位于./src/geekos/kthread.c
/*
* Add given thread to the run queue, so that it
* may be scheduled. Must be called with interrupts disabled!
*/
void Make_Runnable(struct Kernel_Thread* kthread)
{
KASSERT(!Interrupts_Enabled());
Enqueue_Thread(&s_runQueue, kthread);
}
Enqueue_Thread函数把内核线程链入到运行队列中去
参数s_runQueue是运行队列的头,定义如下
位于./src/geekos/kthread.c
/*
* Queue of runnable threads.
*/
static struct Thread_Queue s_runQueue;
再看Thread_Queue的定义
位于./include/geekos/kthread.h
DEFINE_LIST(Thread_Queue, Kernel_Thread);
这里使用DEFINE_LIST宏定义了链表头Thread_Queue的结构
再看一下函数Enqueue_Thread()
位于./include/geekos/kthread.h
static __inline__ void Enqueue_Thread(struct Thread_Queue *queue, struct Kernel_Thread *kthread) {
Add_To_Back_Of_Thread_Queue(queue, kthread);
}
就是把线程结构体加入到内核的可运行队列中去。
回到Yield函数看下一个函数Schedule()
位于./src/geekos/kthread.c
/*
* Schedule a thread that is waiting to run.
* Must be called with interrupts off!
* The current thread should already have been placed
* on whatever queue is appropriate (i.e., either the
* run queue if it is still runnable, or a wait queue
* if it is waiting for an event to occur).
*/
void Schedule(void)
{
struct Kernel_Thread* runnable;
/* Make sure interrupts really are disabled */
KASSERT(!Interrupts_Enabled());
/* Preemption should not be disabled. */
KASSERT(!g_preemptionDisabled);
/* Get next thread to run from the run queue */
runnable = Get_Next_Runnable();
/*
* Activate the new thread, saving the context of the current thread.
* Eventually, this thread will get re-activated and Switch_To_Thread()
* will "return", and then Schedule() will return to wherever
* it was called from.
*/
Switch_To_Thread(runnable);
}
看Get_Next_Runnable()函数,它从运行队列中取出下一个运行线程
位于./src/geekos/kthread.c
/*
* Get the next runnable thread from the run queue.
* This is the scheduler.
*/
struct Kernel_Thread* Get_Next_Runnable(void)
{
struct Kernel_Thread* best = 0;
best = Find_Best(&s_runQueue);
KASSERT(best != 0);
Remove_Thread(&s_runQueue, best);
/*
* Print("Scheduling %x\n", best);
*/
return best;
}
看Find_Best()函数,它返回运行队列中优先级最高的线程
位于./src/geekos/kthread.c
/*
* Find the best (highest priority) thread in given
* thread queue. Returns null if queue is empty.
*/
static __inline__ struct Kernel_Thread* Find_Best(struct Thread_Queue* queue)
{
/* Pick the highest priority thread */
struct Kernel_Thread *kthread = queue->head, *best = 0;
while (kthread != 0) {
if (best == 0 || kthread->priority > best->priority)
best = kthread;
kthread = Get_Next_In_Thread_Queue(kthread);
}
return best;
}
再回到Get_Next_Runnable()中,下一个函数Remove_Thread()
把线程best从运行队列中删除了。
Remove_Thread位于./include/geekos/kthread.h
static __inline__ void Remove_Thread(struct Thread_Queue *queue, struct Kernel_Thread *kthread) {
Remove_From_Thread_Queue(queue, kthread);
}
回到Schedule()
得到下一个要运行的线程结构体之后,调用函数Switch_To_Thread切换到此线程。
Switch_To_Thread这个函数保存了当前线程(即Idle这个线程)的上下文,然后切换到新线程的堆栈中。
Switch_To_Thread位于./src/geekos/lowlevel.asm
; ----------------------------------------------------------------------
; Switch_To_Thread()
; Save context of currently executing thread, and activate
; the thread whose context object is passed as a parameter.
;
; Parameter:
; - ptr to Kernel_Thread whose state should be restored and made active
;
; Notes:
; Called with interrupts disabled.
; This must be kept up to date with definition of Kernel_Thread
; struct, in kthread.h.
; ----------------------------------------------------------------------
align 16
Switch_To_Thread://栈中存储着函数的第一个参数(欲运行的线程结构体)
; Modify the stack to allow a later return via an iret instruction.
; We start with a stack that looks like this:
;
; thread_ptr
; esp --> return addr
;
; We change it to look like this:
;
; thread_ptr
; eflags
; cs
; esp --> return addr //调整栈状态,因为将来系统会在其他的线程上下文中使用iret切换回来。iret需要从栈中弹出eflag、cs、return addr(线程运行地址)
push eax ; save eax
mov eax, [esp+4] ; get return address,得到返回地址存到eax中,也就是Schedule中Switch_To_Thread函数执行完后的下条指令地址
mov [esp-4], eax ; move return addr down 8 bytes from orig loc,将返回地址向下移8B
add esp, 8 ; move stack ptr up
pushfd ; put eflags where return address was
mov eax, [esp-4] ; restore saved value of eax
push dword KERNEL_CS ; push cs selector
sub esp, 4 ; point stack ptr at return address,这句运行结束后,堆栈状态就是上面的图示。这里压入的线程参数会在下次
; Push fake error code and interrupt number,压入错误码,和中断向量,这里是线程切换,无需理会中断向量,所以中断向量设为0即可。
push dword 0
push dword 0
; Save general purpose registers.
Save_Registers
; Save stack pointer in the thread context struct (at offset 0).g_currentThread指向当前执行的线程,这里就是得到Idle这个线程的结构体指针,并把esp赋给Idle线程结构体
mov eax, [g_currentThread]
mov [eax+0], esp
; Clear numTicks field in thread context, since this
; thread is being suspended.Idle线程结构体中的numTicks变量置0,因为Idle将被挂起
mov [eax+4], dword 0
; Load the pointer to the new thread context into eax.
; We skip over the Interrupt_State struct on the stack to
; get the parameter.将要运行的线程的结构体指针赋给eax
mov eax, [esp+INTERRUPT_STATE_SIZE]
; Make the new thread current, and switch to its stack.将线程设置为当前运行线程,并切换esp到新的堆栈。
mov [g_currentThread], eax
mov esp, [eax+0]
; Restore general purpose and segment registers, and clear interrupt
; number and error code.从新的esp栈中弹出数据,在这里还原新进程的上下文。
Restore_Registers
; We'll return to the place where the thread was
; executing last.切换到新进程了,这里是极其核心,极其关键的一步!!!注意iret和ret的区别,iret还还原了寄存器cs和eflag。
iret
对照Interrupt_State结构看,可以看到,Switch_To_Thread就是先逆序将各个参数入栈。
然后切换到新的线程上下文。
理解最后一句iret至关重要,iret执行完后,新进程开始执行它的第一条指令。
而原来这个Idle进程的Switch_To_Thread也完全的完成了任务,再返回运行Idle线程时候,执行的就是到Schedule函数中的下一句了。
为统一上下文结构,线程上下文和中断上下文Interrupt_State保持了一致。
Interrupt_State结构位于./include/geekos/int.h
/*
* This struct reflects the contents of the stack when
* a C interrupt handler function is called.
* It must be kept up to date with the code in "lowlevel.asm".
*/
struct Interrupt_State {
/*
* The register contents at the time of the exception.
* We save these explicitly.
*/
uint_t gs;
uint_t fs;
uint_t es;
uint_t ds;
uint_t ebp;
uint_t edi;
uint_t esi;
uint_t edx;
uint_t ecx;
uint_t ebx;
uint_t eax;
/*
* We explicitly push the interrupt number.
* This makes it easy for the handler function to determine
* which interrupt occurred.
*/
uint_t intNum;
/*
* This may be pushed by the processor; if not, we push
* a dummy error code, so the stack layout is the same
* for every type of interrupt.
*/
uint_t errorCode;
/* These are always pushed on the stack by the processor. */
uint_t eip;
uint_t cs;
uint_t eflags;
};
至此Idle线程完毕。。
这里要说明一下这里的线程切换函数Switch_To_Thread和通用中断处理函数Handle_Interrupt的区别和联系。
都在./src/geekos/lowlevel.asm中
它们的共同点都是会将当前进程的上下文保存。
具体过程:
Handle_Interrupt过程:
保护被中断进程的上下文---->从栈中取出中断向量---->根据中断向量取出中断处理函数地址并调用中断处理函数---->中断处理函数结束,返回到Handle_Interrupt---->判断是否需要调度新进程---->需要则调度新进程,调用Make_Runnable得到新进程的上下文并恢复执行;不需要调度则恢复被中断进程的上下文,继续执行被中断进程。
Switch_To_Thread过程:
保护当前进程的上下文---->从栈中取出欲运行进程的结构体---->切换到欲运行进程堆栈,恢复其上下文---->跳到欲运行进程执行。
Handle_Interrupt中使用了三个系统全局变量:g_currentThread、g_preemptionDisabled、g_needReschedule。
Switch_To_Thread中只使用了一个系统全局变量:g_currentThread
Handle_Interrupt是由于外部中断触发,有特权级的变换。
Switch_To_Thread是在某任务A中调用函数Schedule触发,无特权级变换。
再来看另外一个内核线程Reaper,Reaper线程用于回收进程退出时的内存。
位于/src/geekos/kthread.c中
/*
* The reaper thread. Its job is to de-allocate memory
* used by threads which have finished.
*/
static void Reaper(ulong_t arg)
{
struct Kernel_Thread *kthread;
Disable_Interrupts();
while (true) {
/* See if there are any threads needing disposal. s_graveyardQueue是等待回收内存的队列*/
if ((kthread = s_graveyardQueue.head) == 0) {
/* Graveyard is empty, so wait for a thread to die. */
Wait(&s_reaperWaitQueue);
}
else {
/* Make the graveyard queue empty.清空队列,下面要回收队列中的所有内存 */
Clear_Thread_Queue(&s_graveyardQueue);
/*
* Now we can re-enable interrupts, since we
* have removed all the threads needing disposal.
*/
Enable_Interrupts();
Yield(); /* allow other threads to run? 因为Reaper线程永远不会退出,所以要给其他线程运行的机会,这里没有Yield的话系统就死机了*/
/* Dispose of the dead threads.遍历整个链表 */
while (kthread != 0) {
struct Kernel_Thread* next = Get_Next_In_Thread_Queue(kthread);
#if 0
Print("Reaper: disposing of thread @ %x, stack @ %x\n",
kthread, kthread->stackPage);
#endif
Destroy_Thread(kthread);//清除线程的栈空间,以及线程结构体,将线程从系统所有线程队列中删除
kthread = next;
}
/*
* Disable interrupts again, since we're going to
* do another iteration.
*/
Disable_Interrupts();
}
}
}
可以看到,和Idle线程一样,Reaper也是一个恒久的线程,使用while无限循环。
这里说一下在队列上阻塞等待的Wait函数,和唤醒函数Wake_Up是一对,看一下他俩的实现
位于/src/geekos/kthread.c
/*
* Wait on given wait queue.
* Must be called with interrupts disabled!
* Note that the function will return with interrupts
* disabled. This is desirable, because it allows us to
* atomically test a condition that can be affected by an interrupt
* and wait for it to be satisfied (if necessary).
* See the Wait_For_Key() function in keyboard.c
* for an example.
*/
void Wait(struct Thread_Queue* waitQueue)
{
struct Kernel_Thread* current = g_currentThread;
KASSERT(!Interrupts_Enabled());
/* Add the thread to the wait queue. */
Enqueue_Thread(waitQueue, current);
/* Find another thread to run. */
Schedule();
}
/*
* Wake up all threads waiting on given wait queue.
* Must be called with interrupts disabled!
* See Keyboard_Interrupt_Handler() function in keyboard.c
* for an example.
*/
void Wake_Up(struct Thread_Queue* waitQueue)
{
struct Kernel_Thread *kthread = waitQueue->head, *next;
KASSERT(!Interrupts_Enabled());
/*
* Walk throught the list of threads in the wait queue,
* transferring each one to the run queue.
*/
while (kthread != 0) {
next = Get_Next_In_Thread_Queue(kthread);
Make_Runnable(kthread);
kthread = next;
}
/* The wait queue is now empty. */
Clear_Thread_Queue(waitQueue);
}
Wait将当前线程进入特定的等待队列,并调度其他线程运行。
Wake则唤醒等待队列中的所有线程,唤醒的就是将其进入系统运行队列,调度程序会从运行队列中取线程执行。
Reaper将线程从系统中永远的删除了,就像它从来没有来过一样。
线程进入s_graveyardQueue就是进入了墓地,等待着最终的躯壳的消散。
在Setup_Kernel_Thread中我们压入了线程退出函数Shutdown_Thread,线程运行完用户执行的函数时候,就会运行Shutdown_Thread
Shutdown_Thread内就是一个Exit(0)函数。
结合Exit(0)函数来看,就能明白Reaper线程的功能了。
/*
* Exit the current thread.
* Calling this function initiates a context switch.
*/
void Exit(int exitCode)
{
struct Kernel_Thread* current = g_currentThread;
if (Interrupts_Enabled())
Disable_Interrupts();
/* Thread is dead */
current->exitCode = exitCode;
current->alive = false;
/* Clean up any thread-local memory */
Tlocal_Exit(g_currentThread);
/* Notify the thread's owner, if any */
Wake_Up(¤t->joinQueue);
/* Remove the thread's implicit reference to itself. */
Detach_Thread(g_currentThread);
/*
* Schedule a new thread.
* Since the old thread wasn't placed on any
* thread queue, it won't get scheduled again.
*/
Schedule();
/* Shouldn't get here */
KASSERT(false);
}
Exit置线程的退出码和存活标志,然后调用Tlocal_Exit释放将线程的本地内存。
再调用Wake_Up通知线程的所有者。Wake_Up将唤醒current->joinQueue上的所有线程。
Detach_Thread则通知Reaper内核线程"收割"此线程。
Detach_Thread位于./src/geekos/kthread.c
/*
* Hand given thread to the reaper for destruction.
* Must be called with interrupts disabled!
*/
static void Reap_Thread(struct Kernel_Thread* kthread)
{
KASSERT(!Interrupts_Enabled());
Enqueue_Thread(&s_graveyardQueue, kthread);
Wake_Up(&s_reaperWaitQueue);
}
/*
* Called when a reference to the thread is broken.
*/
static void Detach_Thread(struct Kernel_Thread* kthread)
{
KASSERT(!Interrupts_Enabled());
KASSERT(kthread->refCount > 0);
--kthread->refCount;
if (kthread->refCount == 0) {
Reap_Thread(kthread);
}
}
可以看到,当线程的引用技术refCount为0时,说明此线程彻底结束了,Reap_Thread将线程进入坟墓s_graveyardQueue,并唤醒s_reaperWaitQueue队列上的所有线程(其实此队列上只有Reaper一个线程)。