上篇:Windows内核学习笔记之线程(中)
六.Windows线程调度和切换
1.基本概念
一旦系统中存在多个线程,则这些线程将共享物理的处理器资源,它们实际上只得到了一部分处理器时间。如果线程的个数大于处理器的数量,则自然地,哪些线程先执行,哪些线程后执行,这需要由系统的线程调度器来决定。
Windows的线程调度器建立在线程的优先级的基础上,它允许一个高优先级的线程将CPU资源从低优先级的线程上抢过来,而线程的优先级并不是绝对一成不变,在系统执行过程中有些线程的优先级会动态地变化。另外,有些线程具有处理器亲和性,即只能在某些处理器上运行;而有些线程对特定的处理器更具有倾向性。
2.线程优先级
Windows内核将线程的优先级分为32级,其值从0到31。其中0级为最低,31级为最高,16级以上用于实时线程,16级以下则用于普通线程。因此,这32个值被分成了三种类别:
实时类别:16~31
动态类别:1~15
系统类别:0
在源码中,由如下的宏定义说明了这3个类别
进程对象KPROCESS结构的BasePriority域指定了一个进程的基本优先级,由于进程本身并不参与调度,所以这个值的意义在于该进程中的每个线程在初始化时都可以直接从该进程对象中获得基本的优先级。而线程对象KTHREAD结构的BasePriority和Priority域则分别定义了一个线程的静态和动态优先级。进程和线程的基本优先级是保持不变的,除非是通过调用函数KeSetPriorityAndQuantumProcess或KeSetBasePriorityThread来显式地改变。
运行在动态优先级类别中的线程,它们的实际优先级可能会根据特定的情形而作调整,所以,KTHREAD结构的Priority域记录了一个线程当前实际优先级。它往往是在BasePriority域的基础上提升一定的优先级增量。但是,无论怎么调整,Prioriry域值的范围都在1~15。
执行体层提供了6个优先级分类:
实时:Realtime
高:High
普通之上:Above Normal
普通:Normal
普通之下:Below Normal
低:Low
执行体运行按着6个类别来设置进程的优先级,这些选择项对应的优先级分别是24,13,10,8,6和4。执行体在创建进程时,调用PspComputeQuantumAndPriority来计算优先级,而PspComputerQuantumAndPriority函数根据进程EPROCESS对象中的PriorityClass域来查表以便获得内核层的优先级定义。此对照表定义在全局变量 PspPriorityTable中。以下是一些相关定义:
1 2 3 4 5 6 7 8 9 | KPRIORITY PspPriorityTable[PROCESS_PRIORITY_CLASS_REALTIME + 1 ] = { 8 , 4 , 8 , 13 , 24 , 6 , 10 };
|
执行体层的优先级类别经过全局表PspPriorityTable变换后,就变成了内核层上的0~31优先级值。另外,在执行体层上,除了通过优先级类别的方式来设置一个进程的基本优先级以外,还可以通过NtSetInformationProcess函数对基本优先级进行微调,并非绝对由全局表PspPriorityTable决定的。
从这里对进程优先级的控制也可看到,执行体层的职能是提供管理和策略支持,并为上层应用程序提供服务,而内核层提供的是基本的线程调度机制。这正是策略与机制相分离的设计思想。
Windows提供的函数中,有不少带有"KPRIORITY Increment"参数,它代表了优先级提升量。优先级提升应该发生在该线程作出调度决定(即插入到对应优先级的调度链表中)以前。内核提供的带Increment参数的函数包括:
KeInsertQueueApc
KePulseEvent
KeSetEvent
KeReleaseMutant
KeReleaseSemaphore
KeSetProcess
KeBoostPriorityThread
KeTeriminateThread
下面这些是优先级提升的典型情形:
当一个I/O操作完成时,正在等待此I/O线程有更多的机会被立即执行。IoCompleteRequest函数有一个参数是优先级提升值,至于具体的提升量则由负责I/O的驱动程序来完成
一个线程在等待事件和信号量以后,其优先级得到提升
前台线程从等待状态中醒来时,有一点小小的优先级提升
在平衡集管理器系统线程中,扫描那些已进入就绪状态4s,但尚未被执行的线程,将它们的优先级提升至15。其用意在于避免优先级反转情形的长时间出现
窗口线程因窗口的活动而醒来时,会得到一个额外的优先级提升。这是窗口管理系统在调用KeSetEvent函数时设置的
3.线程状态
KTHREAD的State域是一个跟线程调度有关的成员,反映了线程的当前调度状态,其类型是枚举类型KTHREAD_STATE,定义如下:
1 2 3 4 5 6 7 8 9 | typedef enum _KTHREAD_STATE {
Initialized,
Ready,
Running,
Standby,
Terminated,
Waiting,
Transition
} KTHREAD_STATE;
|
名称 |
含义 |
Initialized |
已初始化:说明一个线程对象的内部状态已初始化,这是线程创建过程中的一个内部状态,此时线程尚未加入到进程的链表中,没有启动 |
Ready |
就绪:代表该线程已经准备就绪,等待被调度执行。当线程调度器选择一个线程来执行时,它只考虑处于就绪状态的线程。此时,线程已被加入到某个处理器的就绪线程链表中 |
Running |
运行:线程正在运行。该线程一直占有处理器,直到分到的时限结束,或者被一个更高优先级的线程抢占,或者线程终止,或者主动放弃处理器执行权,或者进入等待状态 |
Standby |
备用:处于备用状态的线程已经被选中作为某个处理器上下一个要运行的线程。对于系统中的每个处理器,只能有一个线程可以处于备用状态。然而,一个处于备用状态的线程在真正被执行以前,有可能被被更高优先级的线程抢占 |
Terminated |
已终止:表示线程已完成任务,正在进行资源回收。可以通过使用函数KeterminateThread来设置此状态 |
Waiting |
等待:表示一个线程正在等待某个条件,比如等待一个分发器对象变成有信号状态,也可以等待多个对象。当等待的条件满足时,线程或者立即开始执行,或者回到就绪状态 |
Transition |
转移:处于转移状态的线程已经准备好运行,但是它的内核栈不在内存中。一旦它的内核栈被换入内存,则该线程进入就绪状态 |
DeferredReady |
延迟的就绪:处于延迟的就绪状态的线程也已经准备好可以运行了,但是,与就绪状态不同的是,它尚未确定在哪个处理器上运行。当有机会被调度时,或者直接转入备用状态,或者转到就绪状态。因此,此状态是为了多级处理器而引入的,对于单处理器系统没有意义 |
GateWait |
门等待:线程正在等待一个门对象。此状态与等待状态类似,只不过它是专门针对门对象而设计 |
下图是这些状态的转移图:
在一个线程的生命周期中,它从完成初始化开始进入到线程调度器的视野中,之后,一直到完成所有预定的功能,最后终止并被销毁。
4.线程调度
在通过系统调用NtCreateThread创建线程的过程中,会调用KeReadyThread来改变线程的状态,让线程进入就绪状态,由该函数的反汇编结果可以得知,该函数的实现是通过调用KiReadyThread来实现的,在该函数中同时会完成线程的调度。由于KiReadyThread的函数声明是fastcall,所以在调用之前会将线程对象作为参数赋给ecx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .text: 0041501A ; int __stdcall KeReadyThread(PETHREAD Thread)
.text: 0041501A _KeReadyThread@ 4 proc near ; CODE XREF: PspCreateThread(x,x,x,x,x,x,x,x,x,x,x) + 3AD ↓p
.text: 0041501A ; PspCreateThread(x,x,x,x,x,x,x,x,x,x,x) + 8EFD9 ↓p ...
.text: 0041501A
.text: 0041501A Thread = dword ptr 8
.text: 0041501A
.text: 0041501A mov edi, edi
.text: 0041501C push ebp
.text: 0041501D mov ebp, esp
.text: 0041501F push ebx
.text: 00415020 xor ecx, ecx
.text: 00415022 call ds:__imp_@KeAcquireQueuedSpinLockRaiseToSynch@ 4 ; KeAcquireQueuedSpinLockRaiseToSynch(x)
.text: 00415028 mov ecx, [ebp + Thread] ; 将线程对象赋给ecx
.text: 0041502B mov bl, al
.text: 0041502D call @KiReadyThread@ 4 ; KiReadyThread(x)
.text: 00415032 mov cl, bl
.text: 00415034 call @KiUnlockDispatcherDatabase@ 4 ; KiUnlockDispatcherDatabase(x)
.text: 00415039 pop ebx
.text: 0041503A pop ebp
.text: 0041503B retn 4
.text: 0041503B _KeReadyThread@ 4 endp
|
KiReadyThread函数执行过程中会用到比较多结构体的成员
以下是用到的线程对象中的成员
偏移 |
名称 |
含义 |
0x128 |
Preempted |
布尔值,说明这个线程是否被高优先级的线程抢占了,只有当一个线程正在运行或者正在等待运行而被高优先级线程抢占的时候,此值才会是TRUE |
0x033 |
Priority |
指明了线程的优先级 |
0x02D |
State |
反映了当前线程的状态 |
0x129 |
ProcessReadyQueue |
布尔值,说明线程是否在所属进程的KPROCESS对象的ReadyListEntry链表中,TRUE表示在此链表中,FALSE表示不在此链表中 |
0x60 |
WaitListEntry |
双向链表节点,当一个线程正在等待被执行时,该域作为一个线程节点加入到某个链表中 |
0x60 |
SwapListEntry |
单链表节点,当线程的内核栈需要被换入时,插入到以全局变量KiStackInSwapListHead为链表头的单链表中 |
0x12A |
KernelStackResident |
布尔值,说明该线程的内核栈是否驻留在内存中,当内核栈被换出内存时,此值将被设置成FALSE;当换入内存时,在设置成TRUE |
0x124 |
Affinity |
指定了线程的处理器亲和性,此值初始继承自进程对象的Affinity值 |
0x1BA |
IdealProcessor |
指明了在多处理器上该线程的理想处理器 |
0x12B |
NextProcessor |
指明了该线程开始运行时的处理器 |
以下是用到的进程对象中的成员
偏移 |
名称 |
作用 |
0x65 |
State |
说明了进程是否在内存中,共有六种可能的状态:ProcessInMemory, ProcessOutOfMemory, ProcessInTransition, ProcessOutTransition, ProcessInSwap, ProcessOutSwap |
0x40 |
ReadyListHead |
是一个双向链表头,记录了进程中处于就绪状态但尚未被加入全局就绪链表的线程,这个域的意义在于,当一个进程被换出内存后,它所属的线程一旦就绪,则被挂到此链表中,并要求换入该进程。此后,当该进程被换入内存时,ReadyListHead中的所有线程被加入到系统全局的就绪线程链表中 |
0x48 |
SwapListEntry |
单链表项,当一个进程要被换出内存时,它通过此域加入到KiProcessOutSwapListHead为链头的单链表中;当一个进程被换入内存时,它通过此域加入到以KiProcessInSwapListHead为链头的单链表中 |
0x60 |
StackCount |
记录当前由多少线程栈位于内存中 |
每个CPU都有一个KPCR结构体,那也就有KPCB结构体,在一个多核环境下就会有多个KPCB结构体,而全局变量KiProcessorBlock保存的就是不同的核对应的KPCB结构体地址。全局变量kiReadySummary则保存了这些处理器的使用情况,当相应处理器没有被使用到的时候全局变量KiReadySummary中相应的位就会是0,否则是1
完成变量的赋值以后,判断进程状态是否为ProcessInMemory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | .text: 00405667 ; int __fastcall KiReadyThread(PKTHREAD Thread)
.text: 00405667 @KiReadyThread@ 4 proc near ; CODE XREF: KiInsertQueue(x,x,x) + 7B ↑p
.text: 00405667 ; KiUnwaitThread(x,x,x,x) - 7671 ↑p ...
.text: 00405667
.text: 00405667 var_8 = dword ptr - 8
.text: 00405667 var_Preempted = byte ptr - 1
.text: 00405667 Thread = dword ptr 8
.text: 00405667
.text: 00405667 mov edi, edi
.text: 00405669 push ebp
.text: 0040566A mov ebp, esp
.text: 0040566C push ecx
.text: 0040566D push ecx
.text: 0040566E push ebx
.text: 0040566F push esi
.text: 00405670 mov esi, ecx ; 将线程对象赋给esi
.text: 00405672 lea ecx, [esi + _KTHREAD.Preempted] ; 将Preempted地址赋给ecx
.text: 00405678 mov al, [ecx] ; 将Preempted地址中的内容赋给al
.text: 0040567A mov byte ptr [ecx], 0 ; 将Preempted赋值为 0
.text: 0040567D mov ecx, ds:_KeTickCount.LowPart ; 将时间赋给ecx
.text: 00405683 push edi
.text: 00405684 mov edi, [esi + _KTHREAD.ApcState.Process]
.text: 00405687 mov [ebp + var_Preempted], al ; 将al赋给局部变量
.text: 0040568A movsx eax, [esi + _KTHREAD.Priority] ; 将线程优先级赋给eax
.text: 0040568E mov [esi + _KTHREAD.WaitTime], ecx ; 为WaitTime赋值
.text: 00405691
.text: 00405691 loc_405691: ; CODE XREF: KiReadyThread(x) + 9817 ↓j
.text: 00405691 cmp [edi + _KPROCESS.State], 0 ; 判断进程状态是否为ProcessInMemory
.text: 00405695 jnz loc_432827
|
如果不是,修改线程状态为Ready,ProcessReadyQueue为TRUE,将线程对象的WaitListEntry域链接到所属进程对象的WaitListEntry域中
1 2 3 4 5 6 7 8 9 10 | .text: 00432827 loc_432827: ; CODE XREF: KiReadyThread(x) + 2E ↑j
.text: 00432827 mov [esi + _KTHREAD.State], 1 ; 将线程State赋值为Ready
.text: 0043282B mov [esi + _KTHREAD.ProcessReadyQueue], 1 ; 将线程ProcessReadyQueue赋值为TRUE
.text: 00432832 lea ecx, [edi + _KPROCESS.ReadyListHead] ; 将进程ReadListHead地址赋给ecx
.text: 00432835 mov edx, [ecx + LIST_ENTRY.Blink]
.text: 00432838 lea eax, [esi + 60h ] ; 取出WaitListEntry地址赋给eax
.text: 0043283B mov [eax + LIST_ENTRY.Flink], ecx
.text: 0043283D mov [eax + LIST_ENTRY.Blink], edx
.text: 00432840 mov [edx + LIST_ENTRY.Flink], eax
.text: 00432842 mov [ecx + LIST_ENTRY.Blink], eax
|
判断进程对象是否为ProcessOutOfMemory,如果不是则退出函数
1 2 | .text: 00432845 cmp [edi + _KPROCESS.State], 1 ; 判断进程状态是否为ProcessOutOfMemory
.text: 00432849 jnz loc_405745
|
如果是则将进程的SwapListEntry域加入到链表KiProcessInSwapListHead中
1 2 3 4 5 6 7 8 9 10 11 12 | .text: 00432853 mov eax, ds:_KiProcessInSwapListHead
.text: 00432858 lea ecx, [edi + _KPROCESS.SwapListEntry]
.text: 0043285B
.text: 0043285B loc_43285B: ; CODE XREF: KiReadyThread(x) + 2D20B ↓j
.text: 0043285B mov [ecx], eax
.text: 0043285D mov edx, eax
.text: 0043285F mov esi, ecx
.text: 00432861 mov edi, offset _KiProcessInSwapListHead
.text: 00432866 lock cmpxchg [edi], esi
.text: 0043286A cmp eax, edx
.text: 0043286C jz loc_41475D
.text: 00432872 jmp short loc_43285B
|
调用KiSetSwapEvent通知交换线程执行换入操作然后退出函数
1 2 3 4 | .text: 0041475D loc_41475D: ; CODE XREF: KiReadyThread(x) + 2D205 ↓j
.text: 0041475D ; .text: 0044B716 ↓j
.text: 0041475D call _KiSetSwapEvent@ 0 ; KiSetSwapEvent()
.text: 00414762 jmp loc_405745
|
如果进程状态是ProcessInMemory,判断KernelStackResident是否为0
1 2 | .text: 0040569B cmp [esi + _KTHREAD.KernelStackResident], 0
.text: 004056A2 jz loc_41473A
|
如果为0,修改进程与线程对象中的成员,将线程对象的SwapListEntry域加入到链表KiStackInSwapListHead中,调用KiSetSwapEvent后退出函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | .text: 0041473A loc_41473A: ; CODE XREF: KiReadyThread(x) + 3B ↑j
.text: 0041473A inc [edi + _KPROCESS.StackCount] ; StackCount加 1
.text: 0041473E mov [esi + _KTHREAD.State], 6 ; 线程状态赋值为Transition
.text: 00414742 mov eax, ds:_KiStackInSwapListHead
.text: 00414747 lea ecx, [esi + 60h ] ; 将线程对象的SwapListEntry地址赋给ecx
.text: 0041474A
.text: 0041474A loc_41474A: ; CODE XREF: KiReadyThread(x) + F0F4↓j
.text: 0041474A mov [ecx], eax
.text: 0041474C mov edx, eax
.text: 0041474E mov esi, ecx
.text: 00414750 mov edi, offset _KiStackInSwapListHead
.text: 00414755 lock cmpxchg [edi], esi
.text: 00414759 cmp eax, edx
.text: 0041475B jnz short loc_41474A
.text: 0041475D
.text: 0041475D loc_41475D: ; CODE XREF: KiReadyThread(x) + 2D205 ↓j
.text: 0041475D ; .text: 0044B716 ↓j
.text: 0041475D call _KiSetSwapEvent@ 0 ; KiSetSwapEvent()
.text: 00414762 jmp loc_405745
|
如果KernelStackResident不为0,接下来会将线程状态该位Standby,线程中的Affinity与线程中的SoftAffinity与的结果如果不为0,就会将结果与全局变量KiIdelSummary进行与操作,如果为0就会将Affinity与全局变量KiIdleSummary进行与操作,结果保留在edx中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .text: 004056A8 movzx ecx, [esi + _KTHREAD.IdealProcessor] ; 将IdealProcessor赋给ecx
.text: 004056AF mov edi, [esi + _KTHREAD.Affinity] ; 将Affinity赋给edi
.text: 004056B5 mov [esi + _KTHREAD.State], 3 ; 将线程状态改为Standby
.text: 004056B9 mov edx, ds:_KiProcessorBlock[ecx * 4 ]
.text: 004056C0 mov ebx, [edx + 4D0h ] ; 将MultiThreadSetMaster赋给ebx
.text: 004056C6 mov edx, [esi + _KTHREAD.SoftAffinity]
.text: 004056CC and edx, edi
.text: 004056CE jz short loc_4056D2
.text: 004056D0 mov edi, edx
.text: 004056D2
.text: 004056D2 loc_4056D2: ; CODE XREF: KiReadyThread(x) + 67 ↑j
.text: 004056D2 mov edx, ds:_KiIdleSummary
.text: 004056D8 and edx, edi
.text: 004056DA jnz loc_4106D5
|
如果满足条件,接下来就会从KiProcessorBlock中获取相应的KPCB,此时ecx保存的是线程的IdealProcessor
1 2 3 4 5 6 7 8 9 10 11 | .text: 004056E0 xor edx, edx
.text: 004056E2 inc edx ; edx赋值为 1
.text: 004056E3 mov ebx, edx ; 将edx赋给ebx
.text: 004056E5 shl ebx, cl
.text: 004056E7 test edi, ebx
.text: 004056E9 jz loc_441903
.text: 004056EF
.text: 004056EF loc_4056EF: ; CODE XREF: KiReadyThread(x) + 3C2A9 ↓j
.text: 004056EF ; KiReadyThread(x) + 3C2D6 ↓j
.text: 004056EF mov [esi + _KTHREAD.NextProcessor], cl
.text: 004056F5 mov ebx, ds:_KiProcessorBlock[ecx * 4 ] ; 取出KPCB地址赋给ebx
|
判断获取的KPCB是否有下一线程
1 2 3 | .text: 004056FC mov edi, [ebx + 8 ] ; 将下一线程对象地址赋给edi
.text: 004056FF test edi, edi
.text: 00405701 jnz loc_40EE20
|
如果有下一线程,就会判断下一线程的优先级是否小于要设置状态的线程的优先级,此时的eax保存的是调度的线程的优先级
1 2 3 4 | .text: 0040EE20 loc_40EE20: ; CODE XREF: KiReadyThread(x) + 9A ↑j
.text: 0040EE20 movsx ecx, [edi + _KTHREAD.Priority]
.text: 0040EE24 cmp eax, ecx
.text: 0040EE26 jle loc_405716
|
如果小于,就会将调度线程赋值到KPCB的下一线程中
1 2 3 4 5 6 7 8 9 10 | .text: 0041232E loc_41232E: ; CODE XREF: KiReadyThread(x) + 97E7 ↑j
.text: 0041232E call @KeReleaseQueuedSpinLockFromDpcLevel@ 4 ; KeReleaseQueuedSpinLockFromDpcLevel(x)
.text: 00412333 mov [ebx + 8 ], esi ; 将调度线程赋值给下一线程
.text: 00412336 mov eax, large fs: 20h
.text: 0041233C mov cl, [esi + _KTHREAD.NextProcessor]
.text: 00412342 cmp [eax + 10h ], cl ; 将cl赋值给Number
.text: 00412345 jz loc_405745
.text: 0041234B xor eax, eax
.text: 0041234D inc eax ; eax = 1
.text: 0041234E jmp loc_40EB31
|
如果获取的KPCB不存在下一线程,就会判断调度线程的优先级是否大于获取的KPCB的正在运行的线程的优先级
1 2 3 4 | .text: 00405707 mov ecx, [ebx + 4 ] ; 将当前线程对象地址赋给ecx
.text: 0040570A movsx edi, [ecx + _KTHREAD.Priority] ; 将线程优先级赋给edi
.text: 0040570E cmp eax, edi
.text: 00405710 jg loc_40EB10
|
如果大于,就会将调度线程赋值给下一线程
1 2 3 4 5 6 7 8 9 10 11 12 | .text: 0040EB10 loc_40EB10: ; CODE XREF: KiReadyThread(x) + A9↑j
.text: 0040EB10 mov [ecx + _KTHREAD.Preempted], 1
.text: 0040EB17 mov [ebx + 8 ], esi ; 将调度进程赋给下一线程
.text: 0040EB1A mov eax, large fs: 20h
.text: 0040EB20 mov cl, [esi + _KTHREAD.NextProcessor]
.text: 0040EB26 cmp [eax + 10h ], cl
.text: 0040EB29 jz loc_405745
.text: 0040EB2F mov eax, edx
.text: 0040EB31
.text: 0040EB31 loc_40EB31: ; CODE XREF: KiReadyThread(x) + CCE7↓j
.text: 0040EB31 shl eax, cl
.text: 0040EB33 jmp loc_40E9BC
|
如果调度线程优先级小于当前线程的优先级,就会将线程状态设置为Ready,将esi指向线程对象的WaitListEntry地址,从全局链表KiDispatcherReadyListHead中根据优先级获取调度链表,此时的eax代表了调度线程的优先级,判断Preempted是否为0
1 2 3 4 5 6 | .text: 00405716 loc_405716: ; CODE XREF: KiReadyThread(x) + 97BF ↓j
.text: 00405716 mov [esi + _KTHREAD.State], 1 ; 将线程状态赋值为Ready
.text: 0040571A add esi, 60h ; esi指向WaitListEntry地址
.text: 0040571D cmp [ebp + var_Preempted], 0
.text: 00405721 lea ecx, _KiDispatcherReadyListHead[eax * 8 ]
.text: 00405728 jnz loc_40565105651
|
如果不为0,就会将调度线程加到调度链表头部
1 2 3 4 5 6 7 | .text: 00405651 loc_405651: ; CODE XREF: KiReadyThread(x) + C1↓j
.text: 00405651 mov edi, [ecx + LIST_ENTRY.Flink]
.text: 00405653 mov [esi + LIST_ENTRY.Flink], edi
.text: 00405655 mov [esi + LIST_ENTRY.Blink], ecx
.text: 00405658 mov [edi + LIST_ENTRY.Blink], esi
.text: 0040565B mov [ecx + LIST_ENTRY.Flink], esi
.text: 0040565D jmp loc_40573B
|
如果为0,则将调度线程加到调度链表尾部
1 2 3 4 5 | .text: 0040572E mov edi, [ecx + LIST_ENTRY.Blink]
.text: 00405731 mov [esi + LIST_ENTRY.Flink], ecx
.text: 00405733 mov [esi + LIST_ENTRY.Blink], edi
.text: 00405736 mov [edi + LIST_ENTRY.Flink], esi
.text: 00405738 mov [ecx + LIST_ENTRY.Blink], esi
|
根据调度线程优先级,将KiReadySummary相应的位置1,这里的edx等于1,eax为调度线程的优先级
1 2 3 4 5 6 7 8 9 10 11 12 13 | .text: 0040573B loc_40573B: ; CODE XREF: KiReadyThread(x) - A↑j
.text: 0040573B mov ecx, eax
.text: 0040573D shl edx, cl
.text: 0040573F or ds:_KiReadySummary, edx
.text: 00405745
.text: 00405745 loc_405745: ; CODE XREF: KiReadyThread(x) + 935F ↓j
.text: 00405745 ; KiReadyThread(x) + 94C2 ↓j ...
.text: 00405745 pop edi
.text: 00405746 pop esi
.text: 00405747 pop ebx
.text: 00405748 leave
.text: 00405749 retn
.text: 00405749 @KiReadyThread@ 4 endp
|
由此可以知道,在KiDispatcherReadyListHead保存了需要被调度的不同优先级的线程的链表,不同的下标代表了不同的优先级。
5.线程切换
有两种情况会发生线程切换,分为主动切换和被动切换。主动切换是通过调用一些系统提供的API,在这些API中会调用KiSwapThread,而该函数通过调用SwapContext来实现线程切换。被动切换则发生在线程分配的时限用完或者被抢占的时候,此时KiDispatchInter会处理这两种情况,而无论哪种情况,最后都会通过调用函数SwapContext来切换线程,具体情况如下图所示:
在KiSwapThread中首先会判断下一线程是否为NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 | .text: 0040AB0A ; _DWORD __cdecl KiSwapThread()
.text: 0040AB0A @KiSwapThread@ 0 proc near ; CODE XREF: KeDelayExecutionThread(x,x,x):loc_40A56D↑p
.text: 0040AB0A ; KeWaitForMultipleObjects(x,x,x,x,x,x,x,x):loc_40AAEE↑p ...
.text: 0040AB0A
.text: 0040AB0A mov edi, edi
.text: 0040AB0C push esi
.text: 0040AB0D push edi
.text: 0040AB0E mov eax, large fs: 20h ; 将当前线程KPCB赋给eax
.text: 0040AB14 mov esi, eax ; 将eax赋给esi
.text: 0040AB16 mov eax, [esi + 8 ] ; 将下一线程赋给eax
.text: 0040AB19 test eax, eax ; 下一线程是否为NULL
.text: 0040AB1B mov edi, [esi + 4 ] ; 将当前线程赋给edi
.text: 0040AB1E jnz loc_410939
|
如果不为NULL,则将其清空,然后跳转到loc_40AB3B处执行
1 2 3 | .text: 00410939 loc_410939: ; CODE XREF: KiSwapThread() + 14 ↑j
.text: 00410939 and dword ptr [esi + 8 ], 0 ; 将下一线程赋值为 0
.text: 0041093D jmp loc_40AB3B
|
如果没有下一线程,就会调用KiFindReadyThread来获取要切换的线程,接下来和上面一样开始运行loc_40AB3B的代码
1 2 3 4 5 6 7 8 9 10 11 | .text: 0040AB24 push ebx
.text: 0040AB25 movsx ebx, byte ptr [esi + 10h ] ; 将Number赋给ebx
.text: 0040AB29 xor edx, edx ; 清空edx
.text: 0040AB2B mov ecx, ebx ; 将Number赋给ecx
.text: 0040AB2D call @KiFindReadyThread@ 8 ; KiFindReadyThread(x,x)
.text: 0040AB32 test eax, eax
.text: 0040AB34 jz loc_41073E
.text: 0040AB3A
.text: 0040AB3A loc_40AB3A: ; CODE XREF: KiSwapThread() + 5C54 ↓j
.text: 0040AB3A ; KiSwapThread() + 5C78 ↓j ...
.text: 0040AB3A pop ebx
|
loc_40AB3B的代码就是通过调用KiSwapContext来完成线程切换,此时eax保存的是要切换的线程对象,调用前会将其赋给ecx,判断返回值是否为0
1 2 3 4 5 6 7 8 | .text: 0040AB3B loc_40AB3B: ; CODE XREF: KiSwapThread() + 5E33 ↓j
.text: 0040AB3B mov ecx, eax
.text: 0040AB3D call @KiSwapContext@ 4 ; KiSwapContext(x)
.text: 0040AB42 test al, al
.text: 0040AB44 mov cl, [edi + _KTHREAD.WaitIrql] ; NewIrql
.text: 0040AB47 mov edi, [edi + _ETHREAD.Tcb.WaitStatus]
.text: 0040AB4A mov esi, ds:__imp_@KfLowerIrql@ 4 ; KfLowerIrql(x)
.text: 0040AB50 jnz loc_41BC76
|
如果不为0,则调用KiDeliverApc来交付APC队列
1 2 3 4 5 6 7 8 9 10 | .text: 0041BC76 loc_41BC76: ; CODE XREF: KiSwapThread() + 46 ↑j
.text: 0041BC76 mov cl, 1 ; NewIrql
.text: 0041BC78 call esi ; KfLowerIrql(x) ; KfLowerIrql(x)
.text: 0041BC7A xor eax, eax
.text: 0041BC7C push eax ; int
.text: 0041BC7D push eax ; int
.text: 0041BC7E push eax ; PreviousMode
.text: 0041BC7F call _KiDeliverApc@ 12 ; KiDeliverApc(x,x,x)
.text: 0041BC84 xor cl, cl
.text: 0041BC86 jmp loc_40AB56
|
如果为0,退出函数执行
1 2 3 4 5 6 7 | .text: 0040AB56 loc_40AB56: ; CODE XREF: KiSwapThread() + 1117C ↓j
.text: 0040AB56 call esi ; KfLowerIrql(x) ; KfLowerIrql(x)
.text: 0040AB58 mov eax, edi
.text: 0040AB5A pop edi
.text: 0040AB5B pop esi
.text: 0040AB5C retn
.text: 0040AB5C @KiSwapThread@ 0 endp
|
KiSwapContext函数则将要切换的线程对象赋值到当前KPCR的当前线程中,通过调用函数SwapContext来完成切换
在SwapContext中,首先就将目标线程的状态改为Running
1 2 3 4 5 6 7 | .text: 00405942 SwapContext proc near ; CODE XREF: KiUnlockDispatcherDatabase(x) + 99 ↑p
.text: 00405942 ; KiSwapContext(x) + 2A ↑p ...
.text: 00405942
.text: 00405942 ; FUNCTION CHUNK AT .text: 00405AC9 SIZE 00000033 BYTES
.text: 00405942
.text: 00405942 or cl, cl
.text: 00405944 mov es:[esi + _KTHREAD.State], 2 ; 将当前线程状态赋值为Running
|
判断NpxState是否为0
1 2 3 4 5 6 | .text: 0040597D loc_40597D: ; CODE XREF: SwapContext + 18F ↓j
.text: 0040597D ; SwapContext + 1A0 ↓j ...
.text: 0040597D mov ebp, cr0
.text: 00405980 mov edx, ebp ; edx赋值为cr0
.text: 00405982 cmp [edi + _KTHREAD.NpxState], 0
.text: 00405986 jz loc_405AA4
|
如果为0,就会修改Cr0
1 2 3 4 5 6 7 8 | .text: 00405AA4 loc_405AA4: ; CODE XREF: SwapContext + 44 ↑j
.text: 00405AA4 and edx, 0FFFFFFF1h
.text: 00405AA7 mov ecx, [ebx + 4 ]
.text: 00405AAA cmp ebp, edx
.text: 00405AAC jz short _ScPatchFxb
.text: 00405AAE mov cr0, edx ; 修改cr0
.text: 00405AB1 mov ebp, edx
.text: 00405AB1 SwapContext
|
将当前栈顶地址赋给当前线程的KernelStack,将目标线程的栈底和栈边界分别赋值到KPCR相应的位置中,将栈底赋给eax后再提升0x210
1 2 3 4 5 6 7 8 9 10 | .text: 0040598C loc_40598C: ; CODE XREF: _ScPatchFxe + E↓j
.text: 0040598C mov cl, [esi + _ETHREAD.Tcb.DebugActive]
.text: 0040598F mov [ebx + KPCR.SpareUnused], cl
.text: 00405992 cli
.text: 00405993 mov [edi + _KTHREAD.KernelStack], esp ; 将当前栈顶保存到当前线程的KernelStack
.text: 00405996 mov eax, [esi + _KTHREAD.InitialStack] ; 取出目标线程的栈底赋给eax
.text: 00405999 mov ecx, [esi + _KTHREAD.StackLimit] ; 将目标线程的栈顶赋给ecx
.text: 0040599C sub eax, 210h ; 栈地址提升 0x210
.text: 004059A1 mov [ebx + KPCR.anonymous_0.NtTib.StackLimit], ecx
.text: 004059A4 mov [ebx + KPCR.anonymous_0.NtTib.StackBase], eax
|
再次将eax提升0x10以后赋值给TSS中的esp0供线程进行模式切换使用
1 2 3 4 5 | .text: 004059CA sub eax, 10h ; 栈地址提升 0x10
.text: 004059CD
.text: 004059CD loc_4059CD: ; CODE XREF: SwapContext + 86 ↑j
.text: 004059CD mov ecx, [ebx + KPCR.TSS]
.text: 004059D0 mov [ecx + 4 ], eax ; 将栈地址赋给TSS的esp0
|
将栈顶地址修改为目标线程的KernelStack,将目标线程的Teb
1 2 3 4 | .text: 004059D3 mov esp, [esi + _KTHREAD.KernelStack] ; 将目标线程的内核栈顶地址赋给esp
.text: 004059D6 mov eax, [esi + _KTHREAD.Teb]
.text: 004059D9 mov [ebx + KPCR.anonymous_0.NtTib.Self], eax ; 为KPCR的地址赋值
.text: 004059DC sti
|
判断当前线程和目标线程的所属进程是否相同
1 2 3 4 | .text: 004059DD mov eax, [edi + _KTHREAD.ApcState.Process] ; 将当前线程的Process赋给eax
.text: 004059E0 cmp eax, [esi + _KTHREAD.ApcState.Process] ; 判断目标线程与当前线程的Process是否相等
.text: 004059E3 mov [edi + _KTHREAD.IdleSwapBlock], 0
.text: 004059E7 jz short loc_405A29
|
如果不同会清空gs寄存器,修改cr3寄存器的内容为目标进程的页表基地址
1 2 3 4 5 6 7 8 9 | .text: 004059E9 mov edi, [esi + _KTHREAD.ApcState.Process] ; 将目标线程的Process赋给edi
.text: 00405A0D xor eax, eax
.text: 00405A0F mov gs, eax ; GS寄存器清 0
.text: 00405A11 assume gs:nothing
.text: 00405A11 mov eax, [edi + _KPROCESS.DirectoryTableBase] ; 取出当前线程的Cr3
.text: 00405A14 mov ebp, [ebx + KPCR.TSS]
.text: 00405A17 mov ecx, dword ptr [edi + _KPROCESS.IopmOffset]
.text: 00405A1A mov [ebp + 1Ch ], eax
.text: 00405A1D mov cr3, eax ; 修改cr3寄存器为当前线程的cr3
|
为了让fs:[0]指向的地址是正确的KPCR地址,需要将当前KPCR地址赋给fs寄存器段选择子对应的段描述符中,此时GDT表基址偏移0x38的地址对应的段描述符就是fs寄存器的段选择子对应的段描述符
1 2 3 4 5 6 7 | .text: 00405A34 loc_405A34: ; CODE XREF: SwapContext + E2↑j
.text: 00405A34 mov eax, [ebx + KPCR.anonymous_0.NtTib.Self] ; 将KPCR地址赋给eax
.text: 00405A37 mov ecx, [ebx + KPCR.GDT] ; 将GDT表地址赋给ecx
.text: 00405A3A mov [ecx + 3Ah ], ax ; 修改低 16 位地址
.text: 00405A3E shr eax, 10h ; 右移 0x10 得到高 16 位地址
.text: 00405A41 mov [ecx + 3Ch ], al ; 修改高 16 位的低 8 位
.text: 00405A44 mov [ecx + 3Fh ], ah ; 修改高 16 位的高 8 位
|
最后退出函数
1 2 3 4 5 6 7 8 9 | .text: 00405A47 inc [esi + _KTHREAD.ContextSwitches] ; 增加ContextSwitchs
.text: 00405A4A inc dword ptr [ebx + 61Ch ]
.text: 00405A50 pop ecx
.text: 00405A51 mov [ebx], ecx
.text: 00405A53 cmp [esi + _KTHREAD.ApcState.KernelApcPending], 0 ; 判断是否有内核APC要执行
.text: 00405A57 jnz short loc_405A5D
.text: 00405A59 popf
.text: 00405A5A xor eax, eax
.text: 00405A5C retn
|
七.参考资料
《Windows内核原理与实现》
《Windows内核源码情景分析》(上册)
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!
最后于 2021-12-24 11:26
被1900编辑
,原因: