前一篇,我们主要分析了线程切换的几种情况,其中一种是时钟中断,但并不是说只要有时钟中断就一定会切换线程,时钟中断时,两种情况会导致线程切换:
- 当前线程的CPU时间片到期
- 存在备用线程(KPCR.PrcbData.NextThread处值不为空)
CPU时间片
ThreadQuantum
当一个新的线程开始执行时,初始化程序会在KTHREAD.Quantum赋初始值,该值的大小由KPROCESS.ThreadQuantum决定 随机选择一个进程查看,发现ThreadQuantum的值为6。这个值,就是该进程的线程执行时的CPU时间片。那如何使用这个值呢?我们接下来继续看。
分析KeUpdateRunTime
每次时钟中断发生时都会先调用KeUpdateRunTime函数,我们来看看这个函数干了什么事 结合两张图来看,ebx保存的是当前线程的KTHREAD,接着将当前线程的Quantum的值-3。这下就清晰一些了,前面说了,一个线程初始的Quantum值为6,这里将Quantum的值 减了3。然后还做了什么呢,根据上面的信息,可以得知eax保存的KPCR,下面有一个逻辑判断,如果减3后值不为0,这里程序就跳转了,但是如果为0,此时程序会给KPCR+0x9AC处(QuantumEnd)的值赋上一个不为0的值,这个操作有什么用呢?往后看就知道了。
分析KiDispatchInterrupt
这里小盆友可能会奇怪了,为什么突然就从KeUpdateRunTime就跳到这了呢?这里需要说明一下,KeUpdateRumTime函数,是每次时钟中断发生时都会调用的函数,这个函数做了两件事:
- 将当前线程的KTHREAD.Quantum的值减3
- 若Quantum的值减到了0,则会将KPCR.QuantumEnd的值置为一个不为0的数
之后这个函数就执行完了,接着,就是我们上一篇分析过的,时钟中断的执行流程,最终,在进行线程切换之前,会执行到KiDispatchInterrupt函数,接下来,就来看看刚刚修改过的两个值,和这个函数有何关系:
- 进入KiDispatchInterrupt函数,这里有一个判断,稍作分析 这里的ebx存着的是KPCR,然后程序会去判断KPCR.QuantumEnd处的值是否为0,如果不是0,说明时间片走完了,也就是KTHREAD.Quantum值被减为0了,这是就会进行跳转,图中会跳转到loc_404902的位置
- 跟到loc_404902的位置继续观察, 这里先将KPCR.QuantumEnd的值置零,然后跳转到KiQuantumEnd函数中继续执行(为什么先赋值,再清零呢?因为已经判断过了,已经跳转到这里了,将QuantumEnd的值置零,也是为了下一个执行的线程)
- 好,进入KiQuantumEnd函数 具体细节看图,这部分,主要是重新设置了当前这个线程的CPU时间片的值为ThreadQuantum。接着往下看 然后这个函数调用了KiFindReadyThread函数,在就绪队列中找到一个线程,接着就返回了
- 执行完KiQuantumEnd函数后,我们又回到了KiDispatchInterrupt函数 如果刚刚在KiFindReadyThread可以在就绪队列中找到一个线程,那么eax的值就不为空,如图,接下来会跳转到loc_4048BB的位置
- 接着看4048BB的位置 看到了我们熟悉的线程切换函数
小结
通过分析KeUpdateRunTime和KiDispatchInterrupt函数,我们可以发现。在CPU时间片用完的情况下,当时钟中断发生时,会发生线程的切换,这里做个小结:
- 当一个新的线程开始执行时,初始化程序会在KTHREAD.Quantum赋初始值,该值的大小由KPRCOESS.ThreadQuantum决定
- 每次时钟中断会调用KeUpdateRunTime函数,该函数每次将当前线程Quantum减少3个单,如果减到0,则将KPCR.PrcbData.QuantumEnd的值设置为非0
- KiDispatchInterrupt判断时间片到期后,调用KiQuantumEnd函数(重新设置时间片、找到要运行的线程)
备用线程
这里我们直接定位到KiDispatchInterrupt的位置,看图 可以发现,这是刚刚判断时间片是否到期的位置,这里共有两个判断:
- 若CPU时间片到期(即KPCR.QuantumEnd的值为非0),则跳转,否则继续执行
- 若存在备用线程,则将备用线程取出。啥是备用线程呢?就是KPCR+0x128的位置,该处成员名称叫做NextThread,是一个KTHREAD结构。如果这个位置的值不为0,那么程序会继续执行
后面的事情,看图片也就知道了。取出备用线程后,会先将当前线程放入就绪链表。这里为什么不放入等待链表呢?因为该线程处于就绪状态,只是在时钟中断发生时CPU时间片走完了或者存在备用线程,所以不会放入等待链表中。
当然。如果两个判断都没有执行,程序会直接跳到最后,返回了,也就是不发生线程切换
总结
当前线程主动调用API:
KiSwapThread -> KiSwapContext -> SwapContext
当前线程时间片到期:
KiDispatchInterrupt -> KiQuantumEnd -> SwapContext存在备用线程:
KiDispatchInterrupt -> SwapContext
参考教程:https://www.bilibili.com/video/BV1NJ411M7aE?p=50
参考文章: