进程线程004 Windows线程切换的三种方式
文章目录
- 主动切换(调用API)
- KiSwapContext函数分析
- 哪些API调用了SwapContext函数
- 总结
- 时钟中断切换
- 如何中断一个正在执行的程序
- 系统时钟
- 时钟中断的执行流程
- 总结
- 时间片管理
- 1.时间片到期
- 什么是时间片?
- 时间片什么时候发生改变?
- CPU时间片到期了如何处理?
- CPU时间片总结
- 2.存在备用线程
- 总结
主动切换(调用API)
之前我们已经学习了模拟Windows线程切换的代码,里面用于线程切换的函数就是SwitchContext。只要调用这个函数就会导致线程切换,Windows也有类似的函数:KiSwapContext
KiSwapContext函数分析
用IDA打开ntkrlpa.exe,找到KiSwapContext函数
.text:004699B4 sub esp, 10h
.text:004699B7 mov [esp+10h+var_4], ebx ; ------------------------
.text:004699BB mov [esp+10h+var_8], esi
.text:004699BF mov [esp+10h+var_C], edi ; 保存当前线程的寄存器现场
.text:004699C3 mov [esp+10h+var_10], ebp ; ---------------------------
首先,KiSwapContext保存当前线程的寄存器现场
.text:004699C6 mov ebx, ds:0FFDFF01Ch ; 取出KPCR存到ebx
接着取出KPCR存到ebx
.text:004699CC mov esi, ecx ; ecx是上一层调用的函数传进来的 是要切换线程的KTHREAD
这个ecx来自上一层函数的传参
Ctrl+X找到上一层调用
ecx来自于eax,而eax是KiFindReadyThread
函数的返回值,该函数会返回一个就绪线程的KTHREAD
.text:004699C6 mov ebx, ds:0FFDFF01Ch ; 取出KPCR存到ebx
.text:004699CC mov esi, ecx ; 要切换线程的KTHREAD
.text:004699CE mov edi, [ebx+124h] ; KPCR+0x124是当前线程的KTHREAD结构体
回到KiSwapContext函数,此时esi存储的是要切换线程的KTHREAD,edi是当前线程的KTHREAD。
.text:004699D4 mov [ebx+124h], esi ; 修改KPCR里的当前线程为目标线程
接着修改KPCR里的当前线程为目标线程
.text:004699DA mov cl, [edi+58h]
.text:004699DD call SwapContext ; 进行线程切换
接着调用SwapContext函数进行线程切换,跟进SwapContext函数。这个函数的代码比较复杂,先来看几个关键代码
这行代码将当前的ESP存储到KernelStack里,继续往下找到另外一行关键代码
这行代码将目标线程的KernelStack存到ESP里。真正的线程切换从这里开始,从这行代码往后已经不再是当前线程了,而是目标线程的堆栈。
哪些API调用了SwapContext函数
现在我们知道了只要调用了SwapContext就会导致线程切换,那么现在我们可以看一下到底有多少个API调用了这个函数
先找到KiSwapContext的函数KiSwapThread
打开交叉引用,可以看到有7个函数都调用了KiSwapThread。那就意味着只要我们调用了这里面的任何一个函数都会导致线程切换。
再来查看一下其中一个父函数KeWaitForSingleObject,看看这个函数被多少个函数调用
总共270个函数调用了父函数KeWaitForSingleObject,还有6个父函数我们没有查看。这270个函数如果再被其他函数调用也会导致线程切换
这样我们可以得出一个结论,绝大多数的内核函数都会调用SwapContext,导致线程切换
总结
- Windows中绝大部分的API都会调用SwapContext函数,也就是说,当线程调用了API就会导致线程切换
时钟中断切换
那么如果当前的线程不去调用系统API,操作系统是不是就无法实现线程切换了呢?实际上并不是这样?我们先要来分析一下如何中断一个正在执行的程序
如何中断一个正在执行的程序
- 异常 比如缺页或者INT N指令
- 中断 比如时钟中断
系统时钟
IDT表中断号 | IRQ | 说明 |
---|---|---|
0x30 | IRQ0 | 时钟中断 |
Windows系列的操作系统每隔10-20毫秒会触发一次时钟中断。如果要获取当前系统的时钟中断间隔,可使用W32 API:GetSystemTimeAdjustment
时钟中断的执行流程
接下来分析时钟中断的执行流程
用IDA打开ntkrnlpa.exe,搜索_IDT
找到中断号为0x30的中断处理函数,只要分析这个函数,就能知道系统的执行流程
这里调用了一个非当前模块的函数
在导入表中我们可以看到这个函数来自于HAL
然后又调用了一个HAL模块中的函数。
继续跟进,用IDA打开hal.dll,找到HalBeginSystemInterrupt函数。这个函数并没有回到ntkrnlpa.exe
再往下找到HalEnableSystemInterrupt函数
这个函数在内部又调用了KiDispatchInterrupt
接着搜索导入表,可以看到这个函数来自于ntoskrl内核文件
继续在ntoskrl跟进KiDispatchInterrupt函数
这个函数里面也调用了SwapContext。到这里,大致的流程也就分析完成了。这说明当时钟中断发生的时候,也会触发线程切换
时钟中断的执行流程:
- KiStartUnexpectedRange
- KiEndUnexpectedRange
- KiUnexpectedInterruptTail
- HalEndSystemInterrupt
- KiDispatchInterrupt
- SwapContext
总结
线程切换的几种情况
- 主动调用API函数
- 时钟中断
- 异常处理
如果一个线程不调用API,在代码中屏蔽中断,并且不会出现异常,那么当前线程将永久占有CPU。单核占有率100%,2核就是50%
时间片管理
我们已经知道时钟中断会导致线程进行切换,但并不是说只要有时钟中断就一定会切换线程,时钟中断时,两种情况会导致线程切换
- 当前线程的时间片到期
- 存在备用线程(KPCR.PrcbData.NextThread)
下面分别解释这两种情况
1.时间片到期
什么是时间片?
当一个新的线程开始执行的时候,初始化程序会在_KTHREAD.Quantum
赋初始值,该值的大小由_KPROCESS.ThreadQuantum
决定
我们在winbdg中随便查看一个进程结构体
kd> dt _KPROCESS 889e0288
ntdll!_KPROCESS+0x000 Header : _DISPATCHER_HEADER+0x010 ProfileListHead : _LIST_ENTRY [ 0x889e0298 - 0x889e0298 ]+0x018 DirectoryTableBase : 0x7ea1d520+0x01c LdtDescriptor : _KGDTENTRY+0x024 Int21Descriptor : _KIDTENTRY+0x02c ThreadListHead : _LIST_ENTRY [ 0x889e1960 - 0x88846e58 ]+0x034 ProcessLock : 0+0x038 Affinity : _KAFFINITY_EX+0x044 ReadyListHead : _LIST_ENTRY [ 0x889e02cc - 0x889e02cc ]+0x04c SwapListEntry : _SINGLE_LIST_ENTRY+0x050 ActiveProcessors : _KAFFINITY_EX+0x05c AutoAlignment : 0y0+0x05c DisableBoost : 0y0+0x05c DisableQuantum : 0y0+0x05c ActiveGroupsMask : 0y1+0x05c ReservedFlags : 0y0000000000000000000000000000 (0)+0x05c ProcessFlags : 0n8+0x060 BasePriority : 8 ''+0x061 QuantumReset : 6 ''
其中0x061这个位置的QuantumReset值为6。这就意味着当进程里面的线程开始执行的时候,初始化的程序就会将QuantumReset的值拿出来存到当前线程结构体的Quantum里。这个值就是当前线程时间片的大小
时间片什么时候发生改变?
每次时钟中断会调用KeUpdateRunTime函数,该函数每次将当前线程Quantum减少3个单位,如果减到0,则将KPCR.PrcbData.QuantumEnd
的值设置为非0
在IDA中找到KeUpdateRunTime函数
每一次时钟中断,都会把当前线程的CPU时间片减少3,
接着会判断这个值是否为0,如果为零,就会把QuantumEnd的值设置为非0,这个值是一个标志,标志着当前CPU的时间片有没有用完。
没有用完的时候这个值为0,如果用完了,会存储一个非0的值。
CPU时间片到期了如何处理?
KiDispatchInterrupt会判断时间片是否到期。
这个函数是每一次系统时钟中断最后要执行的函数
这行代码会判断当前的CPU时间片是否到期,当系统时间片到期后会发生跳转
如果时间片到期会将QuantumEnd修改为0,然后调用KiQuantumEnd函数,跟进这个函数
这个函数主要做的事情就是将CPU的时间片重新设置为ThreadQuantum,也就是最开始看的6
设置完成之后会调用KiFindReadyThread,通过这个函数找到下一个要运行的线程。找到以后函数返回。
也就是说KiQuantumEnd函数的作用是重设CPU时间片 找到下一个要运行的线程,接着跳转
跳转以后,先调用KiReadyThread将当前线程挂到就绪链表里,然后调用SwapContext切换线程
CPU时间片总结
- 当一个新的线程开始执行时,初始化程序会在
_KTHREAD.Quantum
赋初始值,该值的大小由_KPROCESS.ThreadQuantum
决定(观察ThreadQuantum大小) - 每次时钟中断会调用KeUpdateRunTime函数,该函数每次将当前线程Quantum减少3个单位,如果减到0,则将
KPCR.PrcbData.QuantumEnd
的值设置为非0 - KiDispatchInterrupt判断时间片到期后,调用KiQuantumEnd(重新设置时间片、找到要运行的线程)
2.存在备用线程
接着回到KiDispatchInterrupt函数,这里首先会判断CPU时间片是否到期,接着判断备用线程是否为0,如果在不为0有备用线程的前提下,继续往下执行
同样会调用SwapContext函数进行线程切换
如果以上两个条件都不满足,代码会进行跳转,函数直接retn返回,此时不会发生线程切换
总结
线程切换的三种情况
- 当前线程主动调用API:API函数–>KiSwapThread–>KiSwapContext–>SwapContext
- 当前线程的时间片到期:KiDispatchInterrrupt–>KiQuantumEnd–>KiSwapContext–>SwapContext
- 有备用线程:KiDispatchInterrrupt–>SwapContext
- 如果时钟中断的时候时间片没有到期且没有备用线程,那么函数会直接返回,不会发生线程切换
进程线程004 Windows线程切换的三种方式相关推荐
- 宿主机为linux、windows分别实现VMware三种方式上网(转)
一.VMware三种方式工作原理 1 Host-only连接方式 让虚机具有与宿主机不同的各自独立IP地址,但与宿主机位于不同网段,同时为宿主主机新增一个IP地址,且保证该IP地址与各虚机IP地址位 ...
- UE4中英文语言切换的三种方式(当然也可以多种语言)
一.用ue4的Localization Dashboard 1. 2. 3. 4. 5.最后,必须独立运行游戏才能看到效果 二.使用WidgetSwitcher 1. 2. 3. 4.用一个按钮点击进 ...
- windows结束线程的三种方式
windows 结束线程有三种方式 一.让线程函数执行到 return 二.在线程函数内调用 ExitThread 三.调用 TerminateThread 其中前两种方式比较类似,它们都是通过修改某 ...
- 实现线程的三种方式KLT/ULT/LWP
大家好,我是神韵,是一个技术&生活博主.关于文章都是定位为基础,我不敢讲的太深入,因为我怕自己没时间.欢迎来点赞打卡,你们的行动将是我无限的动力. 今日主题是:实现线程的三种方式KLT/LWP ...
- 对Java多线程编程的初步了解,实现多线程的三种方式以及多线程并发安全的线程同步机制
什么叫进程?什么叫线程? 进程相当于一个应用程序,线程就是进程中的一个应用场景或者说是一个执行单元,一个进程可以启动多个线程,每个线程执行不同的任务,一个线程不能单独存在,他必须是进程的一部分,当进程 ...
- java 多线程编程(包括创建线程的三种方式、线程的生命周期、线程的调度策略、线程同步、线程通信、线程池、死锁等)
1 多线程的基础知识 1.1 单核CPU和多核CPU 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务.微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那 ...
- 创建线程的三种方式、线程运行原理、常见方法、线程状态
文章目录 1.创建线程的三种方式 1.1 继承Thread类并重写run方法 1.2 使用Runnable配合Thread 1.3 通过Callable和FutureTask创建线程 2.Runnab ...
- java实现线程安全的三种方式
前言 一个程序在运行起来的时候会转换成进程,通常含有多个线程.通常情况下,一个进程中的比较耗时的操作(如长循环.文件上传下载.网络资源获取等),往往会采用多线程来解决. 比如现实生活中,银行取钱问题. ...
- Java 创建一个线程的三种方式
Java 创建一个线程的三种方式 更多内容,点击了解: https://how2j.cn/k/thread/thread-start/353.html 创建多线程有3种方式,分别是继承线程类,实现Ru ...
最新文章
- Python3中生成器介绍
- 归来吧,haproxy
- jquery 鼠标经过显示 信息小卡片
- Vue nextTick 机制
- DataGridView控件初始化,添加删除行(不绑定数据库)
- PHP中路由和rewrite的使用
- 快速入门 Jupyter notebook
- 没有工作经验找it_校招和社招有什么区别?没有工作经验,如何找工作?
- log4j2配置文件
- 百宝云在线表单云平台
- 网络——局域网和广域网
- 《当我谈跑步时我谈些什么》:痛苦难以避免,而磨难可以选择
- 《男孩别哭》海龟先生
- 网贷查询接口开发 网贷黑名单查询 个人网贷黑名单查询
- springboot事件通知
- 万能解决问题思路方法——3W
- MISC机制编写字符驱动程序
- 苹果手机浏览器 不支持line-height属性的解决方法
- 【图像超分辨】RDN
- 双机热备_磁盘阵列柜