进程线程004 Windows线程切换的三种方式
文章目錄
- 主動切換(調(diào)用API)
- KiSwapContext函數(shù)分析
- 哪些API調(diào)用了SwapContext函數(shù)
- 總結(jié)
- 時鐘中斷切換
- 如何中斷一個正在執(zhí)行的程序
- 系統(tǒng)時鐘
- 時鐘中斷的執(zhí)行流程
- 總結(jié)
- 時間片管理
- 1.時間片到期
- 什么是時間片?
- 時間片什么時候發(fā)生改變?
- CPU時間片到期了如何處理?
- CPU時間片總結(jié)
- 2.存在備用線程
- 總結(jié)
主動切換(調(diào)用API)
之前我們已經(jīng)學習了模擬Windows線程切換的代碼,里面用于線程切換的函數(shù)就是SwitchContext。只要調(diào)用這個函數(shù)就會導(dǎo)致線程切換,Windows也有類似的函數(shù):KiSwapContext
KiSwapContext函數(shù)分析
用IDA打開ntkrlpa.exe,找到KiSwapContext函數(shù)
.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 ; 保存當前線程的寄存器現(xiàn)場 .text:004699C3 mov [esp+10h+var_10], ebp ; ---------------------------首先,KiSwapContext保存當前線程的寄存器現(xiàn)場
.text:004699C6 mov ebx, ds:0FFDFF01Ch ; 取出KPCR存到ebx接著取出KPCR存到ebx
.text:004699CC mov esi, ecx ; ecx是上一層調(diào)用的函數(shù)傳進來的 是要切換線程的KTHREAD這個ecx來自上一層函數(shù)的傳參
Ctrl+X找到上一層調(diào)用
ecx來自于eax,而eax是KiFindReadyThread函數(shù)的返回值,該函數(shù)會返回一個就緒線程的KTHREAD
.text:004699C6 mov ebx, ds:0FFDFF01Ch ; 取出KPCR存到ebx .text:004699CC mov esi, ecx ; 要切換線程的KTHREAD .text:004699CE mov edi, [ebx+124h] ; KPCR+0x124是當前線程的KTHREAD結(jié)構(gòu)體回到KiSwapContext函數(shù),此時esi存儲的是要切換線程的KTHREAD,edi是當前線程的KTHREAD。
.text:004699D4 mov [ebx+124h], esi ; 修改KPCR里的當前線程為目標線程接著修改KPCR里的當前線程為目標線程
.text:004699DA mov cl, [edi+58h] .text:004699DD call SwapContext ; 進行線程切換接著調(diào)用SwapContext函數(shù)進行線程切換,跟進SwapContext函數(shù)。這個函數(shù)的代碼比較復(fù)雜,先來看幾個關(guān)鍵代碼
這行代碼將當前的ESP存儲到KernelStack里,繼續(xù)往下找到另外一行關(guān)鍵代碼
這行代碼將目標線程的KernelStack存到ESP里。真正的線程切換從這里開始,從這行代碼往后已經(jīng)不再是當前線程了,而是目標線程的堆棧。
哪些API調(diào)用了SwapContext函數(shù)
現(xiàn)在我們知道了只要調(diào)用了SwapContext就會導(dǎo)致線程切換,那么現(xiàn)在我們可以看一下到底有多少個API調(diào)用了這個函數(shù)
先找到KiSwapContext的函數(shù)KiSwapThread
打開交叉引用,可以看到有7個函數(shù)都調(diào)用了KiSwapThread。那就意味著只要我們調(diào)用了這里面的任何一個函數(shù)都會導(dǎo)致線程切換。
再來查看一下其中一個父函數(shù)KeWaitForSingleObject,看看這個函數(shù)被多少個函數(shù)調(diào)用
總共270個函數(shù)調(diào)用了父函數(shù)KeWaitForSingleObject,還有6個父函數(shù)我們沒有查看。這270個函數(shù)如果再被其他函數(shù)調(diào)用也會導(dǎo)致線程切換
這樣我們可以得出一個結(jié)論,絕大多數(shù)的內(nèi)核函數(shù)都會調(diào)用SwapContext,導(dǎo)致線程切換
總結(jié)
時鐘中斷切換
那么如果當前的線程不去調(diào)用系統(tǒng)API,操作系統(tǒng)是不是就無法實現(xiàn)線程切換了呢?實際上并不是這樣?我們先要來分析一下如何中斷一個正在執(zhí)行的程序
如何中斷一個正在執(zhí)行的程序
系統(tǒng)時鐘
| 0x30 | IRQ0 | 時鐘中斷 |
Windows系列的操作系統(tǒng)每隔10-20毫秒會觸發(fā)一次時鐘中斷。如果要獲取當前系統(tǒng)的時鐘中斷間隔,可使用W32 API:GetSystemTimeAdjustment
時鐘中斷的執(zhí)行流程
接下來分析時鐘中斷的執(zhí)行流程
用IDA打開ntkrnlpa.exe,搜索_IDT
找到中斷號為0x30的中斷處理函數(shù),只要分析這個函數(shù),就能知道系統(tǒng)的執(zhí)行流程
這里調(diào)用了一個非當前模塊的函數(shù)
在導(dǎo)入表中我們可以看到這個函數(shù)來自于HAL
然后又調(diào)用了一個HAL模塊中的函數(shù)。
繼續(xù)跟進,用IDA打開hal.dll,找到HalBeginSystemInterrupt函數(shù)。這個函數(shù)并沒有回到ntkrnlpa.exe
再往下找到HalEnableSystemInterrupt函數(shù)
這個函數(shù)在內(nèi)部又調(diào)用了KiDispatchInterrupt
接著搜索導(dǎo)入表,可以看到這個函數(shù)來自于ntoskrl內(nèi)核文件
繼續(xù)在ntoskrl跟進KiDispatchInterrupt函數(shù)
這個函數(shù)里面也調(diào)用了SwapContext。到這里,大致的流程也就分析完成了。這說明當時鐘中斷發(fā)生的時候,也會觸發(fā)線程切換
時鐘中斷的執(zhí)行流程:
總結(jié)
線程切換的幾種情況
如果一個線程不調(diào)用API,在代碼中屏蔽中斷,并且不會出現(xiàn)異常,那么當前線程將永久占有CPU。單核占有率100%,2核就是50%
時間片管理
我們已經(jīng)知道時鐘中斷會導(dǎo)致線程進行切換,但并不是說只要有時鐘中斷就一定會切換線程,時鐘中斷時,兩種情況會導(dǎo)致線程切換
下面分別解釋這兩種情況
1.時間片到期
什么是時間片?
當一個新的線程開始執(zhí)行的時候,初始化程序會在_KTHREAD.Quantum賦初始值,該值的大小由_KPROCESS.ThreadQuantum決定
我們在winbdg中隨便查看一個進程結(jié)構(gòu)體
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。這就意味著當進程里面的線程開始執(zhí)行的時候,初始化的程序就會將QuantumReset的值拿出來存到當前線程結(jié)構(gòu)體的Quantum里。這個值就是當前線程時間片的大小
時間片什么時候發(fā)生改變?
每次時鐘中斷會調(diào)用KeUpdateRunTime函數(shù),該函數(shù)每次將當前線程Quantum減少3個單位,如果減到0,則將KPCR.PrcbData.QuantumEnd的值設(shè)置為非0
在IDA中找到KeUpdateRunTime函數(shù)
每一次時鐘中斷,都會把當前線程的CPU時間片減少3,
接著會判斷這個值是否為0,如果為零,就會把QuantumEnd的值設(shè)置為非0,這個值是一個標志,標志著當前CPU的時間片有沒有用完。
沒有用完的時候這個值為0,如果用完了,會存儲一個非0的值。
CPU時間片到期了如何處理?
KiDispatchInterrupt會判斷時間片是否到期。
這個函數(shù)是每一次系統(tǒng)時鐘中斷最后要執(zhí)行的函數(shù)
這行代碼會判斷當前的CPU時間片是否到期,當系統(tǒng)時間片到期后會發(fā)生跳轉(zhuǎn)
如果時間片到期會將QuantumEnd修改為0,然后調(diào)用KiQuantumEnd函數(shù),跟進這個函數(shù)
這個函數(shù)主要做的事情就是將CPU的時間片重新設(shè)置為ThreadQuantum,也就是最開始看的6
設(shè)置完成之后會調(diào)用KiFindReadyThread,通過這個函數(shù)找到下一個要運行的線程。找到以后函數(shù)返回。
也就是說KiQuantumEnd函數(shù)的作用是重設(shè)CPU時間片 找到下一個要運行的線程,接著跳轉(zhuǎn)
跳轉(zhuǎn)以后,先調(diào)用KiReadyThread將當前線程掛到就緒鏈表里,然后調(diào)用SwapContext切換線程
CPU時間片總結(jié)
2.存在備用線程
接著回到KiDispatchInterrupt函數(shù),這里首先會判斷CPU時間片是否到期,接著判斷備用線程是否為0,如果在不為0有備用線程的前提下,繼續(xù)往下執(zhí)行
同樣會調(diào)用SwapContext函數(shù)進行線程切換
如果以上兩個條件都不滿足,代碼會進行跳轉(zhuǎn),函數(shù)直接retn返回,此時不會發(fā)生線程切換
總結(jié)
線程切換的三種情況
總結(jié)
以上是生活随笔為你收集整理的进程线程004 Windows线程切换的三种方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程线程003 模拟线程切换
- 下一篇: 进程线程006 Windows线程切换-