KiSwapThread(OldThread, CurrentPrcb)
{
//单核:从Prcb拿一个新线程,如果没有就选(KiSelectReadyThread)一个,如果再没有就拿Idle
NewThread = CurrentPrcb->NextThread
//多核:自己还是没有可执行的线程就从其他核去拿,如果有就得看是不是Idle,要是Idle那还得去其他核上找,不是的话就算拿到了
//切换上下文,FASTCALL KiSwapContext这个函数是个纯汇编函数
KiSwapContext(OldThread, NewThread)
{
......
mov ebx, PCR[PcSelfPcr]
mov edi, ecx ; set old thread address
mov esi, edx ; set new thread address
movzx ecx, byte ptr [edi].ThWaitirql ; set APC interrupt bypass disable
call SwapContext ;这块就把下一条指令的地址压栈了,对应新线程内核栈中的SwitchFrame->RetAddress,不过新线程内核栈存的是KiThreadStartup
......
}
}
SwapContext proc
;
; Save the APC disable flag.
;
push ecx ; save APC bypass disable ;这条指令就把APC bypass disable的状态压栈了,也能和新线程内核栈中的SwitchFrame->ApcBypassDisable对应上,而新线程是TRUE
cPublicFpo 0, 1
;
; Wait for context to be swapped forthe target thread.
;
ifndef NT_UP
sc00: cmp byte ptr [esi].ThSwapBusy, 0 ; check if context swap busy
je short sc01 ; if e, context swap idle
YIELD ; yield execution for SMT system
jmp short sc00 ;
endif
;
; Increment thenumberof context switches on this processor.
;
; N.B. This increment is done here is force the cache block containing the
; context switch countintothe cache aswrite exclusive. There are
; several other references to this cache block inthe following code.
;
sc01: inc es:dword ptr [ebx]+PcContextSwitches ; processor count
;
; Save the thread exception list head.
;
push [ebx]+PcExceptionList ; save thread exception list head ;这条指令就把ExceptionList压栈,也和新线程内核栈SwitchFrame->ExceptionList对应上,到这新线程内核栈不会再有东西,那也说明OldThread内核栈不会再增长,要把esp切到NewThread的内核栈了
cPublicFpo 0, 2
;
; Check for context swap logging.
;
cmp [ebx]+PcPerfGlobalGroupMask, 0 ; check if logging enable
jne sc92 ; If not, then check if we are enabled
sc03:
ifndef NT_UP
if DBG
mov cl, [esi]+ThNextProcessor ; get current processor number
cmp cl, [ebx]+PcPrcbData+PbNumber ; same asrunning processor?
jne sc_error2 ; if ne, processor number mismatch
endif
endif
;
; On a uniprocessor system the NPX state is swapped in a lazy manner.
; If a thread whose state isnotinthe coprocessor attempts to perform
; a coprocessor operation, the current NPX state is swapped out (if needed),
; andthe new state is swapped in during the fault. (KiTrap07)
;
; On a multiprocessor system we still fault inthe NPX state on demand, but
; we save the state when the thread switches out (assuming the NPX state
; was loaded). This is because it could be difficult to obtain the thread's
; NPX inthe trap handler ifit was loaded into a different processor's
; coprocessor.
;
mov ebp, cr0 ; get current CR0
mov edx, ebp ;
ifndef NT_UP
cmp byte ptr [edi]+ThNpxState, NPX_STATE_LOADED ; check if NPX state
je sc_save_npx_state ; if e, NPX state not loaded
endif
;
; Save the old stack pointer and compute the new stack limits.
;
sc05: mov [edi]+ThKernelStack, esp ; save old kernel stack pointer ;OldThread的esp保存下来
mov eax, [esi]+ThInitialStack ; get new initial stack pointer
;
; (eax) = Initial Stack
; (ebx) = PCR
; (edi) = OldThread
; (esi) = NewThread
; (ebp) = Current CR0
; (edx) = Current CR0
;
.errnz (NPX_STATE_NOT_LOADED - CR0_TS - CR0_MP)
.errnz (NPX_STATE_LOADED - 0)
ifdef NT_UP
;
; On UP systems floating point state might be being changed by an ISR so we
; block interrupts.
;
cli
endif
movzx ecx, byte ptr [esi]+ThNpxState ; new NPX state is (orisnot) loaded
and edx, NOT (CR0_MP+CR0_EM+CR0_TS) ; clear thread settable NPX bits
or ecx, edx ; orin new thread's cr0
or ecx, [eax]+FpCr0NpxState-NPX_FRAME_LENGTH ; merge new thread settable state
cmp ebp, ecx ; check if old and new CR0 match
jne sc_reload_cr0 ; if ne, change in CR0
sc06:
ifdef NT_UP
sti
endif
if DBG
mov eax, [esi]+ThKernelStack ; set new stack pointer
cmp esi, dword ptr [eax-4]
je @f
int 3
@@:
xchg esp, eax
mov [eax-4], edi ; Save thread address on stack below stack pointer
else
mov esp, [esi]+ThKernelStack ; set new stack pointer ;把NewThread的内核栈给esp,完成切换
endif
;
; Check ifthe old process isthe same asthe new process.
;
mov ebp, [esi].ThApcState.AsProcess ; get old process address ;这块就是看OldThrad和NewThread是不是属于一个进程,因为要牵扯切换Cr3
mov eax, [edi].ThApcState.AsProcess ; get old process address
cmp ebp, eax ; check if process match
jz short sc23 ; if z, process match
;
; Set the processor bit inthe new process and clear the old.
;
ifndef NT_UP
mov ecx, [ebx]+PcSetMemberCopy ; get processor set member
lock xor [ebp]+PrActiveProcessors, ecx ; set bit in new processor set
lock xor [eax]+PrActiveProcessors, ecx ; clear bit in old processor setif DBG
test [ebp]+PrActiveProcessors, ecx ; test if bit setin new set
jz sc_error5 ; if z, bit notsetin new set
test [eax]+PrActiveProcessors, ecx ; test if bit clear in old set
jnz sc_error4 ; if nz, bit not clear in old set
endif
endif
;
; LDT switch, If either the target or source process have an LDT we need to
; load the ldt
;
mov ecx, [ebp]+PrLdtDescriptor ;这块就看是不是要重载ldt表
or ecx, [eax]+PrLdtDescriptor
jnz sc_load_ldt ; if nz, LDT limit
sc_load_ldt_ret:
;
; Load the new CR3 andas a side effect flush non-global TB entries.
;
mov eax, [ebp]+PrDirectoryTableBase ; get new directory base
mov cr3, eax ; and flush TB
;
; Set context swap idle forthe old thread.
;
sc23: ;
ifndef NT_UP
and byte ptr [edi].ThSwapBusy, 0 ; clear old thread swap busy
endif
xor eax, eax
mov gs, eax
;
; Set the TEB descriptor to point tothe thread TEB andsetthe TEB address
; inthe PCR. The es override here isto force lazy segment loading to occur.
;
mov eax, [esi]+ThTeb ; get user TEB address
mov [ebx]+PcTeb, eax ; set user TEB address ;把新线程的TEB设到Prcb上
mov ecx, [ebx]+PcGdt ; get GDT address
mov [ecx]+(KGDT_R3_TEB+KgdtBaseLow), ax ; ;改Gdt表中的Teb项
shr eax, 16 ;
mov [ecx]+(KGDT_R3_TEB+KgdtBaseMid), al ;
mov [ecx]+(KGDT_R3_TEB+KgdtBaseHi), ah ;
;
; Adjust the initial stack address, if necessary, and store inthe TSS so V86
; mode threads and32 bit threads can share a common trapframe structure and
; the NPX save area will be accessible inthe same manner on all threads.
;
mov eax, [esi].ThInitialStack ; get initial stack address
sub eax, NPX_FRAME_LENGTH
.errnz (EFLAGS_V86_MASK AND 0FF00FFFFh)
test byte ptr [eax] - KTRAP_FRAME_LENGTH + TsEFlags + 2, EFLAGS_V86_MASK / 10000h
jnz short sc24 ; if nz, V86 frame, no adjustment
sub eax, TsV86Gs - TsHardwareSegSs ; bias for missing fields
sc24: mov ecx, [ebx]+PcTssCopy ; get TSS address
mov [ecx]+TssEsp0, eax ; set initial kernel stack address
;
; Set the IOPM map offset value.
;
; N.B. This may be a redundant load of this value ifthe process did not
; change during the context switch. However, always reloading this
; value saves several instructions under the context swap lock.
;
mov ax, [ebp]+PrIopmOffset ; set IOPM offset
mov [ecx]+TssIoMapBase, ax ;
;
; Update context switch counters.
;
inc dword ptr [esi]+ThContextSwitches ; thread count
;
; Restore thread exception list head andget APC bypass disable.
;
pop [ebx].PcExceptionList ; restore thread exception list head
pop ecx ; get APC bypass disable
;
; Check if an attempt is being made to context switch whilein a DPC routine.
;
cmp byte ptr [ebx]+PcPrcbData+PbDpcRoutineActive, 0 ; check if DPC active
jne sc91 ; bugcheck if DPC active. ;这时候不能有Dpc
;
; If the new thread has a kernel mode APC pending, then request an APC
; interrupt.
;
cmp byte ptr [esi].ThApcState.AsKernelApcPending, 0 ; APC pending?
jne short sc80 ; if ne, kernel APC pending
xor eax, eax ; setreturn value
ret ; return
;
; The new thread has an APC interrupt pending.
;
; If thethe special APC disable countis nonzero, thenreturn no kernel APC
; pending. An APC will be requested when the special APC disable count reaches
; zero.
;
; If APC interrupt bypass isnot enabled, then request a software interrupt
; at APC_LEVEL andreturn no kernel APC pending. Otherwise, return kernel APC
; pending.
;
sc80: cmp word ptr [esi].ThSpecialApcDisable, 0 ; check if special APC disable
jne short sc90 ; if ne, special APC disable
test cl, cl ; test for APC bypass disable
jz short sc90 ; if z, APC bypass enabled
mov cl, APC_LEVEL ; request software interrupt level
fstCall HalRequestSoftwareInterrupt ;
or eax, esp ; clear ZF flag
sc90: setz al ; setreturn value
ret ; return
;
; Set for new LDT value
;
sc_load_ldt:
mov eax, [ebp+PrLdtDescriptor] ;
test eax, eax
je @f
mov ecx, [ebx]+PcGdt ; get GDT address
mov [ecx+KGDT_LDT], eax ;
mov eax, [ebp+PrLdtDescriptor+4] ;
mov [ecx+KGDT_LDT+4], eax ;
;
; Set up int 21 descriptor of IDT. If the process doesnot have an Ldt, it
; should never make any int 21 calls. If itdoes, an exception is generated. If
; the process has an Ldt, we need to update int21 entry of LDT forthe process.
; Note the Int21Descriptor ofthe process may simply indicate an invalid
; entry. In which case, the int 21 will be trapped tothe kernel.
;
mov ecx, [ebx]+PcIdt ;
mov eax, [ebp+PrInt21Descriptor] ;
mov [ecx+21h*8], eax ;
mov eax, [ebp+PrInt21Descriptor+4] ;
mov [ecx+21h*8+4], eax ;
mov eax, KGDT_LDT
@@: lldt ax
jmp sc_load_ldt_ret
;
; Cr0 has changed (ie, floating point processor present), load the new value.
;
sc_reload_cr0:
if DBG
test byte ptr [esi]+ThNpxState, NOT (CR0_TS+CR0_MP)
jnz sc_error ;
test dword ptr [eax]+FpCr0NpxState-NPX_FRAME_LENGTH, NOT (CR0_PE+CR0_MP+CR0_EM+CR0_TS)
jnz sc_error3 ;
endif
mov cr0,ecx ; set new CR0 NPX state
jmp sc06
;
; Save coprocessor's current context. FpCr0NpxState isthe current thread's
; CR0 state. The following bits are valid: CR0_MP, CR0_EM, CR0_TS. MVDMs
; may setand clear MP & EM as they please andthe settings will be reloaded
; on a context switch (but they will not be saved from CR0 to Cr0NpxState).
; The kernel sets and clears TS as required.
;
; (ebp) = Current CR0
; (edx) = Current CR0
;
ifndef NT_UP
sc_save_npx_state:
and edx, NOT (CR0_MP+CR0_EM+CR0_TS) ; we need access tothe NPX state
mov ecx, [edi].ThInitialStack ; get NPX save save area address
sub ecx, NPX_FRAME_LENGTH
cmp ebp, edx ; Does CR0 need reloading?
je short sc_npx10
mov cr0, edx ; set new cr0
mov ebp, edx ; (ebp) = (edx) = current cr0 state
sc_npx10:
;
; The fwait following the fnsave isto make sure thatthe fnsave has stored the
; data intothe save area before this coprocessor state could possibly be
; context switched inand used on a different (co)processor. I've added the
; clocks from when the dispatcher lock is released and don't believe it's a
; possibility. I've also timed the impact this fwait seems to have on a 486
; when performing lots of numeric calculations. It appears asif there is
; nothing to wait forafterthe fnsave (although the486 manual says there is)
; and therefore the calculation time far outweighed the3clk fwait andit
; didn't make a noticable difference.
;
;
; If FXSR feature is NOT present onthe processor, the fxsave instruction is
; patched at boot timeto start using fnsave instead
;
_ScPatchFxb:
; fxsave [ecx] ; save NPX state
db 0FH, 0AEH, 01
_ScPatchFxe:
mov byte ptr [edi]+ThNpxState, NPX_STATE_NOT_LOADED ; set no NPX state
mov dword ptr [ebx].PcPrcbData+PbNpxThread, 0 ; clear npx owner
jmp sc05
endif
;
; This code isout of line to optimize the normal case with tracing is off.
;
sc92: mov eax, [ebx]+PcPerfGlobalGroupMask ; Load the ptr into eax
cmp eax, 0 ; catch race condition on pointer here
jz sc03 ; instead ofabovein mainline code
mov edx, esi ; pass the new ETHREAD object
mov ecx, edi ; pass the old ETHREAD object
test dword ptr [eax+PERF_CONTEXTSWAP_OFFSET], PERF_CONTEXTSWAP_FLAG
jz sc03 ; returnif our flag isnotset
fstCall WmiTraceContextSwap ; call the Wmi context swap trace
jmp sc03 ;
;
; A context switch was attempted while executing a DPC - bugcheck.
;
.fpo (2, 0, 0, 0, 0, 0)
sc91:
mov eax, [edi]+ThInitialStack ; getthe old stack so it can
; be saved inthe minidump
stdCall _KeBugCheckEx <ATTEMPTED_SWITCH_FROM_DPC, edi, esi, eax, 0>
ret ; returnif DBG
sc_error5: int 3
sc_error4: int 3
sc_error3: int 3
sc_error2: int 3
sc_error: int 3
endif
SwapContext endp