windows内核情景分析---进程线程2
二、線程調(diào)度與切換
眾所周知:Windows系統(tǒng)是一個(gè)分時(shí)搶占式系統(tǒng),分時(shí)指每個(gè)線程分配時(shí)間片,搶占指時(shí)間片到期前,中途可以被其他更高優(yōu)先級(jí)的線程強(qiáng)制搶占。
背景知識(shí):每個(gè)cpu都有一個(gè)TSS,叫‘任務(wù)狀態(tài)段’。這個(gè)TSS內(nèi)部中的一些字段記錄著該cpu上當(dāng)前正在運(yùn)行的那個(gè)線程的一些信息(如ESP0記錄著該線程的內(nèi)核棧位置,IO權(quán)限位圖記錄著當(dāng)前線程的IO空間權(quán)限)
IO空間有64KB,IO權(quán)限位圖中的每一位記錄著對(duì)應(yīng)IO地址的IN、OUT許可權(quán)限,所以IO權(quán)限位圖本身有8KB大小,TSS中就就記錄著當(dāng)前線程IO權(quán)限位圖的偏移位置。
每當(dāng)切換線程時(shí):自然要跟著修改TSS中的ESP0和IO權(quán)限位圖。TSS0中為什么要保存當(dāng)前線程的內(nèi)核棧位置?原因是:每當(dāng)一個(gè)線程內(nèi)部,從用戶模式進(jìn)入內(nèi)核模式時(shí),需要將cpu中的esp換成該線程的內(nèi)核棧(各線程的內(nèi)核棧是不同的)每當(dāng)進(jìn)入內(nèi)核模式時(shí),cpu就自動(dòng)從TSS中找到ESP0,然后MOV?ESP,?TSS.ESP0,換成內(nèi)核棧后,cpu然后在內(nèi)核棧中壓入浮點(diǎn)寄存器和標(biāo)準(zhǔn)的5個(gè)寄存器:原cs、原eip、原ss、原esp、原eflags。這就是為什么需要在TSS中記錄當(dāng)前線程的內(nèi)核棧地址。(注意ESP0并不是棧底地址,而是要壓入保存寄存器處的存放地址)
與線程切換相關(guān)的數(shù)據(jù)結(jié)構(gòu)定義:
Struct?KPCR?//處理器控制塊(內(nèi)核中的fs寄存器總是指向這個(gè)結(jié)構(gòu)體的基址)
{
???KPCR_TIB?Tib;
???KPCR*?self;//方便尋址
???KPRCB*?Prcb;
???KIRQL?irql;//物理上表示cpu的當(dāng)前中斷級(jí),邏輯上理解為當(dāng)前線程的中斷級(jí)更好
???USHORT*?IDT;//本cpu的中斷描述符表的地址
???USHORT*?GDT;//本cpu的全局描述符表的地址
???KTSS*?TSS;//本cpu上當(dāng)前線程的信息(ESP0)
???…
}
Struct?KPCR_TIB
{
???Void*?ExceptionList;//當(dāng)前線程的內(nèi)核seh鏈表頭結(jié)點(diǎn)地址
???Void*?StackBase;//內(nèi)核棧底地址
???Void*?StackLimit;//棧的提交邊界
???…
???KPCR_TIB*?self;//方便尋址
}
Struct?KPRCB
{
???…
???KTHREAD*?CurrentThread;//本cpu上當(dāng)前正在運(yùn)行的線程
???KTHREAD*?NextThread;//將剝奪(即搶占)當(dāng)前線程的下一個(gè)線程
??KTHREAD*?IdleThread;//空轉(zhuǎn)線程
??BOOL?QuantumEnd;//重要字段。指當(dāng)前線程的時(shí)間片是否已經(jīng)用完。
??LIST_ENTRY?WaitListHead;//本cpu的等待線程隊(duì)列
??ULONG?ReadSummary;//各就緒隊(duì)列中是否為空的標(biāo)志
??ULONG?SelectNextLast;
??LIST_ENTRY?DispatcherReadyListHead[32];//對(duì)應(yīng)32個(gè)優(yōu)先級(jí)的32個(gè)就緒線程隊(duì)列
??FX_SAVE_AREA?NpxSaveArea;
??…
}
typedef?struct?_KSWITCHFRAME??//切換幀(用來(lái)保存切換線程)
{
PVOID?ExceptionList;//保存線程切換時(shí)的內(nèi)核she鏈表(不是用戶空間中的seh)
Union
 {
BOOLEAN?ApcBypassDisable;//用于首次調(diào)度
UCHAR?WaitIrql;//用于保存切換時(shí)的WaitIrql
};
//實(shí)際上首次時(shí)為KiThreadStartup,以后都固定為call?KiSwapContextInternal后面的那條指令
????PVOID?RetAddr;//保存發(fā)生切換時(shí)的斷點(diǎn)地址(以后切換回來(lái)時(shí)從這兒繼續(xù)執(zhí)行)
}?KSWITCHFRAME,?*PKSWITCHFRAME;
typedef?struct?_KTRAP_FRAME?//Trap現(xiàn)場(chǎng)幀
{
???------------------這些是KiSystemService保存的---------------------------
????ULONG?DbgEbp;
????ULONG?DbgEip;
????ULONG?DbgArgMark;
????ULONG?DbgArgPointer;
????ULONG?TempSegCs;
????ULONG?TempEsp;
????ULONG?Dr0;
????ULONG?Dr1;
????ULONG?Dr2;
????ULONG?Dr3;
????ULONG?Dr6;
????ULONG?Dr7;
????ULONG?SegGs;
????ULONG?SegEs;
????ULONG?SegDs;
????ULONG?Edx;//xy?這個(gè)位置不是用來(lái)保存edx的,而是用來(lái)保存上個(gè)Trap幀,因?yàn)門rap幀是可以嵌套的
????ULONG?Ecx;?//中斷和異常引起的自陷要保存eax,系統(tǒng)調(diào)用則不需保存ecx
????ULONG?Eax;//中斷和異常引起的自陷要保存eax,系統(tǒng)調(diào)用則不需保存eax
????ULONG?PreviousPreviousMode;
????struct?_EXCEPTION_REGISTRATION_RECORD?FAR?*ExceptionList;//上次seh鏈表的開頭地址
????ULONG?SegFs;
????ULONG?Edi;
????ULONG?Esi;
????ULONG?Ebx;
ULONG?Ebp;
----------------------------------------------------------------------------------------
ULONG?ErrCode;//發(fā)生的不是中斷,而是異常時(shí),cpu還會(huì)自動(dòng)在棧中壓入對(duì)應(yīng)的具體異常碼在這兒
-----------下面5個(gè)寄存器是由int?2e內(nèi)部本身保存的或KiFastCallEntry模擬保存的現(xiàn)場(chǎng)---------
????ULONG?Eip;
????ULONG?SegCs;
????ULONG?EFlags;
????ULONG?HardwareEsp;
ULONG?HardwareSegSs;
---------------以下用于用于保存V86模式的4個(gè)寄存器也是cpu自動(dòng)壓入的-------------------
????ULONG?V86Es;
????ULONG?V86Ds;
????ULONG?V86Fs;
ULONG?V86Gs;
}?KTRAP_FRAME,?*PKTRAP_FRAME;
下面這個(gè)核心函數(shù)用來(lái)切換線程(從當(dāng)前線程切換到新線程去)。這個(gè)函數(shù)的原型是:
BOOL?FASTCALL?KiSwapContex(KTHREAD*?Currentthread*,?KTHREAD*?NewThread);
返回值表示下次切換回來(lái)時(shí)是否需要手動(dòng)掃描執(zhí)行內(nèi)核APC。這個(gè)函數(shù)的匯編代碼為:
@KiSwapContext@8:???//開頭的@表示fastcall調(diào)用約定
{
sub?esp,?4?*?4?//騰出局部變量空間
//保存這4個(gè)寄存器,因?yàn)镵iSwapContextInternal函數(shù)內(nèi)部要使用這幾個(gè)寄存器
????mov?[esp+12],?ebx
????mov?[esp+8],?esi
????mov?[esp+4],?edi
????mov?[esp+0],?ebp
????mov?ebx,?fs:[KPCR_SELF]?//ebx=當(dāng)前cpu的KPCR*
????mov?edi,?ecx?//edi=?KiSwapContext的第一個(gè)參數(shù),即CurrentThread
????mov?esi,?edx?//edi=?KiSwapContext的第而個(gè)參數(shù),即NewThread
????movzx?ecx,?byte?ptr?[edi+KTHREAD_WAIT_IRQL]?//ecx=當(dāng)前線程的WaitIrql
call?@KiSwapContextInternal@0??//調(diào)用真正的切換工作函數(shù)
這中間已經(jīng)被切換到新線程去了,當(dāng)前線程已經(jīng)讓出cpu,掛入了就緒隊(duì)列。需要等到下次重新被調(diào)度運(yùn)行時(shí),才又從這兒的斷點(diǎn)處繼續(xù)向下執(zhí)行下去
mov?ebp,?[esp+0]??//這條指令就是斷點(diǎn)處,以后切換回來(lái)時(shí)就從這個(gè)斷點(diǎn)處繼續(xù)執(zhí)行
????mov?edi,?[esp+4]
????mov?esi,?[esp+8]
????mov?ebx,?[esp+12
????add?esp,?4?*?4
????ret
}
下面的函數(shù)完成真正的切換工作(返回值表示切換回來(lái)后是否需要手動(dòng)掃描執(zhí)行內(nèi)核apc)
@KiSwapContextInternal@0:??//edi指向當(dāng)前線程,esi指向要切換到的新線程,ebx指向當(dāng)前KPCR*
{
inc?dword?ptr?es:[ebx+KPCR_CONTEXT_SWITCHES]??//遞增當(dāng)前cpu上發(fā)生的歷史線程切換計(jì)數(shù)
????push?ecx??//保存本線程切換時(shí)的WaitIrql
push?[ebx+KPCR_EXCEPTION_LIST]?//保存本線程切換時(shí)的內(nèi)核seh鏈表
-------------------------至此,上面的兩條push連同本函數(shù)的返回地址(即斷點(diǎn)地址),就構(gòu)成了一個(gè)切換幀。當(dāng)前線程切換時(shí)的內(nèi)核棧頂位置就在此處-----------------------------
AfterTrace:
????mov?ebp,?cr0
????mov?edx,?ebp??//將cr0寄存器保存在edx中(cr0的Bit3位“TaskSwitched”標(biāo)志位,與浮點(diǎn)運(yùn)算相關(guān))
SetStack:
????mov?[edi+KTHREAD_KERNEL_STACK],?esp?//保存本線程切換時(shí)的內(nèi)核棧頂位置
mov?eax,?[esi+KTHREAD_INITIAL_STACK]?//eax=新線程的內(nèi)核棧底地址
--------------------------------------------------------------------------------
????cli??//下面檢查Npx浮點(diǎn)寄存器,要關(guān)中斷
????movzx?ecx,?byte?ptr?[esi+KTHREAD_NPX_STATE]?//ecx=新線程的Npx狀態(tài)
????and?edx,?~(CR0_MP?+?CR0_EM?+?CR0_TS)
????or?ecx,?edx
????or?ecx,?[eax?-?(NPX_FRAME_LENGTH?-?FN_CR0_NPX_STATE)]?//獲得新線程需要的cr0
????cmp?ebp,?ecx
????jnz?NewCr0?//如果新線程需要的cr0不同于當(dāng)前的cr0,則修改當(dāng)前cr0為新線程的cr0
StackOk:
Sti
--------------------------------------------------------------------------------
mov?esp,?[esi+KTHREAD_KERNEL_STACK]?//關(guān)鍵。恢復(fù)成新線程當(dāng)初被切換時(shí)的內(nèi)核棧頂
????mov?ebp,?[esi+KTHREAD_APCSTATE_PROCESS]?//ebp=目標(biāo)進(jìn)程
????mov?eax,?[edi+KTHREAD_APCSTATE_PROCESS]?//eax=當(dāng)前進(jìn)程
????cmp?ebp,?eax??//檢查是否是切換到同一個(gè)進(jìn)程中的其他線程(若是。就不用切換LDT和cr3)
jz?SameProcess?
//若切換到其他進(jìn)程中的線程,則要同時(shí)修改LDT和CR3
????mov?ecx,?[ebp+KPROCESS_LDT_DESCRIPTOR0]
????or?ecx,?[eax+KPROCESS_LDT_DESCRIPTOR0]
????jnz?LdtReload?//如果兩個(gè)進(jìn)程的LDT不同,就要換用不同的LDT
UpdateCr3:
????mov?eax,?[ebp+KPROCESS_DIRECTORY_TABLE_BASE]
????mov?cr3,?eax?//關(guān)鍵。將cr3換成目標(biāo)進(jìn)程的頁(yè)目錄
SameProcess:
????xor?eax,?eax
????mov?gs,?ax
????mov?eax,?[esi+KTHREAD_TEB]?//新線程的TEB地址
????mov?[ebx+KPCR_TEB],?eax?//當(dāng)前KPCR中的TEB指向新線程的TEB
mov?ecx,?[ebx+KPCR_GDT]
//修改GDT中的TEB描述符,指向新線程的TEB
????mov?[ecx+0x3A],?ax
????shr?eax,?16
????mov?[ecx+0x3C],?al
????mov?[ecx+0x3F],?ah
????mov?eax,?[esi+KTHREAD_INITIAL_STACK]?//eax=新線程的內(nèi)核棧底位置
????sub?eax,?NPX_FRAME_LENGTH?//跳過(guò)浮點(diǎn)保存區(qū)空間
????test?dword?ptr?[eax?-?KTRAP_FRAME_SIZE?+?KTRAP_FRAME_EFLAGS],?EFLAGS_V86_MASK
????jnz?NoAdjust?//檢查新線程是否運(yùn)行在V86模式
????sub?eax,?KTRAP_FRAME_V86_GS?-?KTRAP_FRAME_SS?//跳過(guò)V86保存區(qū)
NoAdjust:
????mov?ecx,?[ebx+KPCR_TSS]
????mov?[ecx+KTSS_ESP0],?eax??//關(guān)鍵,修改TSS中的ESP0,指向新線程的內(nèi)核棧底
????mov?ax,?[ebp+KPROCESS_IOPM_OFFSET]
????mov?[ecx+KTSS_IOMAPBASE],?ax??//修改TSS中的IO權(quán)限位圖偏移指向新進(jìn)程中的IO權(quán)限位圖
????inc?dword?ptr?[esi+KTHREAD_CONTEXT_SWITCHES]?//遞增線程的切換次數(shù)(也即歷史調(diào)度次數(shù))
????pop?[ebx+KPCR_EXCEPTION_LIST]?//將當(dāng)前KPCR中記錄的seh鏈表恢復(fù)成新線程的seh鏈表
????pop?ecx?//ecx=新線程原來(lái)切換前的WaitIrql
????cmp?byte?ptr?[ebx+KPCR_PRCB_DPC_ROUTINE_ACTIVE],?0?//檢查當(dāng)前是否有DPC函數(shù)處于活動(dòng)狀態(tài)
????jnz?BugCheckDpc?//藍(lán)屏
//至此,cpu中的寄存器內(nèi)容全部換成了新線程的那些寄存器,從這個(gè)意思上說(shuō),此時(shí)就已完成了全部切換工作,下面的代碼都是在新線程的環(huán)境中運(yùn)行了。
--------------------------------新線程環(huán)境---------------------------------------
????cmp?byte?ptr?[esi+KTHREAD_PENDING_KERNEL_APC],?0
????jnz?CheckApc?//看到?jīng)],每次線程得到重新調(diào)度運(yùn)行前,都會(huì)掃描執(zhí)行內(nèi)核apc隊(duì)列中的函數(shù)
????xor?eax,?eax?
????ret?//此處返回值表示沒(méi)有內(nèi)核apc
CheckApc:
????cmp?word?ptr?[esi+KTHREAD_SPECIAL_APC_DISABLE],?0?//檢查是否禁用了APC
????jnz?ApcReturn
????test?cl,?cl??//檢查WaitIrql,如果是APC級(jí),就在本函數(shù)內(nèi)部返回前,發(fā)出apc中斷
????jz?ApcReturn
????//if(SPECIAL?APC?沒(méi)禁用?&&?WaitIrql!=PASSIVE_LEVEL),切換回來(lái)時(shí)就先執(zhí)行內(nèi)核APC
????mov?cl,?APC_LEVEL
????call?@HalRequestSoftwareInterrupt@4?//發(fā)出一個(gè)apc中斷
????or?eax,?esp?//既然發(fā)出apc中斷了,那么就return?FALSE表示無(wú)需手動(dòng)掃描執(zhí)行apc
ApcReturn:
????setz?al
ret??//此處返回值表示切回來(lái)后是否需要手動(dòng)掃描執(zhí)行apc
//當(dāng)這個(gè)函數(shù)返回時(shí),之前已經(jīng)換成新線程的內(nèi)核棧了。當(dāng)函數(shù)返回后,將回到KiSwapContext中,當(dāng)KiSwapContext返回到調(diào)用方時(shí),那個(gè)調(diào)用方就是新線程當(dāng)初調(diào)用的KiSwapContext的函數(shù),這樣,就沿著新線程的內(nèi)核棧,逐級(jí)向上回溯到新線程中了。因此,可以說(shuō),切換內(nèi)核棧,即是切換線程。
LdtReload:
????mov?eax,?[ebp+KPROCESS_LDT_DESCRIPTOR0]
????test?eax,?eax??//檢測(cè)目標(biāo)進(jìn)程有沒(méi)有LDT
????jz?LoadLdt
????mov?ecx,?[ebx+KPCR_GDT]
????mov?[ecx+KGDT_LDT],?eax?//改指目標(biāo)進(jìn)程的LDT
????mov?eax,?[ebp+KPROCESS_LDT_DESCRIPTOR1]
????mov?[ecx+KGDT_LDT+4],?eax//改指目標(biāo)進(jìn)程的LDT
????/*?Write?the?INT21?handler?*/
????mov?ecx,?[ebx+KPCR_IDT]
????mov?eax,?[ebp+KPROCESS_INT21_DESCRIPTOR0]
????mov?[ecx+0x108],?eax
????mov?eax,?[ebp+KPROCESS_INT21_DESCRIPTOR1]
????mov?[ecx+0x10C],?eax
????mov?eax,?KGDT_LDT
LoadLdt:
????lldt?ax
????jmp?UpdateCr3
NewCr0:
????mov?cr0,?ecx
????jmp?StackOk
BugCheckDpc:
????mov?eax,?[edi+KTHREAD_INITIAL_STACK]
????push?0
????push?eax
????push?esi
????push?edi
????push?ATTEMPTED_SWITCH_FROM_DPC
????call?_KeBugCheckEx@20???//藍(lán)屏提示:“嘗試從活動(dòng)DPC例程中切換線程”
}
如上:線程從KiSwapContextInternal這個(gè)函數(shù)內(nèi)部切換出去,某一時(shí)刻又切換回這個(gè)函數(shù)內(nèi)。
或者也可以理解為:線程從KiSwapContext這個(gè)函數(shù)切換出去,某一時(shí)刻又切換回這個(gè)函數(shù)內(nèi)。
(注:可以hook這兩個(gè)函數(shù),來(lái)達(dá)到檢測(cè)隱藏進(jìn)程的目的)
明白了線程切換的過(guò)程,所做的工作后,接下來(lái)看:線程的切換時(shí)機(jī)(也即一個(gè)線程什么時(shí)候會(huì)調(diào)用
KiSwapContext這個(gè)函數(shù)把自己切換出去),相信這是大伙最感興趣的問(wèn)題。
三、線程的調(diào)度策略與切換時(shí)機(jī)
調(diào)度策略:Windows嚴(yán)格按優(yōu)先級(jí)調(diào)度線程。
優(yōu)先級(jí)分成32個(gè),每個(gè)cpu對(duì)應(yīng)有32個(gè)就緒線程隊(duì)列。每當(dāng)要發(fā)生線程切換時(shí),就根據(jù)調(diào)度策略從32條就緒隊(duì)列中,按優(yōu)先級(jí)從高到低的順序掃描(同一個(gè)就緒隊(duì)列中,由于優(yōu)先級(jí)相同,則按FIFO順序掃描),這樣,從32條就緒隊(duì)列中,找到優(yōu)先級(jí)最高的那個(gè)候選就緒線程,給予調(diào)度執(zhí)行。
當(dāng)一個(gè)線程得到調(diào)度執(zhí)行時(shí),如果一直沒(méi)有任何其他就緒線程的優(yōu)先級(jí)高于本線程,本線程就可以暢通無(wú)阻地一直執(zhí)行下去,直到本次的時(shí)間片用完。但是如果本次執(zhí)行的過(guò)程中,如果有個(gè)就緒線程的優(yōu)先級(jí)突然高于了本線程,那么本線程將被搶占,cpu將轉(zhuǎn)去執(zhí)行那個(gè)線程。但是,這種搶占可能不是立即性的,只有在當(dāng)前線程的irql在DISPATCH_LEVEL以下(不包括),才會(huì)被立即搶占,否則,推遲搶占(即把那個(gè)高優(yōu)先級(jí)的就緒線程暫時(shí)記錄到當(dāng)前cpu的KPCR結(jié)構(gòu)中的NextThread字段中,標(biāo)記要將搶占)。
切換時(shí)機(jī):一句話【時(shí)片、搶占、等、主動(dòng)】
1、?時(shí)間片耗盡
2、?被搶占
3、?因等待事件、資源、信號(hào)時(shí)主動(dòng)放棄cpu(如調(diào)用WaitForSingleObject)
4、?主動(dòng)切換(如主動(dòng)調(diào)用SwitchToThread這個(gè)Win32?API)
但是:即使到了切換時(shí)機(jī)了,也只有當(dāng)線程的irql在DISPATCH_LEVEL以下(不包括)時(shí),才可以被切換出去,否則,線程將繼續(xù)占有cpu,一直等到irql降到DISPATCH_LEVEL以下。
線程的狀態(tài)(不含掛起態(tài),其實(shí)掛起態(tài)本質(zhì)上也是一種等待態(tài))
1、Ready就緒態(tài)(掛入相應(yīng)的就緒隊(duì)列)
2、某一時(shí)刻得到調(diào)度變成Running運(yùn)行態(tài)
3、因等待某一事件、信號(hào)、資源等變成Waiting等待狀態(tài)
4、Standby狀態(tài)。指處于搶占者狀態(tài)(NextThread就是自己)
5、DeferredReady狀態(tài)。指‘將’進(jìn)入就緒態(tài)。
先看一下主動(dòng)放棄cpu,切換線程的函數(shù)
NTSTATUS?NtYieldExecution()
{
????NTSTATUS?Status?=?STATUS_NO_YIELD_PERFORMED;
????KIRQL?OldIrql;
????PKPRCB?Prcb?=?KeGetCurrentPrcb();//當(dāng)前cpu的控制塊
????PKTHREAD?Thread?=?KeGetCurrentThread(),?NextThread;
if?(Prcb->ReadySummary==0)
?return?Status;//如果沒(méi)有其他線程處于就緒態(tài),就不用切換了
//重要。線程的調(diào)度過(guò)程與切換過(guò)程,本身就運(yùn)行在SynchLevel,目的是防止在執(zhí)行調(diào)度、切換工作的過(guò)程中又被切換了出去。因此,可以說(shuō),調(diào)度、切換這個(gè)過(guò)程是原子的。
????OldIrql?=?KeRaiseIrqlToSynchLevel();//先提到SynchLevel,再做調(diào)度、切換工作
????if?(Prcb->ReadySummary!=0)//如果當(dāng)前cpu上有就緒線程
????{
????????KiAcquireThreadLock(Thread);
????????KiAcquirePrcbLock(Prcb);
????????if?(Prcb->NextThread?!=?NULL)
NextThread?=?Prcb->NextThread;//優(yōu)先選擇那個(gè)等待搶占的線程
????????Else?//如果當(dāng)前沒(méi)有候選搶占線程,就從就緒隊(duì)列調(diào)度出一個(gè)線程
?NextThread?=?KiSelectReadyThread(1,?Prcb);????????
????????if?(NextThread)
????????{
????????????Thread->Quantum?=?Thread->QuantumReset;//設(shè)置下次調(diào)度運(yùn)行的時(shí)間片
????????????Thread->Priority?=?KiComputeNewPriority(Thread,?1);//略微降低一個(gè)優(yōu)先級(jí)
????????????KiReleaseThreadLock(Thread);
????????????KiSetThreadSwapBusy(Thread);//標(biāo)記本線程正在被切換
Prcb->CurrentThread?=?NextThread;//標(biāo)記已切換到下一個(gè)線程
????????????Prcb->NextThread?=?NULL;//初始運(yùn)行時(shí)尚未有任何搶占者線程
????????????NextThread->State?=?Running;//標(biāo)記線程狀態(tài)正在運(yùn)行
????????????Thread->WaitReason?=?WrYieldExecution;//標(biāo)記本線程上次被切換的原因是主動(dòng)放棄
????????????KxQueueReadyThread(Thread,?Prcb);//將本線程轉(zhuǎn)入就緒隊(duì)列
????????????Thread->WaitIrql?=?APC_LEVEL;//這將導(dǎo)致下次切換回來(lái)時(shí)會(huì)自動(dòng)發(fā)出apc中斷
????????????MiSyncForContextSwitch(NextThread);
????????????KiSwapContext(Thread,?NextThread);//真正切換到目標(biāo)線程
????????????---------------------------華麗的分割線---------------------------------------
????????????Status?=?STATUS_SUCCESS;//本線程下次切回來(lái)時(shí)繼續(xù)從這里執(zhí)行下去
????????}
????????else
????????{
????????????KiReleasePrcbLock(Prcb);
????????????KiReleaseThreadLock(Thread);
????????}
????}
????KeLowerIrql(OldIrql);//完成調(diào)度、切換過(guò)程后,降低到原irql(這個(gè)過(guò)程可能會(huì)執(zhí)行apc)
????return?Status;
}
//下面就是調(diào)度策略:按優(yōu)先級(jí)從高到低的順序掃描32條就緒隊(duì)列,取下最高優(yōu)先級(jí)的線程
PKTHREAD
KiSelectReadyThread(IN?KPRIORITY?Priority,//指調(diào)度出的線程必須>=這個(gè)優(yōu)先級(jí)
????????????????????IN?PKPRCB?Prcb)//指定cpu
{
????ULONG?PrioritySet;
????LONG?HighPriority;//含有就緒線程的最高優(yōu)先級(jí)隊(duì)列
????PLIST_ENTRY?ListEntry;
????PKTHREAD?Thread?=?NULL;//調(diào)度出來(lái)的線程
????PrioritySet?=?Prcb->ReadySummary?>>?Priority;
????if?(!PrioritySet)?goto?Quickie;
????BitScanReverse((PULONG)&HighPriority,?PrioritySet);//從高位到地位掃描那個(gè)標(biāo)志位圖
????HighPriority?+=?Priority;
????ASSERT(IsListEmpty(&Prcb->DispatcherReadyListHead[HighPriority])?==?FALSE);
????ListEntry?=?Prcb->DispatcherReadyListHead[HighPriority].Flink;//隊(duì)列中的第一個(gè)線程
????Thread?=?CONTAINING_RECORD(ListEntry,?KTHREAD,?WaitListEntry);
????ASSERT(HighPriority?==?Thread->Priority);//確保優(yōu)先級(jí)符合
????ASSERT(Thread->Affinity?&?AFFINITY_MASK(Prcb->Number));//確保cpu親緣性
????ASSERT(Thread->NextProcessor?==?Prcb->Number);//確保是在那個(gè)cpu中等待調(diào)度
????if?(RemoveEntryList(&Thread->WaitListEntry))//取下來(lái)
????????Prcb->ReadySummary?^=?PRIORITY_MASK(HighPriority);//如果隊(duì)列變空了,修改對(duì)應(yīng)的標(biāo)志位
Quickie:
????return?Thread;
}
每當(dāng)一個(gè)非實(shí)時(shí)線程被切換出去,放棄cpu后,系統(tǒng)都會(huì)略微降低該線程的優(yōu)先級(jí),以免該線程總是占住cpu不放。下面的函數(shù)就是做這個(gè)目的。
SCHAR??KiComputeNewPriority(IN?PKTHREAD?Thread,//非實(shí)時(shí)線程
????????????????????????????IN?SCHAR?Adjustment)//‘調(diào)減量’
{
????SCHAR?Priority;
????Priority?=?Thread->Priority;//原優(yōu)先級(jí)
????if?(Priority?<?LOW_REALTIME_PRIORITY)//只對(duì)非實(shí)時(shí)性線程做調(diào)整
{
//先減去‘恢減量’(對(duì)應(yīng)于喚醒線程時(shí)系統(tǒng)臨時(shí)提高的優(yōu)先級(jí)量,現(xiàn)在要把它恢復(fù)回去)
????????Priority?-=?Thread->PriorityDecrement;?
????????//再減去‘調(diào)減量’,這才是真正的調(diào)整,上面只是恢復(fù)優(yōu)先級(jí)
????????Priority?-=?Adjustment;
????????if?(Priority?<?Thread->BasePriority)
?Priority?=?Thread->BasePriority;//優(yōu)先級(jí)不管怎么調(diào),不能低于基本優(yōu)先級(jí)
????????Thread->PriorityDecrement?=?0;
????}
????return?Priority;
}
下面的函數(shù)用來(lái)將現(xiàn)場(chǎng)加入指定cpu的相應(yīng)優(yōu)先級(jí)的就緒隊(duì)列
VOID??KxQueueReadyThread(IN?PKTHREAD?Thread,IN?PKPRCB?Prcb)
{
????BOOLEAN?Preempted;
????KPRIORITY?Priority;
????ASSERT(Prcb?==?KeGetCurrentPrcb());
????ASSERT(Thread->State?==?Running);
????ASSERT(Thread->NextProcessor?==?Prcb->Number);
????{
????????Thread->State?=?Ready;//有運(yùn)行態(tài)改為就緒態(tài)
????????Priority?=?Thread->Priority;
????????Preempted?=?Thread->Preempted;//表示是否是因?yàn)楸粨屨荚蚨尦龅腸pu
????????Thread->Preempted?=?FALSE;
????????Thread->WaitTime?=?KeTickCount.LowPart;//記錄上次被切換的時(shí)間
?????
???????//若是被搶占原因讓出的cpu,就把那個(gè)線程加入隊(duì)列的開頭,以平衡它的怒氣,否則加入尾部
???Preempted???InsertHeadList(&Prcb->DispatcherReadyListHead[Priority],
???????????????????????????????????&Thread->WaitListEntry)?:
????????????????????InsertTailList(&Prcb->DispatcherReadyListHead[Priority],
???????????????????????????????????&Thread->WaitListEntry);
????????Prcb->ReadySummary?|=?PRIORITY_MASK(Priority);//標(biāo)志相應(yīng)的就緒隊(duì)列不空
????????KiReleasePrcbLock(Prcb);
????}
}
前面說(shuō)的主動(dòng)切換。但主動(dòng)切換是非常少見的,一般都是不情愿的,被動(dòng)切換。典型的被動(dòng)切換情形是:
每觸發(fā)一次時(shí)鐘中斷(通常每10毫秒觸發(fā)一次),就會(huì)在時(shí)鐘中斷的isr中遞減當(dāng)前線程KTHREAD結(jié)構(gòu)中的Quantum字段(表示剩余時(shí)間片),當(dāng)減到0時(shí)(也即時(shí)間片耗盡時(shí)),會(huì)將KPCRB結(jié)構(gòu)中的QuantumEnd字段標(biāo)記為TRUE。同時(shí),當(dāng)cpu在每次掃描執(zhí)行完DPC隊(duì)列中的函數(shù)后,irql將降到DISPATCH_LEVEL以下,這時(shí)系統(tǒng)會(huì)檢查QuantumEnd字段,若發(fā)現(xiàn)時(shí)間片已經(jīng)用完(可能已經(jīng)用完很久了),就會(huì)調(diào)用下面的函數(shù)切換線程,這時(shí)切換線程的一種典型時(shí)機(jī)。
VOID?KiQuantumEnd()?//每次時(shí)間片自然到期后執(zhí)行這個(gè)函數(shù)
{
????PKPRCB?Prcb?=?KeGetCurrentPrcb();
????PKTHREAD?NextThread,?Thread?=?Prcb->CurrentThread;//當(dāng)前線程
????if?(InterlockedExchange(&Prcb->DpcSetEventRequest,?0))//檢查是否有‘觸發(fā)DPC事件’的請(qǐng)求
????????KeSetEvent(&Prcb->DpcEvent,?0,?0);
????KeRaiseIrqlToSynchLevel();//提升到SynchLevel,準(zhǔn)備調(diào)度、切換
????KiAcquireThreadLock(Thread);
????KiAcquirePrcbLock(Prcb);
????if?(Thread->Quantum?<=?0)//確認(rèn)該線程的時(shí)間片已到期
????{
????????if?((Thread->Priority?>=?LOW_REALTIME_PRIORITY)?&&
????????????????????????????????(Thread->ApcState.Process->DisableQuantum))
????????{
????????????Thread->Quantum?=?MAX_QUANTUM;//實(shí)時(shí)線程可以禁用時(shí)間片機(jī)制
????????}
????????else
????????{
????????????Thread->Quantum?=?Thread->QuantumReset;//設(shè)置下次調(diào)度時(shí)的時(shí)間片
????????????Thread->Priority?=?KiComputeNewPriority(Thread,1);//降低一個(gè)優(yōu)先級(jí)(以免占住cpu)
????????????if?(Prcb->NextThread?!=?NULL)
????????????{
????????????????NextThread?=?Prcb->NextThread//直接使用這個(gè)候選的線程
????????????????Thread->Preempted?=?FALSE;//因?yàn)槭菚r(shí)間片到期發(fā)生的切換,所以不是被搶占
????????????}
????????????else
????????????{?
??NextThread?=?KiSelectReadyThread(Thread->Priority,?Prcb);//調(diào)度出一個(gè)線程
//表示這個(gè)線程已被選中處于候選搶占狀態(tài),將立馬上架投入運(yùn)行
????????????????NextThread->State?=?Standby;?
????????????}
????????}
????}
????KiReleaseThreadLock(Thread);
KiSetThreadSwapBusy(Thread);//標(biāo)記當(dāng)前線程正在被切換
????Prcb->CurrentThread?=?NextThread;//標(biāo)記為切換到下一個(gè)線程了
????Prcb->NextThread?=?NULL;//初始運(yùn)行時(shí)沒(méi)有搶占者線程
????NextThread->State?=?Running;//已在運(yùn)行了
????Thread->WaitReason?=?WrQuantumEnd;//標(biāo)記上次被切換的原因是時(shí)間片到期
????KxQueueReadyThread(Thread,?Prcb);//當(dāng)前線程轉(zhuǎn)入就緒隊(duì)列
????Thread->WaitIrql?=?APC_LEVEL;//?這將導(dǎo)致下次切換回來(lái)時(shí)會(huì)自動(dòng)發(fā)出apc中斷
KiSwapContext(Thread,?NextThread);//正式切換到新線程
---------------------------華麗的分割線---------------------------------------
????KeLowerIrql(DISPATCH_LEVEL);
}
除了時(shí)間片自然到期,線程被切換外,線程還可以在運(yùn)行的過(guò)程中被其他高優(yōu)先級(jí)線程,強(qiáng)制搶占而切換。
如一個(gè)線程調(diào)用ResumeThread將別的線程恢復(fù)調(diào)度時(shí),自己會(huì)檢查那個(gè)剛被恢復(fù)成就緒態(tài)的線程是否因優(yōu)先級(jí)高于自己而要搶占本線程,如果是,就會(huì)切換到那個(gè)線程。因此這個(gè)api內(nèi)部有切換線程的可能
ULONG??KeResumeThread(IN?PKTHREAD?Thread)?//恢復(fù)指定目標(biāo)線程
{
????KLOCK_QUEUE_HANDLE?ApcLock;
????ULONG?PreviousCount;
????ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);//當(dāng)前irql一定<=DISPATCH_LEVEL
????KiAcquireApcLock(Thread,?&ApcLock);//鎖定apc隊(duì)列,同時(shí)提升irql到DISPATCH_LEVEL
????PreviousCount?=?Thread->SuspendCount;
????if?(PreviousCount)
????{
????????Thread->SuspendCount--;//遞減掛起計(jì)數(shù)
//若掛起計(jì)數(shù)減到0,喚醒目標(biāo)線程,進(jìn)入就緒隊(duì)列或者變成搶占者線程
????????if?((!Thread->SuspendCount)?&&?(!Thread->FreezeCount))?
????????{
????????????KiAcquireDispatcherLockAtDpcLevel();
????????????Thread->SuspendSemaphore.Header.SignalState++;
????????????KiWaitTest(&Thread->SuspendSemaphore.Header,?IO_NO_INCREMENT);//嘗試喚醒它
????????????KiReleaseDispatcherLockFromDpcLevel();
????????}
????}
KiReleaseApcLockFromDpcLevel(&ApcLock);//注意這個(gè)函數(shù)只釋放apc隊(duì)列鎖,不降低irql
//關(guān)鍵函數(shù)。降低當(dāng)前線程的irql,同時(shí)先檢查是否有搶占者線程,若有,先執(zhí)行搶占切換。
????KiExitDispatcher(ApcLock.OldIrql);?
????return?PreviousCount;//返回之前的掛起計(jì)數(shù)
}
//下面這個(gè)函數(shù)的主功能是降回當(dāng)前線程的irql到指定OldIrql。不過(guò)在正式的降低前,會(huì)先檢查是否發(fā)生了搶占,若有,就先執(zhí)行線程切換,等下次切換回來(lái)后再降低當(dāng)前線程的irql。
//這個(gè)函數(shù)經(jīng)常在系統(tǒng)中的其它線程的運(yùn)行狀態(tài)一改變后,就主動(dòng)調(diào)用。其目的是檢測(cè)是否為此而發(fā)生了可能的搶占現(xiàn)象,若已發(fā)生,就立即進(jìn)行搶占式切換。比如,改變了某其它線程的優(yōu)先級(jí),喚醒了某其他的線程,掛起恢復(fù)了某其他線程,給某線程掛入了一個(gè)APC等等操作后,都會(huì)調(diào)用,以嘗試立即切換。
VOID??FASTCALL?//注意,這個(gè)函數(shù)只能在DISPATCH_LEVEL及其以上irql級(jí)別調(diào)用
KiExitDispatcher(IN?KIRQL?OldIrql)?//降低irql,檢測(cè)是否有搶占
{
????PKPRCB?Prcb?=?KeGetCurrentPrcb();
????PKTHREAD?Thread,?NextThread;
????BOOLEAN?PendingApc;
????ASSERT(KeGetCurrentIrql()?>=?DISPATCH_LEVEL);?//確保
????KiCheckDeferredReadyList(Prcb);
????if?(OldIrql?>=?DISPATCH_LEVEL)//如果要降回的irql不在DISPATCH_LEVEL以下,那就不能切換
????{
????????if?((Prcb->NextThread)?&&?!(Prcb->DpcRoutineActive))
????????????HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
????????goto?Quickie;
????}
if?(!Prcb->NextThread)//如果沒(méi)有搶占者線程,那很好,直接降低irql就是
?goto?Quickie;
????//若發(fā)現(xiàn)有搶占發(fā)生,下面將執(zhí)行搶占切換
????KiAcquirePrcbLock(Prcb);
????NextThread?=?Prcb->NextThread;
????Thread?=?Prcb->CurrentThread;
KiSetThreadSwapBusy(Thread);
????Prcb->CurrentThread?=?NextThread;
????Prcb->NextThread?=?NULL;
????NextThread->State?=?Running;
????KxQueueReadyThread(Thread,?Prcb);
????Thread->WaitIrql?=?OldIrql;//可以肯定:OldIrql=APC_LEVEL或PASSIVE_LEVEL,并且:如果原irql是在AP_LEVEL的話,KiSwapContext內(nèi)部會(huì)在返回前發(fā)出apc中斷
PendingApc?=?KiSwapContext(Thread,?NextThread);
-------------------------------------華麗的分割線---------------------------------------
?//如果切回來(lái)后發(fā)現(xiàn)阻塞有內(nèi)核apc,需要手動(dòng)掃描執(zhí)行apc(可以肯定原irql不是APC_LEVEL)???
if?(PendingApc)?
{
????????ASSERT(OldIrql?==?PASSIVE_LEVEL);//可以肯定原來(lái)是PASSIVE_LEVEL級(jí)
????????KeLowerIrql(APC_LEVEL);//當(dāng)然要先降到APC級(jí)別去
????????KiDeliverApc(KernelMode,?NULL,?NULL);//切換回來(lái)后,自己手動(dòng)掃描執(zhí)行內(nèi)核apc
????}
Quickie:
????KeLowerIrql(OldIrql);//本函數(shù)真正的工作:降低到指定irql
}
//如上,上面的函數(shù)在降低irql前,先嘗試檢測(cè)是否發(fā)生了搶占式切換。若有,立即切換。
否則,降低irql。注意降低irql到DISPATCH_LEVEL下以后,也可能會(huì)因?yàn)橹皶r(shí)間片早已到期,但是在DISPATCH_LEVEL以上遲遲沒(méi)有得到切換,現(xiàn)在降到下面了就會(huì)引發(fā)線程切換(遲來(lái)的切換!)
當(dāng)一個(gè)線程被喚醒時(shí)(如isr中將某線程喚醒),往往會(huì)提高其優(yōu)先級(jí),導(dǎo)致發(fā)生搶占。一旦發(fā)現(xiàn)某個(gè)線程的優(yōu)先級(jí)高于當(dāng)前線程的優(yōu)先級(jí)(并且也高于上一個(gè)候選的搶占者線程的優(yōu)先級(jí)),系統(tǒng)就會(huì)把這個(gè)線程作為新的候選搶占者線程記錄到KPCRB結(jié)構(gòu)的NextThread字段中。這樣,只要時(shí)機(jī)一成熟嗎,就會(huì)發(fā)生搶占式切換。
下面的函數(shù)用來(lái)喚醒一個(gè)線程
VOID??FASTCALL
KiUnwaitThread(IN?PKTHREAD?Thread,
???????????????IN?LONG_PTR?WaitStatus,
???????????????IN?KPRIORITY?Increment)//略微提高的優(yōu)先級(jí)量(以便目標(biāo)線程盡快得到調(diào)度)
{
????KiUnlinkThread(Thread,?WaitStatus);//從所有等待對(duì)象的線程鏈表中脫鏈
????Thread->AdjustIncrement?=?(SCHAR)Increment;//要調(diào)整的優(yōu)先級(jí)量
????Thread->AdjustReason?=?AdjustUnwait;//跳轉(zhuǎn)原因?yàn)閱拘?/p>
????KiReadyThread(Thread);//關(guān)鍵函數(shù)。將線程轉(zhuǎn)為就緒態(tài)
}
下面的函數(shù)用來(lái)將一個(gè)線程轉(zhuǎn)為就緒態(tài)
VOID??KiReadyThread(IN?PKTHREAD?Thread)
{
????IN?PKPROCESS?Process?=?Thread->ApcState.Process;
????if?(Process->State?!=?ProcessInMemory)
????????ASSERT(FALSE);//藍(lán)屏
????else?if?(!Thread->KernelStackResident)//如果該線程的內(nèi)核棧被置換到外存了
????{
????????ASSERT(Process->StackCount?!=?MAXULONG_PTR);
????????Process->StackCount++;
????????ASSERT(Thread->State?!=?Transition);
????????Thread->State?=?Transition;
????????ASSERT(FALSE);//藍(lán)屏
????}
????else
????????KiInsertDeferredReadyList(Thread);//實(shí)質(zhì)函數(shù)
}
VOID?KiInsertDeferredReadyList(IN?PKTHREAD?Thread)
{
????Thread->State?=?DeferredReady;//將進(jìn)入就緒態(tài)
????Thread->DeferredProcessor?=?0;//0號(hào)cpu
????KiDeferredReadyThread(Thread);//實(shí)質(zhì)函數(shù),就緒化指定線程
}
//下面的函數(shù)將指定線程轉(zhuǎn)換為‘就緒態(tài)’或者‘搶占態(tài)’
//也可理解為‘就緒化’某個(gè)線程,但特殊處理?yè)屨记樾?#xff08;搶占態(tài)是一種特殊的就緒態(tài))
VOID?FASTCALL??KiDeferredReadyThread(IN?PKTHREAD?Thread)
{
PKPRCB?Prcb;
BOOLEAN?Preempted;
ULONG?Processor?=?0;//一律掛入0號(hào)cpu的就緒隊(duì)列
KPRIORITY?OldPriority;//目標(biāo)線程的當(dāng)前優(yōu)先級(jí)
????PKTHREAD?NextThread;
????if?(Thread->AdjustReason?==?AdjustBoost)?//if是線程首次啟動(dòng)時(shí)的調(diào)整優(yōu)先級(jí)?。。。
????else?if?(Thread->AdjustReason?==?AdjustUnwait)?//if是喚醒時(shí)調(diào)整的優(yōu)先級(jí)?。。。
????Preempted?=?Thread->Preempted;
????OldPriority?=?Thread->Priority;
????Thread->Preempted?=?FALSE;
????Thread->NextProcessor?=?0;
????Prcb?=?KiProcessorBlock[0];
????KiAcquirePrcbLock(Prcb);
????if?(KiIdleSummary)//如果0號(hào)cpu運(yùn)行著空轉(zhuǎn)線程,目標(biāo)線程的優(yōu)先級(jí)肯定高于那個(gè)空轉(zhuǎn)線程
????{
????????KiIdleSummary?=?0;
????????Thread->State?=?Standby;//將目標(biāo)程序改為‘搶占態(tài)’
????????Prcb->NextThread?=?Thread;//指向自己
????????KiReleasePrcbLock(Prcb);
????????return;
????}
????Thread->NextProcessor?=?(UCHAR)Processor;//0
????NextThread?=?Prcb->NextThread;//獲得0號(hào)cpu上的原搶占者線程
????if?(NextThread)//如果原來(lái)已有一個(gè)搶占者線程
????{
????????ASSERT(NextThread->State?==?Standby);//可以確定那個(gè)線程處于搶占態(tài)
????????if?(OldPriority?>?NextThread->Priority)//若高于原‘搶占者線程’的優(yōu)先級(jí)?
????????{
????????????NextThread->Preempted?=?TRUE;//標(biāo)志那個(gè)搶占者線程又被目標(biāo)線程搶占了
????????????Prcb->NextThread?=?Thread;//更改新的搶占者線程,時(shí)機(jī)一成熟就搶占
????????????Thread->State?=?Standby;//更為搶占態(tài)
????????????NextThread->State?=?DeferredReady;//原搶占者線程進(jìn)入將就緒態(tài)
????????????NextThread->DeferredProcessor?=?Prcb->Number;//0
????????????KiReleasePrcbLock(Prcb);
????????????KiDeferredReadyThread(NextThread);//原搶占者線程轉(zhuǎn)入0號(hào)cpu就緒隊(duì)列
????????????return;
????????}
????}
????else//如果原來(lái)沒(méi)有搶占者線程(最典型的情況)
????{
????????NextThread?=?Prcb->CurrentThread;
????????if?(OldPriority?>?NextThread->Priority)//如果優(yōu)先級(jí)高于當(dāng)前運(yùn)行的那個(gè)線程
????????{
????????????if?(NextThread->State?==?Running)
?NextThread->Preempted?=?TRUE;//標(biāo)記已被搶占
????????????Prcb->NextThread?=?Thread;?//指定搶占者線程,時(shí)機(jī)一成熟就搶占
????????????Thread->State?=?Standby;//標(biāo)記目標(biāo)線程處于搶占態(tài)了
????????????KiReleasePrcbLock(Prcb);
????????????if?(KeGetCurrentProcessorNumber()?!=?0)
???????????????KiIpiSend(AFFINITY_MASK(Thread->NextProcessor),?IPI_DPC);//給0號(hào)cpu發(fā)一個(gè)通知
????????????return;
????????}
}
//如果目標(biāo)線程的優(yōu)先級(jí)低于當(dāng)前的搶占者線程,也低于當(dāng)前運(yùn)行中的線程
????Thread->State?=?Ready;//更為就緒態(tài)
????Thread->WaitTime?=?KeTickCount.LowPart;//記錄上次被切換的時(shí)間
????//如果目標(biāo)線程上次是因?yàn)楸粨屨级谐龅腸pu,現(xiàn)在就掛入隊(duì)頭(平衡怒氣)
????Preempted???InsertHeadList(&Prcb->DispatcherReadyListHead[OldPriority],
???????????????????????????????&Thread->WaitListEntry)?:
????????????????InsertTailList(&Prcb->DispatcherReadyListHead[OldPriority],
???????????????????????????????&Thread->WaitListEntry);
????Prcb->ReadySummary?|=?PRIORITY_MASK(OldPriority);//更改相應(yīng)就緒隊(duì)列的標(biāo)志
????KiReleasePrcbLock(Prcb);
}
如上,上面這個(gè)函數(shù)用于將線程掛入0號(hào)cpu的就緒隊(duì)列或者置為搶占者線程。
四、進(jìn)程、線程的優(yōu)先級(jí)
線程的調(diào)度策略是嚴(yán)格按優(yōu)先級(jí)的,因此,優(yōu)先級(jí),不妨叫做‘調(diào)度優(yōu)先級(jí)’。那么優(yōu)先級(jí)是啥,是怎么確定的呢?
先要弄清幾個(gè)概念:
進(jìn)程的優(yōu)先級(jí)類:每種優(yōu)先級(jí)類對(duì)應(yīng)一種基本優(yōu)先級(jí)
進(jìn)程的基本優(yōu)先級(jí):為各個(gè)線程的默認(rèn)基本優(yōu)先級(jí)
線程的基本優(yōu)先級(jí):每個(gè)線程剛創(chuàng)建時(shí)的基本優(yōu)先級(jí)繼承它所屬進(jìn)程的基本優(yōu)先級(jí),但可以人為調(diào)整
線程的當(dāng)前優(yōu)先級(jí):又叫時(shí)機(jī)優(yōu)先級(jí)。當(dāng)前優(yōu)先級(jí)可以浮動(dòng),但永遠(yuǎn)不會(huì)降到該線程的基本優(yōu)先級(jí)下面
系統(tǒng)調(diào)度線程時(shí),是以線程的當(dāng)前優(yōu)先級(jí)為準(zhǔn)的,它才不管你的基本優(yōu)先級(jí)是什么,你所屬的進(jìn)程的基本優(yōu)先級(jí)又是什么,它只看你的當(dāng)前優(yōu)先級(jí)。
進(jìn)程基本優(yōu)先級(jí)與線程基本優(yōu)先級(jí)是一種水漲船高的關(guān)系。進(jìn)程的基本優(yōu)先級(jí)變高了,那么它里面的各個(gè)線程的基本優(yōu)先級(jí)也會(huì)跟著升高對(duì)應(yīng)的幅度。各個(gè)線程初始創(chuàng)建時(shí)的基本優(yōu)先級(jí)等于其進(jìn)程的基本優(yōu)先級(jí)
線程的基本優(yōu)先級(jí)與線程的當(dāng)前優(yōu)先級(jí)也是一種水漲船高的關(guān)系。線程的基本優(yōu)先級(jí)升高了,那么線程的當(dāng)前優(yōu)先級(jí)也會(huì)跟著升高對(duì)應(yīng)的幅度。另外:線程的當(dāng)前優(yōu)先級(jí)可以隨時(shí)變化(比如每次一讓出cpu時(shí)就略微降低那么一點(diǎn)點(diǎn)優(yōu)先級(jí)),但是永遠(yuǎn)不會(huì)降到其基本優(yōu)先級(jí)以下。基本優(yōu)先級(jí)就是它的最低保障!
綜上,可理解為:線程基本優(yōu)先級(jí)相對(duì)于進(jìn)程的基本優(yōu)先級(jí),線程的當(dāng)前優(yōu)先級(jí)相對(duì)于線程的基本優(yōu)先級(jí)
線程1的當(dāng)前優(yōu)先級(jí)???????????????????線程2的當(dāng)前優(yōu)先級(jí)??????????????????線程3的當(dāng)前優(yōu)先級(jí)
線程1的基本優(yōu)先級(jí)???????????????????線程2的基本優(yōu)先級(jí)??????????????????線程3的基本優(yōu)先級(jí)
????????????????????????????????????進(jìn)程的基本優(yōu)先級(jí)
------------------------------------------------------------------------------------------
系統(tǒng)中總共分32個(gè)優(yōu)先級(jí):0到31,其中又分為兩段。0到15的是非實(shí)時(shí)優(yōu)先級(jí),16-31的表示實(shí)時(shí)優(yōu)先級(jí)。
#define?LOW_PRIORITY?0
#define?LOW_RELATIVE_PRIORITY??15?//最低的實(shí)時(shí)優(yōu)先級(jí)
#define?HIGH_PRIORITY?31//最高的實(shí)時(shí)優(yōu)先級(jí),也是整個(gè)系統(tǒng)最高的優(yōu)先級(jí)
SetPriorityClass這個(gè)Win32?API改變的就是一個(gè)進(jìn)程的優(yōu)先級(jí)類,而一種優(yōu)先級(jí)類對(duì)應(yīng)一種基本優(yōu)先級(jí),所以這個(gè)函數(shù)實(shí)際上改變的是進(jìn)程的基本優(yōu)先級(jí)。實(shí)際上最終調(diào)用到下面的函數(shù)
KPRIORITY
KeSetPriorityAndQuantumProcess(IN?PKPROCESS?Process,
???????????????????????????????IN?KPRIORITY?Priority,//新的基本優(yōu)先級(jí)
???????????????????????????????IN?UCHAR?Quantum?OPTIONAL)//新的時(shí)間片
{
????KLOCK_QUEUE_HANDLE?ProcessLock;
????KPRIORITY?Delta;
????PLIST_ENTRY?NextEntry,?ListHead;
????KPRIORITY?NewPriority,?OldPriority;
????PKTHREAD?Thread;
????ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
????if?(Process->BasePriority?==?Priority)?return?Process->BasePriority;
????if?(Priority==0)?Priority?=?1;//只有空轉(zhuǎn)線程的優(yōu)先級(jí)才能是0
????KiAcquireProcessLock(Process,?&ProcessLock);//獲得自旋鎖,同時(shí)提升irql到DISPATCH_LEVEL
if?(Quantum)
?Process->QuantumReset?=?Quantum;//修改進(jìn)程的時(shí)間片(也即里面各個(gè)線程的時(shí)間片)
????OldPriority?=?Process->BasePriority;
????Process->BasePriority?=?(SCHAR)Priority;//修改為新的基本優(yōu)先級(jí)
????Delta?=?Priority?-?OldPriority;//計(jì)算提升幅度(注意Delta可以是負(fù)數(shù))
????ListHead?=?&Process->ThreadListHead;
????NextEntry?=?ListHead->Flink;
????if?(Priority?>=?LOW_REALTIME_PRIORITY)//如果將基本優(yōu)先級(jí)提到了實(shí)時(shí)級(jí)別
????{
????????while?(NextEntry?!=?ListHead)//遍歷該進(jìn)程中的每個(gè)線程
????????{
????????????Thread?=?CONTAINING_RECORD(NextEntry,?KTHREAD,?ThreadListEntry);
????????????if?(Quantum)?Thread->QuantumReset?=?Quantum;//同時(shí)設(shè)置線程的時(shí)間片
????????????KiAcquireThreadLock(Thread);
????????????NewPriority?=?Thread->BasePriority?+?Delta;//水漲船高
????????????if?(NewPriority?<?LOW_REALTIME_PRIORITY)
????????????????NewPriority?=?LOW_REALTIME_PRIORITY;//?實(shí)時(shí)優(yōu)先級(jí)的最小值
????????????else?if?(NewPriority?>?HIGH_PRIORITY)
??
????
????????????????NewPriority?=?HIGH_PRIORITY;//?實(shí)時(shí)優(yōu)先級(jí)的最大值
????????????if?(!(Thread->Saturation)?||?(OldPriority?<?LOW_REALTIME_PRIORITY))
????????????{
????????????????Thread->BasePriority?=?(SCHAR)NewPriority;?//水漲船高
????????????????Thread->Quantum?=?Thread->QuantumReset;//當(dāng)前剩余時(shí)間片=初始時(shí)間片
????????????????Thread->PriorityDecrement?=?0;
????????????????KiSetPriorityThread(Thread,?NewPriority);//提高線程優(yōu)先級(jí)要做的附加工作
????????????}
????????????KiReleaseThreadLock(Thread);
????????????NextEntry?=?NextEntry->Flink;//下一個(gè)線程
????????}
????}
????else//如果將基本優(yōu)先級(jí)提到了非實(shí)時(shí)級(jí)別
????{
????????while?(NextEntry?!=?ListHead)
????????{
????????????Thread?=?CONTAINING_RECORD(NextEntry,?KTHREAD,?ThreadListEntry);
????????????if?(Quantum)?Thread->QuantumReset?=?Quantum;
????????????KiAcquireThreadLock(Thread);
????????????NewPriority?=?Thread->BasePriority?+?Delta;
????????????if?(NewPriority?>=?LOW_REALTIME_PRIORITY)
????????????????NewPriority?=?LOW_REALTIME_PRIORITY?-?1;//非實(shí)時(shí)優(yōu)先級(jí)的最大值
????????????else?if?(NewPriority?<=?LOW_PRIORITY)
????????????????NewPriority?=?1;//非實(shí)時(shí)優(yōu)先級(jí)的最小值
????????????if?(!(Thread->Saturation)?||?(OldPriority?>=?LOW_REALTIME_PRIORITY))
????????????{
????????????????Thread->BasePriority?=?(SCHAR)NewPriority;//水漲船高
????????????????Thread->Quantum?=?Thread->QuantumReset;//當(dāng)前剩余時(shí)間片=初始的時(shí)間片
????????????????Thread->PriorityDecrement?=?0;
????????????????KiSetPriorityThread(Thread,?NewPriority);?//提高線程優(yōu)先級(jí)要做的附加工作
????????????}
????????????KiReleaseThreadLock(Thread);
????????????NextEntry?=?NextEntry->Flink;//下一個(gè)線程
????????}
????}
????KiReleaseDispatcherLockFromDpcLevel();
KiReleaseProcessLockFromDpcLevel(&ProcessLock);
//降低到原irql,同時(shí)先檢查是否發(fā)生了搶占式切換(因?yàn)轱@式改變了線程的優(yōu)先級(jí),有可能讓其他線程的優(yōu)先級(jí)突然高于了當(dāng)前線程而要發(fā)生搶占現(xiàn)象,所以要檢測(cè)這種情況)
????KiExitDispatcher(ProcessLock.OldIrql);?
????return?OldPriority;
}
線程的基本優(yōu)先級(jí)一變了,它的當(dāng)前優(yōu)先級(jí)就會(huì)跟著變,線程的當(dāng)前優(yōu)先級(jí)一變了,那么就會(huì)有很多的附加工作要做,下面的函數(shù)就用來(lái)做這個(gè)工作(如改變就緒隊(duì)列、置為搶占者等)。
VOID??FASTCALL??//設(shè)置線程的當(dāng)前優(yōu)先級(jí)
KiSetPriorityThread(IN?PKTHREAD?Thread,
????????????????????IN?KPRIORITY?Priority)//新的當(dāng)前優(yōu)先級(jí)
{
????PKPRCB?Prcb;
????ULONG?Processor;
????BOOLEAN?RequestInterrupt?=?FALSE;
????KPRIORITY?OldPriority;
????PKTHREAD?NewThread;
????if?(Thread->Priority?!=?Priority)//if?優(yōu)先級(jí)變了
????{
????????for?(;;)
????????{
????????????if?(Thread->State?==?Ready)//如果目標(biāo)線程處于就緒態(tài)
????????????{
????????????????if?(!Thread->ProcessReadyQueue)//其實(shí)一般都會(huì)滿足這個(gè)條件
????????????????{
????????????????????Processor?=?Thread->NextProcessor;
????????????????????Prcb?=?KiProcessorBlock[Processor];
????????????????????KiAcquirePrcbLock(Prcb);
????????????????????//如果現(xiàn)在仍處于就緒態(tài),并且仍在那個(gè)cpu上等待
????????????????????if?((Thread->State?==?Ready)?&&?(Thread->NextProcessor?==?Prcb->Number))
????????????????????{
????????????????????????if?(RemoveEntryList(&Thread->WaitListEntry))//從原就緒隊(duì)列摘下
????????????????????????????Prcb->ReadySummary?^=?PRIORITY_MASK(Thread->Priority);
????????????????????????Thread->Priority?=?(SCHAR)Priority;//=更為新的優(yōu)先級(jí)
????????????????????????KiInsertDeferredReadyList(Thread);//掛入新的就緒隊(duì)列(或置為搶占態(tài))
????????????????????????KiReleasePrcbLock(Prcb);
????????????????????}
????????????????????Else?…
????????????????}
????????????}
????????????else?if?(Thread->State?==?Standby)?//如果目標(biāo)線程處于搶占態(tài)
????????????{
????????????????Processor?=?Thread->NextProcessor;
????????????????Prcb?=?KiProcessorBlock[Processor];
????????????????KiAcquirePrcbLock(Prcb);
????????????????if?(Thread?==?Prcb->NextThread)//如果仍處于搶占態(tài)
????????????????{
????????????????????OldPriority?=?Thread->Priority;
????????????????????Thread->Priority?=?(SCHAR)Priority;//更改優(yōu)先級(jí)
????????????????????if?(Priority?<?OldPriority)//如果優(yōu)先級(jí)降了(可能不再成為搶占者線程了)
????????????????????{
????????????????????????NewThread?=?KiSelectReadyThread(Priority?+?1,?Prcb);
????????????????????????if?(NewThread)//如果選出了一個(gè)比現(xiàn)在的優(yōu)先級(jí)更高的線程
????????????????????????{
????????????????????????????NewThread->State?=?Standby;
????????????????????????????Prcb->NextThread?=?NewThread;//更為新的搶占者線程
????????????????????????????KiInsertDeferredReadyList(Thread);//原搶占線程則轉(zhuǎn)入就緒隊(duì)列
????????????????????????}
????????????????????}
????????????????????KiReleasePrcbLock(Prcb);
????????????????}
????????????????Else?…
????????????}
????????????else?if?(Thread->State?==?Running)?//如果目標(biāo)線程正在運(yùn)行
????????????{
????????????????Processor?=?Thread->NextProcessor;
????????????????Prcb?=?KiProcessorBlock[Processor];
????????????????KiAcquirePrcbLock(Prcb);
????????????????if?(Thread?==?Prcb->CurrentThread)//如果仍在運(yùn)行
????????????????{
????????????????????OldPriority?=?Thread->Priority;
????????????????????Thread->Priority?=?(SCHAR)Priority;//更改優(yōu)先級(jí)
????????????????????if?((Priority?<?OldPriority)?&&?!(Prcb->NextThread))//可能會(huì)出現(xiàn)搶占
????????????????????{
????????????????????????NewThread?=?KiSelectReadyThread(Priority?+?1,?Prcb);
????????????????????????if?(NewThread)//?如果選出了一個(gè)比現(xiàn)在的優(yōu)先級(jí)更高的線程
????????????????????????{
????????????????????????????NewThread->State?=?Standby;
????????????????????????????Prcb->NextThread?=?NewThread;//出現(xiàn)了新的搶占線程
????????????????????????????RequestInterrupt?=?TRUE;//需要立即中斷
????????????????????????}
????????????????????}
????????????????????KiReleasePrcbLock(Prcb);
????????????????????if?(RequestInterrupt)
????????????????????{
????????????????????????//通知目標(biāo)cpu進(jìn)行搶占切換
????????????????????????if?(KeGetCurrentProcessorNumber()?!=?Processor)
????????????????????????????KiIpiSend(AFFINITY_MASK(Processor),?IPI_DPC);
????????????????????}
????????????????}
????????????????Else?…
????????????}
????????????Else?…
????????????break;
????????}
????}
}
如上,這個(gè)函數(shù)改變目標(biāo)線程的優(yōu)先級(jí)為指定優(yōu)先級(jí),并根據(jù)目標(biāo)線程的當(dāng)前所處狀態(tài),最對(duì)應(yīng)的就緒隊(duì)列、搶占者線程調(diào)整。可見,強(qiáng)行改變某個(gè)線程的當(dāng)前優(yōu)先級(jí)并不是件簡(jiǎn)單的工作,需要全盤綜合考慮各方面因素,做出相應(yīng)的調(diào)整。
下面的函數(shù)是一個(gè)小型的封裝函數(shù):(他還會(huì)還原時(shí)間片)
KPRIORITY
KeSetPriorityThread(IN?PKTHREAD?Thread,
????????????????????IN?KPRIORITY?Priority)
{
????KIRQL?OldIrql;
????KPRIORITY?OldPriority;
????OldIrql?=?KiAcquireDispatcherLock();
????KiAcquireThreadLock(Thread);
????OldPriority?=?Thread->Priority;
????Thread->PriorityDecrement?=?0;
????if?(Priority?!=?Thread->Priority)//if?優(yōu)先級(jí)變了
????{
????????Thread->Quantum?=?Thread->QuantumReset;//關(guān)鍵。還原時(shí)間片
????????KiSetPriorityThread(Thread,?Priority);//再做真正的修改工作
????}
????KiReleaseThreadLock(Thread);
????KiReleaseDispatcherLock(OldIrql);
????return?OldPriority;
}
除了修改進(jìn)程的基本優(yōu)先級(jí)會(huì)影響到里面每個(gè)線程的基本優(yōu)先級(jí)和當(dāng)前優(yōu)先級(jí)外,也可以用下面的函數(shù)直接修改線程的基本優(yōu)先級(jí)和當(dāng)前優(yōu)先級(jí)。
NTSTATUS
NtSetInformationThread(IN?HANDLE?ThreadHandle,
???????????????????????IN?THREADINFOCLASS?ThreadInformationClass,
???????????????????????IN?PVOID?ThreadInformation,
???????????????????????IN?ULONG?ThreadInformationLength)
{
????…
????switch?(ThreadInformationClass)
????{
????????case?ThreadPriority://設(shè)置當(dāng)前優(yōu)先級(jí)
????????????Priority?=?*(PLONG)ThreadInformation;//這個(gè)值是相對(duì)于進(jìn)程基本優(yōu)先級(jí)的差值
????????????KeSetPriorityThread(&Thread->Tcb,?Priority);
????????????break;
????????case?ThreadBasePriority://設(shè)置基本優(yōu)先級(jí)
????????????Priority?=?*(PLONG)ThreadInformation;
????????????KeSetBasePriorityThread(&Thread->Tcb,?Priority);
????????????break;
????????case?…
????}//end?switch
}//end?func
線程的基本優(yōu)先級(jí)(非當(dāng)前優(yōu)先級(jí))可以用下面的函數(shù)設(shè)置:
LONG
KeSetBasePriorityThread(IN?PKTHREAD?Thread,
????????????????????????IN?LONG?Increment)//這個(gè)是相對(duì)于進(jìn)程基本優(yōu)先級(jí)的差值
{
????KIRQL?OldIrql;
????KPRIORITY?OldBasePriority,?Priority,?BasePriority;
????LONG?OldIncrement;
????PKPROCESS?Process;
????ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
????Process?=?Thread->ApcState.Process;
????OldIrql?=?KiAcquireDispatcherLock();
????KiAcquireThreadLock(Thread);
????OldBasePriority?=?Thread->BasePriority;
????OldIncrement?=?OldBasePriority?-?Process->BasePriority;
if?(Thread->Saturation)?//如果是個(gè)飽和增量
?OldIncrement?=?16?*?Thread->Saturation;//16或-16
????Thread->Saturation?=?0;
????if?(abs(Increment)?>=?16)?//飽和增量
????????Thread->Saturation?=?(Increment?>?0)???1?:?-1;
????BasePriority?=?Process->BasePriority?+?Increment;//算得現(xiàn)在的基本優(yōu)先級(jí)
????if?(Process->BasePriority?>=?LOW_REALTIME_PRIORITY)
????{
????????Priority?=?BasePriority;//實(shí)時(shí)線程例外,當(dāng)前優(yōu)先級(jí)=基本優(yōu)先級(jí)
????}
????else
????{
?????????Priority?=?KiComputeNewPriority(Thread,?0);//其實(shí)就是當(dāng)前優(yōu)先級(jí)
//看到?jīng)],線程的基本優(yōu)先級(jí)一升高,它的當(dāng)前優(yōu)先級(jí)跟著升高對(duì)應(yīng)的幅度
?????????Priority?+=?(BasePriority?-?OldBasePriority);?
????}
????Thread->BasePriority?=?(SCHAR)BasePriority;//更改線程的基本優(yōu)先級(jí)
????Thread->PriorityDecrement?=?0;
????if?(Priority?!=?Thread->Priority)//如果當(dāng)前優(yōu)先級(jí)變了,做相關(guān)的附加工作
????{
????????Thread->Quantum?=?Thread->QuantumReset;
????????KiSetPriorityThread(Thread,?Priority);
????}
????KiReleaseThreadLock(Thread);
????KiReleaseDispatcherLock(OldIrql);
????return?OldIncrement;
}
五、線程局部存儲(chǔ):TLS
----對(duì)TLS這個(gè)概念陌生的朋友請(qǐng)先自己查閱相關(guān)資料。
TLS分為兩種方法:靜態(tài)tls、動(dòng)態(tài)tls。兩種方法都可以達(dá)到tls的目的。
靜態(tài)tls:
在編寫程序時(shí):只需在要聲明為tls的全局變量前加上__declspec(thread)關(guān)鍵字即可。如:
__declspec(thread)?int?g_a?=?1;
__declspec(thread)?int?g_b;
__declspec(thread)?int?g_c?=?0;
__declspec(thread)?int?g_d;
編譯器在遇到這樣的變量時(shí),自然會(huì)將這種變量當(dāng)做tls變量看待,編譯鏈接存放到pe文件的.tls節(jié)中,
Exe文件中可使用靜態(tài)tls,動(dòng)態(tài)庫(kù)文件中使用靜態(tài)tls則會(huì)有很大的缺點(diǎn),所以動(dòng)態(tài)庫(kù)文件中一般都使用動(dòng)態(tài)tls來(lái)達(dá)到tls的目的。為此,Windows專門提供了一組api和相關(guān)基礎(chǔ)設(shè)施來(lái)實(shí)現(xiàn)動(dòng)態(tài)tls。
DWORD?TlsAlloc():為當(dāng)前線程分配一個(gè)tls槽。返回本線程分得的槽號(hào)
BOOL?TlsSetValue(DWORD?idx,void*?val):寫數(shù)據(jù)到指定槽中
VOID*?TlsGetValue(DWORD?idx?):從指定槽中讀數(shù)據(jù)
BOOL?TlsFree(DWORD?idx);//釋放這個(gè)槽給進(jìn)程,使得其他線程可以分得這個(gè)槽
相關(guān)的結(jié)構(gòu):
Struct?PEB
{
???…
???RTL_BITMAP*??TlsBitmap;//標(biāo)準(zhǔn)的64位動(dòng)態(tài)tls分配標(biāo)志位圖(固定使用下面的64位結(jié)構(gòu))
???DWORD?TlsBitmapBits[2];//內(nèi)置的64bit大小的tls位圖(每一位標(biāo)志表示對(duì)應(yīng)tls槽的分配情況)
???…
}
Struct?RTL_BITMAP
{
???ULONG?SizeOfBitmap;//動(dòng)態(tài)tls位圖的大小,默認(rèn)就是8B(64bit)
???BYTE*?Buffer;//動(dòng)態(tài)tls位圖的地址,默認(rèn)就指向PEB結(jié)構(gòu)中的那個(gè)內(nèi)置的tls位圖。當(dāng)要使用的tls槽個(gè)數(shù)超過(guò)64個(gè)時(shí),將使用擴(kuò)展的tls位圖。
}
Struct?TEB
{
???…
???Void*?ThreadLocalStoragePointer;//本線程的那片靜態(tài)tls區(qū)的地址
???Void*?TlsSlots[64];//內(nèi)置的64個(gè)tls槽(每個(gè)槽中可以存放4B大小的任意數(shù)據(jù))
???Void*?TlsExpansionSlots;//另外擴(kuò)展的1024個(gè)tls槽
???…
}
下面的函數(shù)分配一個(gè)空閑的tls槽,返回分到的槽號(hào)(即索引)
DWORD?TlsAlloc()
{
????ULONG?Index;
RtlAcquirePebLock();
//先從標(biāo)準(zhǔn)的64位tls位圖中找到一個(gè)空閑的tls槽(也即未被其他線程占用的tls槽)
????Index?=?RtlFindClearBitsAndSet(NtCurrentPeb()->TlsBitmap,?1,?0);
????if?(Index?==?-1)//如果找不到
{
????//再去擴(kuò)展的tls槽位圖中查找
????????Index?=?RtlFindClearBitsAndSet(NtCurrentPeb()->TlsExpansionBitmap,?1,?0);
????????if?(Index?!=?-1)//如果找到了
????????{
????????????if?(NtCurrentTeb()->TlsExpansionSlots?==?NULL)
????????????{
????????????????NtCurrentTeb()->TlsExpansionSlots?=?HeapAlloc(GetProcessHeap(),
??????????????????????????????????????????????????HEAP_ZERO_MEMORY,1024?*?sizeof(PVOID));
????????????}
????????????NtCurrentTeb()->TlsExpansionSlots[Index]?=?0;//分到對(duì)應(yīng)的槽后,自動(dòng)將內(nèi)容清0
????????????Index?+=?64;
????????}
????????else
????????????SetLastError(ERROR_NO_MORE_ITEMS);
????}
????else
????????NtCurrentTeb()->TlsSlots[Index]?=?0;?//分到對(duì)應(yīng)的槽后,自動(dòng)將內(nèi)容清0
????RtlReleasePebLock();
????return?Index;
}
下面的函數(shù)將數(shù)據(jù)寫入指定tls槽中
BOOL?TlsSetValue(DWORD?Index,?LPVOID?Value)
{
????if?(Index?>=?64)?//擴(kuò)展tls槽中
????{
????????if?(NtCurrentTeb()->TlsExpansionSlots?==?NULL)
????????{
????????????NtCurrentTeb()->TlsExpansionSlots?=?HeapAlloc(GetProcessHeap(),
???????????????????????????????????HEAP_ZERO_MEMORY,1024?*sizeof(PVOID));
????????}
????????NtCurrentTeb()->TlsExpansionSlots[Index?-?64]?=?Value;
????}
????else
????????NtCurrentTeb()->TlsSlots[Index]?=?Value;
????return?TRUE;
}
下面的函數(shù)讀取指定tls槽中的值
LPVOID?TlsGetValue(DWORD?Index)
{
????if?(Index?>=?64)
????????return?NtCurrentTeb()->TlsExpansionSlots[Index?-?64];
????else
????????return?NtCurrentTeb()->TlsSlots[Index];
}
下面的函數(shù)用來(lái)釋放一個(gè)tls槽給進(jìn)程
BOOL?TlsFree(DWORD?Index)
{
????BOOL?BitSet;
????RtlAcquirePebLock();
????if?(Index?>=?64)
{
???//檢測(cè)該tls槽是否已分配
???????BitSet?=?RtlAreBitsSet(NtCurrentPeb()->TlsExpansionBitmap,Index?-?64,1);
???????if?(BitSet)//若已分配,現(xiàn)在標(biāo)記為空閑
???????????RtlClearBits(NtCurrentPeb()->TlsExpansionBitmap,Index?-?64,1);
????}
????else
????{
????????BitSet?=?RtlAreBitsSet(NtCurrentPeb()->TlsBitmap,?Index,?1);
????????if?(BitSet)
????????????RtlClearBits(NtCurrentPeb()->TlsBitmap,?Index,?1);
????}
????if?(BitSet)
????{
????????//將所有線程的對(duì)應(yīng)tls槽內(nèi)容清0
????????NtSetInformationThread(NtCurrentThread(),ThreadZeroTlsCell,&Index,sizeof(DWORD));
????}
????else
????????SetLastError(ERROR_INVALID_PARAMETER);
????RtlReleasePebLock();
????return?BitSet;
}
上面這些關(guān)于動(dòng)態(tài)tls的函數(shù)都不難理解。動(dòng)態(tài)tls功能強(qiáng)大,但使用起來(lái)不方便。靜態(tài)tls不好用在動(dòng)態(tài)庫(kù)中,比較局限,但靜態(tài)tls使用方便。話又說(shuō)回來(lái),靜態(tài)的tls的使用方便背后,又包含著較為復(fù)雜的初始化流程。下面看靜態(tài)tls的初始化流程。
回顧一下進(jìn)程創(chuàng)建時(shí)的啟動(dòng)流程:
在進(jìn)程啟動(dòng)時(shí),初始化主exe文件的函數(shù)內(nèi)部:
PEFUNC??LdrPEStartup(…)
{
???…
???Status?=?LdrFixupImports(NULL,?*Module);//加載子孫dll,修正IAT導(dǎo)入表
???Status?=?LdrpInitializeTlsForProccess();//初始化進(jìn)程的靜態(tài)tls
???if?(NT_SUCCESS(Status))
???{
??????LdrpAttachProcess();//發(fā)送一個(gè)ProcessAttach消息,調(diào)用該模塊的DllMain函數(shù)
??????LdrpTlsCallback(*Module,?DLL_PROCESS_ATTACH);//調(diào)用各模塊的tls回調(diào)函數(shù)?
???}
???…
}
鉆進(jìn)各個(gè)函數(shù)里面去看一下:
NTSTATUS?LdrFixupImports(…)
{
???…
???if?(TlsDirectory)
???{
???????TlsSize?=?TlsDirectory->EndAddressOfRawData-?TlsDirectory->StartAddressOfRawData
???????????????????+?TlsDirectory->SizeOfZeroFill;
???????if?(TlsSize?>?0?&&?NtCurrentPeb()->Ldr->Initialized)//if?動(dòng)態(tài)加載該模塊
???????????TlsDirectory?=?NULL;//?動(dòng)態(tài)加載的模塊不支持靜態(tài)tls
???}
???…
???if?(TlsDirectory?&&?TlsSize?>?0)//處理靜態(tài)加載的dll模塊中的靜態(tài)tls節(jié)
???????LdrpAcquireTlsSlot(Module,?TlsSize,?FALSE);
???…
}
在修正每個(gè)exe、dll文件的導(dǎo)入表時(shí),會(huì)檢查該文件中.tls節(jié)的大小。由于這個(gè)函數(shù)本身也會(huì)被LoadLibrary函數(shù)在內(nèi)部調(diào)用,所以,這個(gè)函數(shù)他會(huì)檢測(cè)是不是在動(dòng)態(tài)加載dll,若是,如果發(fā)現(xiàn)dll中含有靜態(tài)tls節(jié),就什么都不做。反之,若dll是在進(jìn)程啟動(dòng)階段靜態(tài)加載的,就會(huì)調(diào)用LdrpAcquireTlsSlot處理那個(gè)模塊中的tls節(jié)。具體是怎么處理的呢?我們看:
VOID?LdrpAcquireTlsSlot(PLDR_DATA_TABLE_ENTRY?Module,?ULONG?Size,?BOOLEAN?Locked)
{
if?(!Locked)
RtlEnterCriticalSection?(NtCurrentPeb()->LoaderLock);
Module->TlsIndex?=?LdrpTlsCount;//記錄這個(gè)模塊tls節(jié)的索引(即tls號(hào))
LdrpTlsCount++;//遞增進(jìn)程中的tls節(jié)個(gè)數(shù)
LdrpTlsSize?+=?Size;//遞增進(jìn)程中tls節(jié)總大小
if?(!Locked)
RtlLeaveCriticalSection(NtCurrentPeb()->LoaderLock);
}
如上,每個(gè)模塊在進(jìn)程啟動(dòng)時(shí)的靜態(tài)加載過(guò)程中,只是遞增一下進(jìn)程中總的tls節(jié)個(gè)數(shù)與大小,以及分配該模塊的tls節(jié)編號(hào),以便在進(jìn)程完全初始化完成(即加載了所有模塊)后,統(tǒng)一集中處理各模塊中的靜態(tài)tls節(jié)。
下面再看LdrPEStartup函數(shù)中調(diào)用的LdrpInitializeTlsForProccess函數(shù),顯然,這個(gè)函數(shù)是在LdrFixupImports函數(shù)加載了該exe依賴的所有子孫dll文件后才調(diào)用的。前面已經(jīng)統(tǒng)計(jì)完了該進(jìn)程中所有模塊的所有tls節(jié)的總大小以及tls節(jié)總個(gè)數(shù),現(xiàn)在就到調(diào)用這個(gè)函數(shù)集中統(tǒng)一處理該進(jìn)程的靜態(tài)tls時(shí)候了。我們看:
NTSTATUS?LdrpInitializeTlsForProccess()
{
PLIST_ENTRY?ModuleListHead;
PLIST_ENTRY?Entry;
PLDR_DATA_TABLE_ENTRY?Module;
PIMAGE_TLS_DIRECTORY?TlsDirectory;
PTLS_DATA?TlsData;
ULONG?Size;
if?(LdrpTlsCount?>?0)?//如果有模塊中存在tls節(jié)
{
?????????//分配一個(gè)tls描述符數(shù)組,用來(lái)記錄各模塊的tls節(jié)信息(注意分配的只是描述符,并不用來(lái)存放tls節(jié)體。另外,每個(gè)進(jìn)程的tls描述符數(shù)組都記錄在ntdll.dll模塊中的LdrpTlsArray全局變量中)
LdrpTlsArray?=?RtlAllocateHeap(RtlGetProcessHeap(),0,
LdrpTlsCount?*?sizeof(TLS_DATA));
ModuleListHead?=?&NtCurrentPeb()->Ldr->InLoadOrderModuleList;
Entry?=?ModuleListHead->Flink;
while?(Entry?!=?ModuleListHead)//遍歷所有含有tls節(jié)的靜態(tài)加載模塊
{
Module?=?CONTAINING_RECORD(Entry,?LDR_DATA_TABLE_ENTRY,?InLoadOrderLinks);
if?(Module->LoadCount?==-1?&&?Module->TlsIndex?!=?-1)
{
???????????????????//獲得pe文件中tls目錄的信息
TlsDirectory?=?RtlImageDirectoryEntryToData(Module->DllBase,
????????????????TRUE,IMAGE_DIRECTORY_ENTRY_TLS,&Size);
TlsData?=?&LdrpTlsArray[Module->TlsIndex];//指向該模塊對(duì)應(yīng)的描述符
//非0區(qū)在原模塊中的地址
TlsData->StartAddressOfRawData?=?TlsDirectory->StartAddressOfRawData;
//非0區(qū)的大小
TlsData->TlsDataSize?=?TlsDirectory->EndAddressOfRawData?-?TlsDirectory->
StartAddressOfRawData;
//0區(qū)的大小(即尚未初始化的tls變量總大小)
TlsData->TlsZeroSize?=?TlsDirectory->SizeOfZeroFill;
//tls回調(diào)函數(shù)數(shù)組的地址
if?(TlsDirectory->AddressOfCallBacks)
TlsData->TlsAddressOfCallBacks?=?TlsDirectory->AddressOfCallBacks;
else
TlsData->TlsAddressOfCallBacks?=?NULL;
TlsData->Module?=?Module;//該tls節(jié)所在的原模塊
//重要。回填到原模塊中,該tls節(jié)分得的索引。(寫復(fù)制機(jī)制可確保各進(jìn)程一份)
*(PULONG)TlsDirectory->AddressOfIndex?=?Module->TlsIndex;
}
Entry?=?Entry->Flink;
}
}
return?STATUS_SUCCESS;
}
如上,這個(gè)函數(shù)為進(jìn)程建立起一個(gè)tls描述符數(shù)組。
typedef?struct?_TLS_DATA?//tls節(jié)描述符
{
PVOID?StartAddressOfRawData;?//非0區(qū)在原模塊中的地址
DWORD?TlsDataSize;//?非0區(qū)的大小
DWORD?TlsZeroSize;//?0區(qū)大小
PIMAGE_TLS_CALLBACK?*TlsAddressOfCallBacks;//回調(diào)函數(shù)數(shù)組
PLDR_DATA_TABLE_ENTRY?Module;//所在模塊
}?TLS_DATA,?*PTLS_DATA;
非0區(qū)與0區(qū)是什么意思呢?tls節(jié)中各個(gè)變量可能有的沒(méi)有初值,凡是沒(méi)有初值的tls的變量都被安排到tls節(jié)的末尾,并且不予分配文件空間(這樣,可以節(jié)省文件體積),只記錄他們的總字節(jié)數(shù)即可。
__declspec(thread)?int?g_a?=?1;//已初始化,被安排到tls節(jié)中的非0區(qū)
__declspec(thread)?int?g_b;//被安排到0區(qū)
__declspec(thread)?int?g_c?=?0;//已初始化,被安排到tls節(jié)中的非0區(qū)
__declspec(thread)?int?g_d;?//被安排到0區(qū)
所有未予初始化的tls變量都默認(rèn)賦予初值0。
最后:每當(dāng)一個(gè)線程創(chuàng)建時(shí)的初始化工作如下:
NTSTATUS
LdrpAttachThread?(VOID)
{
?????。。。
Status?=?LdrpInitializeTlsForThread();//關(guān)鍵處。初始化每個(gè)線程的靜態(tài)tls
?????調(diào)用各dll的DllMain,略
return?Status;
}
如上,每當(dāng)一個(gè)線程初始運(yùn)行時(shí),除了會(huì)調(diào)用進(jìn)程中各個(gè)dll的DllMain函數(shù)外,還會(huì)初始化自己的靜態(tài)tls,建立起本線程獨(dú)立的一份靜態(tài)tls副本。如下:
NTSTATUS??LdrpInitializeTlsForThread(VOID)
{
PVOID*?TlsPointers;
PTLS_DATA?TlsInfo;
PVOID?TlsData;
ULONG?i;
PTEB?Teb?=?NtCurrentTeb();
Teb->StaticUnicodeString.Length?=?0;
Teb->StaticUnicodeString.MaximumLength?=?sizeof(Teb->StaticUnicodeBuffer);
Teb->StaticUnicodeString.Buffer?=?Teb->StaticUnicodeBuffer;
if?(LdrpTlsCount?>?0)//如果本進(jìn)程中有包含tls節(jié)的靜態(tài)模塊
{
?????????//將各模塊內(nèi)部的tls節(jié)提取出來(lái),連成一片,形成一塊‘tls片區(qū)’
TlsPointers?=?RtlAllocateHeap(RtlGetProcessHeap(),0,
???????LdrpTlsCount?*?sizeof(PVOID)?+?LdrpTlsSize);//頭部指針數(shù)組+所有tls塊的總大小
?????????//指向頭部后面的各tls節(jié)體部分
TlsData?=?(PVOID)((ULONG_PTR)TlsPointers?+?LdrpTlsCount?*?sizeof(PVOID));
Teb->ThreadLocalStoragePointer?=?TlsPointers;//指向本線程自己的那份tls的頭部?
TlsInfo?=?LdrpTlsArray;//指向本進(jìn)程的tls描述符數(shù)組
for?(i?=?0;?i?<?LdrpTlsCount;?i++,?TlsInfo++)
{
TlsPointers[i]?=?TlsData;//將數(shù)組指針指向?qū)?yīng)的tls塊
if?(TlsInfo->TlsDataSize)
{????
??????????????????//提取對(duì)應(yīng)模塊內(nèi)部的tls節(jié)體(非0區(qū)部分)到這兒來(lái)
memcpy(TlsData,?TlsInfo->StartAddressOfRawData,?TlsInfo->TlsDataSize);
TlsData?=?(PVOID)((ULONG_PTR)TlsData?+?TlsInfo->TlsDataSize);
}
if?(TlsInfo->TlsZeroSize)//0區(qū)部分
{
memset(TlsData,?0,?TlsInfo->TlsZeroSize);//自動(dòng)初始化為0
TlsData?=?(PVOID)((ULONG_PTR)TlsData?+?TlsInfo->TlsZeroSize);//跨過(guò)0區(qū)部分
}
}
}
return?STATUS_SUCCESS;
}
看到?jīng)],每個(gè)線程誕生之初,就將進(jìn)程中各模塊內(nèi)部的tls節(jié)提取出來(lái),復(fù)制到一個(gè)集中的地方存放,這樣,
嗎,每個(gè)線程都建立了一份自己連續(xù)的tls片區(qū)。以后,要訪問(wèn)tls變量時(shí),訪問(wèn)的都是自己的那份tls片區(qū),
當(dāng)然,如何訪問(wèn)?這離不開編譯器對(duì)靜態(tài)tls機(jī)制提供的支持。
編譯器在遇到__declspec(thread)關(guān)鍵字時(shí),會(huì)認(rèn)為那個(gè)變量是tls變量,將之編譯鏈接到pe文件的.tls節(jié)中存放,另外每條訪問(wèn)tls變量的高級(jí)語(yǔ)句都被做了恰當(dāng)?shù)木幾g。每個(gè)tls變量都被編譯為二級(jí)地址:
“Tls節(jié)號(hào).節(jié)內(nèi)偏移”,每個(gè)模塊的tls節(jié)號(hào)(即索引)保存在那個(gè)模塊的tls目錄中的某個(gè)固定字段中(詳見:?*(PULONG)TlsDirectory->AddressOfIndex?=?Module->TlsIndex?這條語(yǔ)句),這樣,編譯器從模塊的這個(gè)位置取得該模塊的tls節(jié)分得的節(jié)號(hào),以此節(jié)號(hào)為索引,根據(jù)TEB中的保存的那塊“tls片區(qū)”的頭部數(shù)組,找到對(duì)應(yīng)于本模塊tls節(jié)副本的位置,然后加上該tls變量在節(jié)內(nèi)的偏移,就正確找到對(duì)應(yīng)的內(nèi)存單元了。
六、進(jìn)程掛靠與跨進(jìn)程操作
前面總在說(shuō):“將一個(gè)線程掛靠到其他進(jìn)程的地址空間”,這是怎么回事?現(xiàn)在就來(lái)看一下。
當(dāng)父進(jìn)程要?jiǎng)?chuàng)建一個(gè)子進(jìn)程時(shí):會(huì)在父進(jìn)程中調(diào)用CreateProcess。這個(gè)函數(shù)本身是運(yùn)行在父進(jìn)程的地址空間中的,但是由它創(chuàng)建了子進(jìn)程,創(chuàng)建了子進(jìn)程的地址空間,創(chuàng)建了子進(jìn)程的PEB。當(dāng)要初始化子進(jìn)程的PEB結(jié)構(gòu)時(shí),由于PEB本身位于子進(jìn)程的地址空間中,如果直接訪問(wèn)PEB那是不對(duì)的,那將會(huì)映射到不同的物理內(nèi)存。所以必須掛靠到子進(jìn)程的地址空間中,去讀寫PEB結(jié)構(gòu)體中的值。下面的函數(shù)就是用來(lái)掛靠的
VOID?KeAttachProcess(IN?PKPROCESS?Process)?//將當(dāng)前線程掛靠到指定進(jìn)程的地址空間
{
????KLOCK_QUEUE_HANDLE?ApcLock;
????PKTHREAD?Thread?=?KeGetCurrentThread();
????if?(Thread->ApcState.Process?==?Process)?return;//如果已經(jīng)位于目標(biāo)進(jìn)程,返回
????if?((Thread->ApcStateIndex?!=?OriginalApcEnvironment)?||?(KeIsExecutingDpc()))
????????KeBugCheckEx(~);//藍(lán)屏錯(cuò)誤
????else
????{
????????KiAcquireApcLock(Thread,?&ApcLock);
????????KiAcquireDispatcherLockAtDpcLevel();//掛靠過(guò)程操作過(guò)程中禁止線程切換
????????KiAttachProcess(Thread,?Process,?&ApcLock,?&Thread->SavedApcState);//實(shí)質(zhì)函數(shù)
????}
}
VOID
KiAttachProcess(IN?PKTHREAD?Thread,//指定線程
????????????????IN?PKPROCESS?Process,//要掛靠到的目標(biāo)進(jìn)程
????????????????IN?PKLOCK_QUEUE_HANDLE?ApcLock,
????????????????IN?PRKAPC_STATE?SavedApcState)//保存原apc隊(duì)列狀態(tài)
{
????Process->StackCount++;//目標(biāo)線程的內(nèi)核棧個(gè)數(shù)遞增(也即增加線程個(gè)數(shù))
KiMoveApcState(&Thread->ApcState,?SavedApcState);//復(fù)制保存原apc隊(duì)列狀態(tài)
//每當(dāng)一掛靠,必然要清空原apc隊(duì)列
????InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]);
????InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]);
????Thread->ApcState.Process?=?Process;//關(guān)鍵。將表示當(dāng)前進(jìn)程的字段更為目標(biāo)進(jìn)程
????Thread->ApcState.KernelApcInProgress?=?FALSE;
????Thread->ApcState.KernelApcPending?=?FALSE;
????Thread->ApcState.UserApcPending?=?FALSE;
????if?(SavedApcState?==?&Thread->SavedApcState)//一般滿足
{
????//修改指向,但不管怎么修改,ApcState字段總是表示當(dāng)前apc狀態(tài)
????????Thread->ApcStatePointer[OriginalApcEnvironment]?=?&Thread->SavedApcState;
????????Thread->ApcStatePointer[AttachedApcEnvironment]?=?&Thread->ApcState;
????????Thread->ApcStateIndex?=?AttachedApcEnvironment;
????}
????if?(Process->State?==?ProcessInMemory)//if?沒(méi)被置換出去
????{
????????KiReleaseDispatcherLockFromDpcLevel();
????????KiReleaseApcLockFromDpcLevel(ApcLock);
????????KiSwapProcess(Process,?SavedApcState->Process);//實(shí)質(zhì)函數(shù)
????????//調(diào)用這個(gè)函數(shù)的目的是檢測(cè)可能的搶占切換條件是否已發(fā)生。(若已發(fā)生就趕緊切換)
????????KiExitDispatcher(ApcLock->OldIrql);//降到指定irql(同時(shí)檢查是否發(fā)生了搶占式切換)
????}
????Else?…
}
實(shí)質(zhì)性的函數(shù)是KiSwapProcess,繼續(xù)看
VOID?KiSwapProcess(IN?PKPROCESS?NewProcess,IN?PKPROCESS?OldProcess)
{
PKIPCR?Pcr?=?(PKIPCR)KeGetPcr();
//關(guān)鍵。修改cr3(存放進(jìn)程頁(yè)目錄的物理地址)寄存器為目標(biāo)進(jìn)程的頁(yè)表
????__writecr3(NewProcess->DirectoryTableBase[0]);
????Ke386SetGs(0);//將gs寄存器清0
????Pcr->TSS->IoMapBase?=?NewProcess->IopmOffset;//修改當(dāng)前線程的IO權(quán)限位圖為目標(biāo)進(jìn)程的那份
}
看到?jīng)],進(jìn)程掛靠的實(shí)質(zhì)工作,就是將cr3寄存器改為目標(biāo)寄存器的地址空間,這樣,線程的所有有關(guān)內(nèi)存的操作,操作的都是目標(biāo)進(jìn)程的地址空間。
明白了進(jìn)程掛靠后,理解跨進(jìn)程操作就很容易了。
一個(gè)進(jìn)程可以調(diào)用OpenProcess打開另一個(gè)進(jìn)程,取得目標(biāo)進(jìn)程的句柄后,就可調(diào)用VirtualAllocEx、WriteProcessMemory、ReadProcessMemory、CreateRemoteThread等函數(shù)操作那個(gè)進(jìn)程的地址空間。這些跨進(jìn)程操作的函數(shù)功能強(qiáng)大,而且?guī)в衅茐男?#xff0c;以至于往往被殺毒軟件重點(diǎn)封殺,特別是CreateRemoteThread這個(gè)函數(shù),冤啊。相關(guān)的示例代碼如下圖所示:
所有的跨進(jìn)程操作都必經(jīng)一步:打開目標(biāo)進(jìn)程。(這是一道需要重點(diǎn)把手的關(guān)口)
HANDLE
OpenProcess(DWORD?dwDesiredAccess,//申請(qǐng)的權(quán)限
????????????BOOL?bInheritHandle,//指本次打開得到的句柄是否可繼承給子進(jìn)程
????????????DWORD?dwProcessId)//目標(biāo)進(jìn)程的pid
{
????NTSTATUS?errCode;
????HANDLE?ProcessHandle;
????OBJECT_ATTRIBUTES?ObjectAttributes;
????CLIENT_ID?ClientId;
????ClientId.UniqueProcess?=?UlongToHandle(dwProcessId);
????ClientId.UniqueThread?=?0;
????InitializeObjectAttributes(&ObjectAttributes,NULL,
???????????????????????????????(bInheritHandle???OBJ_INHERIT?:?0),NULL,NULL);
????//調(diào)用系統(tǒng)服務(wù)打開進(jìn)程
????errCode?=?NtOpenProcess(&ProcessHandle,dwDesiredAccess,&ObjectAttributes,&ClientId);
????if?(!NT_SUCCESS(errCode))
????{
????????SetLastErrorByStatus(errCode);
????????return?NULL;
????}
????return?ProcessHandle;
}
NTSTATUS
NtOpenProcess(OUT?PHANDLE?ProcessHandle,
??????????????IN?ACCESS_MASK?DesiredAccess,
??????????????IN?POBJECT_ATTRIBUTES?ObjectAttributes,
??????????????IN?PCLIENT_ID?ClientId)//pid.tid
{
????KPROCESSOR_MODE?PreviousMode?=?KeGetPreviousMode();
????ULONG?Attributes?=?0;
????BOOLEAN?HasObjectName?=?FALSE;
????PETHREAD?Thread?=?NULL;
????PEPROCESS?Process?=?NULL;
????if?(PreviousMode?!=?KernelMode)
????{
????????_SEH2_TRY
????????{
????????????ProbeForWriteHandle(ProcessHandle);
????????????if?(ClientId)
????????????{
????????????????ProbeForRead(ClientId,?sizeof(CLIENT_ID),?sizeof(ULONG));
????????????????SafeClientId?=?*ClientId;
????????????????ClientId?=?&SafeClientId;
????????????}
????????????ProbeForRead(ObjectAttributes,sizeof(OBJECT_ATTRIBUTES),sizeof(ULONG));
????????????HasObjectName?=?(ObjectAttributes->ObjectName?!=?NULL);
????????????Attributes?=?ObjectAttributes->Attributes;
????????}
????????_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
????????{
????????????_SEH2_YIELD(return?_SEH2_GetExceptionCode());
????????}
????????_SEH2_END;
????}
????else
????{
????????HasObjectName?=?(ObjectAttributes->ObjectName?!=?NULL);
????????Attributes?=?ObjectAttributes->Attributes;
????}
if?((HasObjectName)?&&?(ClientId))//不能同時(shí)給定進(jìn)程名與id
?return?STATUS_INVALID_PARAMETER_MIX;
????//傳遞當(dāng)前令牌以及要求的權(quán)限到AccessState中
????Status?=?SeCreateAccessState(&AccessState,&AuxData,DesiredAccess,
?????????????????????????????????&PsProcessType->TypeInfo.GenericMapping);
????//檢查當(dāng)前令牌是否具有調(diào)試特權(quán)(這就是為什么經(jīng)常在打開目標(biāo)進(jìn)程前要啟用調(diào)試特權(quán))
????if?(SeSinglePrivilegeCheck(SeDebugPrivilege,?PreviousMode))
????{
????????if?(AccessState.RemainingDesiredAccess?&?MAXIMUM_ALLOWED)
????????????AccessState.PreviouslyGrantedAccess?|=?PROCESS_ALL_ACCESS;
????????else
????????????AccessState.PreviouslyGrantedAccess?|=AccessState.RemainingDesiredAccess;
????????AccessState.RemainingDesiredAccess?=?0;
????}
????if?(HasObjectName)?//以對(duì)象名的方式查找該進(jìn)程對(duì)象
????{
????????Status?=?ObOpenObjectByName(ObjectAttributes,PsProcessType,PreviousMode,
????????????????????????????????????&AccessState,0,NULL,&hProcess);
????????SeDeleteAccessState(&AccessState);
????}
????else?if?(ClientId)
????{
????????if?(ClientId->UniqueThread)//根據(jù)tid查找線程、進(jìn)程對(duì)象
????????????Status?=?PsLookupProcessThreadByCid(ClientId,?&Process,?&Thread);
????????Else?//根據(jù)pid從獲活動(dòng)進(jìn)程鏈表中查找進(jìn)程對(duì)象,最常見
????????????Status?=?PsLookupProcessByProcessId(ClientId->UniqueProcess,&Process);
????????if?(!NT_SUCCESS(Status))
????????{
????????????SeDeleteAccessState(&AccessState);
????????????return?Status;
????????}
????????//在該進(jìn)程對(duì)象上打開一個(gè)句柄
????????Status?=?ObOpenObjectByPointer(Process,Attributes,&AccessState,0,
???????????????????????????????????????PsProcessType,PreviousMode,&hProcess);
????????SeDeleteAccessState(&AccessState);
????????if?(Thread)
?ObDereferenceObject(Thread);
????????ObDereferenceObject(Process);
????}
????else
????????return?STATUS_INVALID_PARAMETER_MIX;
????if?(NT_SUCCESS(Status))
????{
????????_SEH2_TRY
????????{
????????????*ProcessHandle?=?hProcess;//返回打開得到的進(jìn)程句柄
????????}
????????_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
????????{
????????????Status?=?_SEH2_GetExceptionCode();
????????}
????????_SEH2_END;
????}
????return?Status;
}
如上,這個(gè)函數(shù)在檢測(cè)權(quán)限滿足后,就打開目標(biāo)進(jìn)程,返回一個(gè)句柄給調(diào)用者。
看下面的典型跨進(jìn)程寫數(shù)據(jù)函數(shù):
NTSTATUS
NtWriteVirtualMemory(IN?HANDLE?ProcessHandle,//遠(yuǎn)程進(jìn)程
?????????????????????IN?PVOID?BaseAddress,
?????????????????????IN?PVOID?Buffer,
?????????????????????IN?SIZE_T?NumberOfBytesToWrite,
?????????????????????OUT?PSIZE_T?NumberOfBytesWritten?OPTIONAL)
{
????KPROCESSOR_MODE?PreviousMode?=?ExGetPreviousMode();
????PEPROCESS?Process;
????NTSTATUS?Status?=?STATUS_SUCCESS;
????SIZE_T?BytesWritten?=?0;
????if?(PreviousMode?!=?KernelMode)
????{
????????if?((((ULONG_PTR)BaseAddress?+?NumberOfBytesToWrite)?<?(ULONG_PTR)BaseAddress)?||
????????????(((ULONG_PTR)Buffer?+?NumberOfBytesToWrite)?<?(ULONG_PTR)Buffer)?||
????????????(((ULONG_PTR)BaseAddress?+?NumberOfBytesToWrite)?>?MmUserProbeAddress)?||
????????????(((ULONG_PTR)Buffer?+?NumberOfBytesToWrite)?>?MmUserProbeAddress))
????????{
????????????return?STATUS_ACCESS_VIOLATION;
????????}
????????_SEH2_TRY
????????{
????????????if?(NumberOfBytesWritten)?ProbeForWriteSize_t(NumberOfBytesWritten);
????????}
????????_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
????????{
????????????_SEH2_YIELD(return?_SEH2_GetExceptionCode());
????????}
????????_SEH2_END;
????}
????if?(NumberOfBytesToWrite)
????{
????????Status?=?ObReferenceObjectByHandle(ProcessHandle,PROCESS_VM_WRITE,PsProcessType,
???????????????????????????????????????????PreviousMode,?(PVOID*)&Process,NULL);
????????if?(NT_SUCCESS(Status))
????????{
????????????Status?=?MmCopyVirtualMemory(PsGetCurrentProcess(),Buffer,Process,
?????????????????????????????????????????BaseAddress,NumberOfBytesToWrite,
?????????????????????????????????????????PreviousMode,&BytesWritten);
????????????ObDereferenceObject(Process);
????????}
????}
????if?(NumberOfBytesWritten)
????{
????????_SEH2_TRY
????????{
????????????*NumberOfBytesWritten?=?BytesWritten;
????????}
????????_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
????????{
????????}
????????_SEH2_END;
????}
????return?Status;
}
NTSTATUS
MmCopyVirtualMemory(IN?PEPROCESS?SourceProcess,
????????????????????IN?PVOID?SourceAddress,
????????????????????IN?PEPROCESS?TargetProcess,
????????????????????OUT?PVOID?TargetAddress,
????????????????????IN?SIZE_T?BufferSize,
????????????????????IN?KPROCESSOR_MODE?PreviousMode,
????????????????????OUT?PSIZE_T?ReturnSize)
{
????NTSTATUS?Status;
????PEPROCESS?Process?=?SourceProcess;
????if?(SourceProcess?==?PsGetCurrentProcess())?Process?=?TargetProcess;
????if?(BufferSize?>?512)//需要使用MDL
????{
????????Status?=?MiDoMappedCopy(SourceProcess,SourceAddress,TargetProcess,TargetAddress,
????????????????????????????????BufferSize,PreviousMode,ReturnSize);
????}
????else
????{
????????Status?=?MiDoPoolCopy(SourceProcess,SourceAddress,TargetProcess,TargetAddress,
??????????????????????????????BufferSize,PreviousMode,ReturnSize);
????}
????return?Status;
}
NTSTATUS
MiDoMappedCopy(IN?PEPROCESS?SourceProcess,
???????????????IN?PVOID?SourceAddress,
???????????????IN?PEPROCESS?TargetProcess,
???????????????OUT?PVOID?TargetAddress,
???????????????IN?SIZE_T?BufferSize,
???????????????IN?KPROCESSOR_MODE?PreviousMode,
???????????????OUT?PSIZE_T?ReturnSize)
{
????PFN_NUMBER?MdlBuffer[(sizeof(MDL)?/?sizeof(PFN_NUMBER))?+?MI_MAPPED_COPY_PAGES?+?1];
????PMDL?Mdl?=?(PMDL)MdlBuffer;
????SIZE_T?TotalSize,?CurrentSize,?RemainingSize;
????volatile?BOOLEAN?FailedInProbe?=?FALSE,?FailedInMapping?=?FALSE,?FailedInMoving;
????volatile?BOOLEAN?PagesLocked;
????PVOID?CurrentAddress?=?SourceAddress,?CurrentTargetAddress?=?TargetAddress;
????volatile?PVOID?MdlAddress;
????KAPC_STATE?ApcState;
????BOOLEAN?HaveBadAddress;
????ULONG_PTR?BadAddress;
????NTSTATUS?Status?=?STATUS_SUCCESS;
????TotalSize?=?14?*?PAGE_SIZE;//每次拷貝14個(gè)頁(yè)面大小
????if?(BufferSize?<=?TotalSize)?TotalSize?=?BufferSize;
????CurrentSize?=?TotalSize;
????RemainingSize?=?BufferSize;
????while?(RemainingSize?>?0)
????{
????????if?(RemainingSize?<?CurrentSize)?CurrentSize?=?RemainingSize;
????????KeStackAttachProcess(&SourceProcess->Pcb,?&ApcState);//掛靠到源進(jìn)程
????????MdlAddress?=?NULL;
????????PagesLocked?=?FALSE;
????????FailedInMoving?=?FALSE;
????????_SEH2_TRY
????????{
????????????if?((CurrentAddress?==?SourceAddress)?&&?(PreviousMode?!=?KernelMode))
????????????{
????????????????FailedInProbe?=?TRUE;
????????????????ProbeForRead(SourceAddress,?BufferSize,?sizeof(CHAR));
????????????????FailedInProbe?=?FALSE;
????????????}
????????????MmInitializeMdl(Mdl,?CurrentAddress,?CurrentSize);
????????????MmProbeAndLockPages(Mdl,?PreviousMode,?IoReadAccess);
????????????PagesLocked?=?TRUE;
????????????MdlAddress?=?MmMapLockedPagesSpecifyCache(Mdl,KernelMode,MmCached,?NULL,
??????????????????????????????????????????????????????FALSE,HighPagePriority);
????????????KeUnstackDetachProcess(&ApcState);//撤銷掛靠
????????????KeStackAttachProcess(&TargetProcess->Pcb,?&ApcState);//掛靠到目標(biāo)進(jìn)程
????????????if?((CurrentAddress?==?SourceAddress)?&&?(PreviousMode?!=?KernelMode))
????????????{
????????????????FailedInProbe?=?TRUE;
????????????????ProbeForWrite(TargetAddress,?BufferSize,?sizeof(CHAR));
????????????????FailedInProbe?=?FALSE;
????????????}
????????????FailedInMoving?=?TRUE;
????????????RtlCopyMemory(CurrentTargetAddress,?MdlAddress,?CurrentSize);//拷貝
????????}
????????_SEH2_EXCEPT()。。。
???
????????if?(Status?!=?STATUS_SUCCESS)?return?Status;
????????KeUnstackDetachProcess(&ApcState);
????????MmUnmapLockedPages(MdlAddress,?Mdl);
????????MmUnlockPages(Mdl);
????????RemainingSize?-=?CurrentSize;
????????CurrentAddress?=?(PVOID)((ULONG_PTR)CurrentAddress?+?CurrentSize);
????????CurrentTargetAddress?=?(PVOID)((ULONG_PTR)CurrentTargetAddress?+?CurrentSize);
????}
????*ReturnSize?=?BufferSize;
????return?STATUS_SUCCESS;
}
看到?jīng)],要掛靠到目標(biāo)進(jìn)程中去復(fù)制數(shù)據(jù)。如果源進(jìn)程不是當(dāng)前進(jìn)程,還要先掛靠到源進(jìn)程中。
七、線程的掛起與恢復(fù)
SuspendThread->NtSuspendThread->PsSuspenThread->?KeSuspendThread,直接看KeSuspendThread函數(shù)
ULONG?KeSuspendThread(PKTHREAD?Thread)
{
????KLOCK_QUEUE_HANDLE?ApcLock;
????ULONG?PreviousCount;
????ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
????KiAcquireApcLock(Thread,?&ApcLock);
????PreviousCount?=?Thread->SuspendCount;
????if?(Thread->ApcQueueable)
????{
????????Thread->SuspendCount++;//遞增掛起計(jì)數(shù)
????????if?(!(PreviousCount)?&&?!(Thread->FreezeCount))
????????{
????????????if?(!Thread->SuspendApc.Inserted)//if尚未插入那個(gè)‘掛起APC’
????????????{
????????????????Thread->SuspendApc.Inserted?=?TRUE;
????????????????KiInsertQueueApc(&Thread->SuspendApc,?IO_NO_INCREMENT);//插入‘掛起APC’
????????????}
????????????else
????????????{
????????????????KiAcquireDispatcherLockAtDpcLevel();
????????????????Thread->SuspendSemaphore.Header.SignalState--;
????????????????KiReleaseDispatcherLockFromDpcLevel();
????????????}
????????}
}
????KiReleaseApcLockFromDpcLevel(&ApcLock);
????KiExitDispatcher(ApcLock.OldIrql);
????return?PreviousCount;
}
這個(gè)專有的‘掛起APC’是一個(gè)特殊的APC,我們看他的工作:
VOID
KiSuspendThread(IN?PVOID?NormalContext,
????????????????IN?PVOID?SystemArgument1,
????????????????IN?PVOID?SystemArgument2)
{
????//等待掛起計(jì)數(shù)減到0
????KeWaitForSingleObject(&KeGetCurrentThread()->SuspendSemaphore,Suspended,KernelMode,
??????????????????????????FALSE,NULL);
}
如上,向指定線程插入一個(gè)‘掛起APC’后,那個(gè)線程下次一得到調(diào)度,就會(huì)先執(zhí)行內(nèi)核中的所有APC,當(dāng)執(zhí)行到這個(gè)APC的時(shí)候,就會(huì)一直等到掛起計(jì)數(shù)降到0。換言之,線程剛一得到調(diào)度運(yùn)行的就會(huì),就又重新進(jìn)入等待了。因此,‘掛起態(tài)’也是一種特殊的‘等待態(tài)’。什么時(shí)候掛起計(jì)數(shù)會(huì)減到0呢?只有在別的線程恢復(fù)這個(gè)線程的掛起計(jì)數(shù)時(shí)。
ULONG?KeResumeThread(IN?PKTHREAD?Thread)
{
????KLOCK_QUEUE_HANDLE?ApcLock;
????ULONG?PreviousCount;
????ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
????KiAcquireApcLock(Thread,?&ApcLock);
????PreviousCount?=?Thread->SuspendCount;
????if?(PreviousCount)
????{
????????Thread->SuspendCount--;//遞減掛起計(jì)數(shù)
????????if?((Thread->SuspendCount==0)?&&?(!Thread->FreezeCount))
????????{
????????????KiAcquireDispatcherLockAtDpcLevel();
????????????Thread->SuspendSemaphore.Header.SignalState++;
????????????//當(dāng)掛起計(jì)數(shù)減到0時(shí),喚醒目標(biāo)線程
????????????KiWaitTest(&Thread->SuspendSemaphore.Header,?IO_NO_INCREMENT);
????????????KiReleaseDispatcherLockFromDpcLevel();
????????}
????}
????KiReleaseApcLockFromDpcLevel(&ApcLock);
????KiExitDispatcher(ApcLock.OldIrql);
????return?PreviousCount;
}
就這樣簡(jiǎn)單。
當(dāng)一個(gè)線程處于等待狀態(tài)時(shí),可以指示本次睡眠是否可被強(qiáng)制喚醒,不必等到條件滿足
如:
DWORD?WaitForSingleObjectEx(
??HANDLE?hHandle,????????
??DWORD?dwMilliseconds,?
??BOOL?bAlertable????//指示本次等待過(guò)程中是否可以被其他線程(或其他線程發(fā)來(lái)的APC)強(qiáng)制喚醒。??
);
BOOLEAN
KeAlertThread(IN?PKTHREAD?Thread,
??????????????IN?KPROCESSOR_MODE?AlertMode)
{
????BOOLEAN?PreviousState;
????KLOCK_QUEUE_HANDLE?ApcLock;
????ASSERT_IRQL_LESS_OR_EQUAL(DISPATCH_LEVEL);
????KiAcquireApcLock(Thread,?&ApcLock);
????KiAcquireDispatcherLockAtDpcLevel();
????PreviousState?=?Thread->Alerted[AlertMode];//檢測(cè)是否收到了來(lái)自那個(gè)模式的強(qiáng)制喚醒要求
????if?(PreviousState==FALSE)
????{
????????if?((Thread->State?==?Waiting)?&&??//線程處于等待狀態(tài)
????????????(Thread->Alertable)?&&??//線程可被強(qiáng)制喚醒
????????????(AlertMode?<=?Thread->WaitMode))??//模式條件符合
????????{
????????????//強(qiáng)制喚醒那個(gè)線程
????????????KiUnwaitThread(Thread,?STATUS_ALERTED,?THREAD_ALERT_INCREMENT);
????????}
????????Else?//僅僅標(biāo)記已收到過(guò)來(lái)自那個(gè)模式的強(qiáng)制喚醒請(qǐng)求
????????????Thread->Alerted[AlertMode]?=?TRUE;
????}
????KiReleaseDispatcherLockFromDpcLevel();
????KiReleaseApcLockFromDpcLevel(&ApcLock);
????KiExitDispatcher(ApcLock.OldIrql);
????return?PreviousState;
}
注意AlertMode?<=?Thread->WaitMode條件指:用戶模式的強(qiáng)制喚醒請(qǐng)求不能喚醒內(nèi)核模式的等待。
八、DLL注入
前面講過(guò),每個(gè)進(jìn)程在啟動(dòng)的時(shí)候會(huì)加載主exe文件依賴的所有子孫dll。實(shí)際上,一般的Win32?GUI進(jìn)程
都會(huì)加載user32.dll模塊。這個(gè)模塊一加載,就會(huì)自動(dòng)搜索注冊(cè)表鍵?HKEY_LOCAL_MACHINE\Software\Microsoft\Windows?NT\CurrentVersion\Windows?下的值:AppInit_DLLs,該值是一個(gè)dll列表,user32.dll會(huì)讀取這個(gè)值,調(diào)用LoadLibrary加載里面的每個(gè)dll,因此我們可以把我們的dll名稱添加到這個(gè)列表中,達(dá)到dll注入的目的。在ReactOS源碼中能看到下面的代碼:
INT DllMain(???//User32.dll的DllMain
??????????IN?PVOID?hInstanceDll,
??????????IN?ULONG?dwReason,
??????????IN?PVOID?reserved)
{
???switch?(dwReason)
???{
??????case?DLL_PROCESS_ATTACH:
?????????Init();//會(huì)調(diào)用這個(gè)函數(shù)
?????????…
??????…
???}
?}
BOOL??Init(VOID)
{
??…
??LoadAppInitDlls();//會(huì)調(diào)用這個(gè)函數(shù)加載那些dll
??…
}
VOID??LoadAppInitDlls()
{
????szAppInit[0]?=?UNICODE_NULL;
????if?(GetDllList())//讀取這冊(cè)表鍵的值,將要加載的dll列表保存在全局變量szAppInit中
????{
????????WCHAR?buffer[KEY_LENGTH];
????????LPWSTR?ptr;
???size_t?i;
????????RtlCopyMemory(buffer,?szAppInit,?KEY_LENGTH);
for?(i?=?0;?i?<?KEY_LENGTH;?++?i)
{
if(buffer[i]?==?L'?'?||?buffer[i]?==?L',')//dll名稱之間必須用空格或逗號(hào)隔開
buffer[i]?=?0;
}
for?(i?=?0;?i?<?KEY_LENGTH;?)
{
if(buffer[i]?==?0)
++?i;
else
{
ptr?=?buffer?+?i;
i?+=?wcslen(ptr);
LoadLibraryW(ptr);//加載每個(gè)dll
}
}
????}
}
BOOL??GetDllList()
{
????NTSTATUS?Status;
????OBJECT_ATTRIBUTES?Attributes;
????BOOL?bRet?=?FALSE;
????BOOL?bLoad;
????HANDLE?hKey?=?NULL;
????DWORD?dwSize;
????PKEY_VALUE_PARTIAL_INFORMATION?kvpInfo?=?NULL;
????UNICODE_STRING?szKeyName?=?RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Microsoft\\Windows?NT\\CurrentVersion\\Windows");
????UNICODE_STRING?szLoadName?=?RTL_CONSTANT_STRING(L"LoadAppInit_DLLs");
????UNICODE_STRING?szDllsName?=?RTL_CONSTANT_STRING(L"AppInit_DLLs");
????InitializeObjectAttributes(&Attributes,?&szKeyName,?OBJ_CASE_INSENSITIVE,?NULL,?NULL);
????Status?=?NtOpenKey(&hKey,?KEY_READ,?&Attributes);
????if?(NT_SUCCESS(Status))
????{
????????dwSize?=?sizeof(KEY_VALUE_PARTIAL_INFORMATION)?+?sizeof(DWORD);
????????kvpInfo?=?HeapAlloc(GetProcessHeap(),?0,?dwSize);
????????if?(!kvpInfo)
????????????goto?end;
???????//先要在那個(gè)鍵中建立一個(gè)DWORD值:LoadAppInit_DLLs,并將數(shù)值設(shè)為1
????????Status?=?NtQueryValueKey(hKey,&szLoadName,KeyValuePartialInformation,
?????????????????????????????????kvpInfo,dwSize,&dwSize);
????????RtlMoveMemory(&bLoad,kvpInfo->Data,kvpInfo->DataLength);
????????HeapFree(GetProcessHeap(),?0,?kvpInfo);
????????kvpInfo?=?NULL;
????????if?(bLoad)//if?需要加載初始列表的那些dll
????????{
????????????Status?=?NtQueryValueKey(hKey,&szDllsName,KeyValuePartialInformation,
?????????????????????????????????????NULL,0,&dwSize);
????????????kvpInfo?=?HeapAlloc(GetProcessHeap(),?0,?dwSize);
????????????Status?=?NtQueryValueKey(hKey,?&szDllsName,KeyValuePartialInformation,
?????????????????????????????????????kvpInfo,dwSize,&dwSize);
????????????if?(NT_SUCCESS(Status))
????????????{
????????????????LPWSTR?lpBuffer?=?(LPWSTR)kvpInfo->Data;
????????????????if?(*lpBuffer?!=?UNICODE_NULL)
????????????????{
????????????????????INT?bytesToCopy,?nullPos;
????????????????????bytesToCopy?=?min(kvpInfo->DataLength,?KEY_LENGTH?*?sizeof(WCHAR));
????????????????????if?(bytesToCopy?!=?0)
????????????????????{
????????????????????????//dll列表拷到全局變量
????????????????????????RtlMoveMemory(szAppInit,kvpInfo->Data,bytesToCopy);?
????????????????????????nullPos?=?(bytesToCopy?/?sizeof(WCHAR))?-?1;
????????????????????????szAppInit[nullPos]?=?UNICODE_NULL;
????????????????????????bRet?=?TRUE;
????????????????????}
????????????????}
????????????}
????????}
????}
end:
????if?(hKey)
????????NtClose(hKey);
????if?(kvpInfo)
????????HeapFree(GetProcessHeap(),?0,?kvpInfo);
????return?bRet;
}
因此,只需在那個(gè)鍵下面添加一個(gè)DWORD值:LoadAppInit_DLLs,設(shè)為1,然后在AppInit_DLLs值中添加我們的dll即可達(dá)到將我們的dll加載到任意GUI進(jìn)程的地址空間中。
總結(jié)
以上是生活随笔為你收集整理的windows内核情景分析---进程线程2的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 治疗不孕不育价格
- 下一篇: 攻破Win7~Win10 PatchGu
