sBeginThreadWithContextInternal会消耗掉KernelStack[6]~ KernelStack[12]然后jmp _KiServiceExit,此时对应正好是KTRAP_FRAME的顶部
对于KernelStack[5]的设置实际上是为线程的调度/切换准备的,目的是为新建线程提供一个虚构的程序执行“断点”,仿佛原先就是在这里被剥夺了运行,下次受调度运行时就从这一点上恢复
那么KernelStack[0]又是干什么用的呢?注释中说了这就是TSS->Esp0,这跟线程切换的机制与过程有关当新建线程受调度运行时,会将这个数值写入“任务状态段”TSS中的ESP0字段,而每当CPU从用户空间进入系统空间、需要切换到系统空间堆栈时,就总是把TSS中的这个数值装入ESP,所以这就是系统空间堆栈的原点。从代码中可以看到,这就是Thread->InitialStack-sizeof(FX_SAVE_AREA)。
最后又要回到对Thread->KernelStack的设置。KTHREAD结构中这个字段的用途是在线程切换的时侯保存堆栈指针。就是说,其它寄存器的内容都保存在堆栈上,而堆栈指针则保留在KTHREAD结构中。当一个线程暂时放弃运行、或被剥夺运行时(一定发生于系统空间),就把它当时的堆栈指针保存在这儿;到下一次又被调度运行时则从这儿恢复其堆栈指针。
当新建线程受调度运行并跳转到_KiServiceExit处时,其系统空间堆栈上已经只剩下陷阱框架了,而随后的“恢复现场”以及最后iret指令的执行则使陷阱框架消失,逻辑意义上系统空间堆栈的大小收缩到0、堆栈指针回到原点;同时CPU返回到用户空间、切换到用户空间堆栈。以后每次从系统调用、中断、或异常返回用户空间时,则物理意义上也都是如此。显然,这一点是很重要的,要不然系统空间堆栈就早晚会被耗尽
这样,CPU中的段寄存器一共是8个,即CS、DS、SS、ES、FS、GS、LDTR、和TR,其中LDTR和TR为系统段寄存器。相比之下,GDT中最多可以有8191个有效的段描述项,所以只要改变段寄存器中的选择项即下标就可以使其灵活地指向不同的地址段。例如,段寄存器FS在用户空间时的内容是TEB_SELECTOR,而进入内核时就改成PCR_SELECTOR。这就在GDT中选择了不同的表项,从而分别指向当前线程用户空间的TEB和内核中的KPCR数据结构;而FS实际上起着指针的作用,同时也有对于越界访问的保护作用。当然,GDT中的描述项都是事先设置好了的。