以下针对WindowsXP
线程三种状态:运行、等待、就绪。
(1)处于运行态的线程存储在KPCR中
(2)处于就绪态和等待态的线程处于另外33个链表中,其中1个是等待链表,32个是就绪链表。(Win7、WIn10 64个)。这些链表使用_KTHREAD(+0x60)追踪线程,即线程在某一时刻,只能属于其中一个圈。(不管是就绪态还是等待态都挂在_KTHREAD(+0x60),即在_KTHREAD中用一块地址,指向等待链表和就绪链表)
等待链表/调度链表
(1)若线程调用了Sleep()或者WaitForSingleObject()等函数,线程就挂到等待链表中,处于挂起态。
(2)32个调度链表,每个链表优先级不同
windbg使用如下命令查看等待链表头:
dd KiWaitListHead
windbg使用如下命令查看调度链表(32个):
dd KiDispatcherReadyListHead L50
线程切换
每个线程有自己的内核堆栈,线程内核堆栈如下:
每个内核堆栈具体如下:InitialStack开始的0x210字节存储浮点寄存器的时,从0x210开始到KernelStack存储_Trap_Frame结构。
线程切换有三种方式(1)主动切换(2)时钟中断(3)异常
主动切换
执行流程:API函数---->KiSwapThread---->KiSwapContext---->SwapContext
线程调用SwapContext内核函数进行线程切换。
(1)Windows中大部分API调用SwapContext函数,该函数用于线程切换(内部是将下一个线程的栈顶指向KPCR中的当前线程的栈顶)
(2)线程切换时会边角是否属于同一个进程,若不是则切换Cr3.
时钟中断
具备以下两个条件通过调用SwapContext()内核函数切换线程:
(1)当前CPU时间片到期
执行流程:KiDispatchInterrupt---->KiQuantumEnd---->SwapContext
(2)有备用线程(即KPCR.PrcbData.NextThread存在)
执行流程:KiDispatchInterrupt---->SwapContext
CPU时间片:
(1)一个新的线程开始时,线程初始化程序会为_KTHREAD.Quantum赋初始值,该值的大小由_KPROCESS.ThreadQuantum决定。
(2)每次时钟中断调用KeUpdateRuntime函数,该函数每次将当前线程Quantum减少3个单位(例如若Quantum=6,需要2个时钟中断才可以让CPU时间片到期),若Quantum=0,则将KPCR.PrcbData.QuantumEnd设为非0(代表该线程时间片到期)
(3)函数KiDispatchInterrup判断时间片到期,调用KiQuantumEnd重新设置时间片,在KiQuantumEnd中通过KiFindReadyThread找到下一个线程,进行线程切换。当前线程被挂到调度链表中
备用线程:
若当前CPU时间片没到期,但是当前线程有备用线程,即KPCR.PrcbData.NextThread存在,线程也会发生切换。
异常处理
也是通过调用SwapContext()内核函数