📄 漫谈兼容内核之二十二windows线程的调度和运行.txt
字号:
漫谈兼容内核之二十二:Windows线程的调度和运行
毛德操
了解Windows线程的系统空间堆栈以后,还有必要对Windows线程的调度、切换、和运行也有所了解。当然,就兼容内核的开发而言,内核的线程调度/切换/运行机制只能有一套,而且必定是基本上沿用Linux的这套机制,而不可能在一个内核中有两套调度/运行机制。但是对于Windows这套机制的了解对于兼容内核的开发也很重要,并且还是必须的。举例来说,大家都知道在Windows系统中段寄存器FS在用户空间指向TEB、而在系统空间则指向KPCR,而且Windows的DDK中也公开了KPCR数据结构的定义。这样,设备驱动模块的开发者就有可能在程序中通过段寄存器FS获取KPCR的地址,并按KPCR数据结构的定义访问其中的某些字段。然而如果内核中并不真的有个KPCR,或者FS并不指向KPCR,那就要乱套了。所以,为了在兼容内核中支持设备驱动界面,就得把KPCR等等揉合进去,而那些东西其实就是Windows的线程切换/运行机制的一部分。
为此,我们先要了解一下Windows内核中有关这一方面的格局,这要从x86的系统结构谈起。
所谓“Intel架构”、即x86的系统结构,其最初的设计是在二十多年以前。当初一来是还没有“简约指令集”即RISC的概念,二来是把操作系统的设计与实现考虑得太复杂、太繁琐,因而把CPU系统结构的设计与实现也考虑得太复杂、太繁琐了。就我们所关心的问题而言,这主要表现在两个方面。
首先,Intel在x86的系统结构中把CPU的执行权限分得很细,分成了从0环至3环共4个“环”,并让CPU运行于0环时具有最高的权限,而运行于3环时则权限最低。但是从后来的发展看,无论是Linux还是Windows,实际上都只分系统(即内核)和用户两种状态、或称两个空间就够了,因而只使用了4个环中的两个,即0环(内核)和3环(用户)。
另一方面,Intel在“任务”(当初的“任务”相当于进程,现在则相当于线程)切换上也动了很多脑筋,其设计意图是让每个任务、即进程或线程、都有一个独立的“任务状态段”TSS,里面包含了几乎所有寄存器的映像,而通过TSS的切换来实现任务的切换,而且只要一条指令就能完成这样的切换。这条指令把几乎所有寄存器(除一些“系统寄存器”如GDTR等以外)的当前内容都一下子保存到当前任务的TSS中;然后通过一个实质上相当于段寄存器的“任务寄存器”TR切换到目标任务的TSS,就是使TR改而指向目标任务的TSS;再从这TSS中恢复目标任务的寄存器映像,切换就完成了。这整个过程都集成在一条指令中,从程序上看是一步到位。而这功能如此强大的指令,则实际上既可以是call指令、也可以是jmp指令,还可以是ret指令或是中断的发生(由此可见本来应该很简单的call指令、jmp指令,还有ret指令的实现变得多么复杂)。
TSS中还有关于一个任务的其它重要信息,包括从0环到2环共三个环的堆栈段寄存器和堆栈指针的映像。其设计意图是,当CPU从外环(例如3环)进入内环(例如0环)时,就从当前任务的TSS中把内环的堆栈指针(以及段寄存器SS的映像)装入ESP(以及SS)。此外,TSS中还有一个“I/O权限位图”,位图中的每一位都代表着I/O地址空间(共64KB)的一个字节,如果为0就表示即使在3环中也可以对此字节执行in、out等I/O指令。
Intel的这些设计意图都实现了。可是论者却认为这样做真是得不偿失,因为这使call、jmp、ret等指令的设计与实现都大大复杂化了,还使指令流水线的设计与实现也大大复杂化了,并且CPU芯片上的许多资源都被用来实现这些并非必须的功能。再说,虽然是单指令“一步到位”,但是这指令的执行时间却大大延长了,实际上也没有带来明显的好处。事实上,jmp指令在实现任务切换时需要200多个时钟周期。有200多个周期,在流水线操作的条件下,通过程序和堆栈实现任袂谢灰膊畈欢嗔恕R虼耍
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -