在线程调度里可以看到,需要调用函数KiSwapContext来进行运行环境切换,由于每个CPU都是只能运行一个线程,而多个线程在运行过程中被中断了,那么就需要保存CPU所有寄存器,以便下一次恢复线程时可以接续运行。现在就来分析这个函数是怎么样实现这些工作的,代码如下:
#001 /*++
#002 * KiSwapContext
#003 *
#004 * The KiSwapContext routine switches context to another thread.
#005 *
#006 * Params:
#007 * TargetThread - Pointer to the KTHREAD to which the caller wishes to
#008 * switch to.
#009 *
#010 * Returns:
#011 * The WaitStatus of the Target Thread.
#012 *
#013 * Remarks:
#014 * This is a wrapper around KiSwapContextInternal which will save all the
#015 * non-volatile registers so that the Internal function can use all of
#016 * them. It will also save the old current thread and set the new one.
#017 *
#018 * The calling thread does not return after KiSwapContextInternal until
#019 * another thread switches to IT.
#020 *
#021 *--*/
#022 .globl @KiSwapContext@8
#023 .func @KiSwapContext@8, @KiSwapContext@8
#024 @KiSwapContext@8:
#025
保存寄存器的值,以便调用后返回。
#026 /* Save 4 registers */
#027 sub esp, 4 * 4
#028
#029 /* Save all the non-volatile ones */
#030 mov [esp+12], ebx
#031 mov [esp+8], esi
#032 mov [esp+4], edi
#033 mov [esp+0], ebp
#034
获取处理器块KPCR,因为FS保存了KPCR的数据结构所在的段。
#035 /* Get the current KPCR */
#036 mov ebx, fs:[KPCR_SELF]
#037
获取当前线程指针。
#038 /* Get the Current Thread */
#039 mov edi, ecx
#040
获取下一个将要运行的线程指针。
#041 /* Get the New Thread */
#042 mov esi, edx
#043
获取当前的IRQL
#044 /* Get the wait IRQL */
#045 movzx ecx, byte ptr [edi+KTHREAD_WAIT_IRQL]
#046
调用函数KiSwapContextInternal来切换运行环境。
#047 /* Do the swap with the registers correctly setup */
#048 call @KiSwapContextInternal@0
#049
恢复调用前的寄存器值。
#050 /* Return the registers */
#051 mov ebp, [esp+0]
#052 mov edi, [esp+4]
#053 mov esi, [esp+8]
#054 mov ebx, [esp+12]
#055
#056 /* Clean stack */
#057 add esp, 4 * 4
#058 ret
#059 .endfunc
这个函数主要把C函数调用修改为合适的函数KiSwapContextInternal调用。因此接着下来分析函数KiSwapContextInternal的代码,如下:
#001 /*++
#002 * KiSwapContextInternal
#003 *
#004 * The KiSwapContextInternal routine switches context to another thread.
#005 *
#006 * Params:
下一个将要运行的线程。
#007 * ESI - Pointer to the KTHREAD to which the caller wishes to
#008 * switch to.
当前运行的线程。
#009 * EDI - Pointer to the KTHREAD to which the caller wishes to
#010 * switch from.
#011 *
#012 * Returns:
#013 * None.
#014 *
#015 * Remarks:
#016 * Absolutely all registers except ESP can be trampled here for maximum code flexibility.
#017 *
#018 *--*/
#019 .globl @KiSwapContextInternal@0
#020 .func @KiSwapContextInternal@0, @KiSwapContextInternal@0
#021 @KiSwapContextInternal@0:
#022
保存IRQL。
#023 /* Save the IRQL */
#024 push ecx
#025
判断是否支持对称多核处理器。
#026 #ifdef CONFIG_SMP
#027 GetSwapLock:
#028 /* Acquire the swap lock */
#029 cmp byte ptr [esi+KTHREAD_SWAP_BUSY], 0
#030 jz NotBusy
#031 pause
#032 jmp GetSwapLock
#033 #endif
#034 NotBusy:
#035 /* Increase context switches (use ES for lazy load) */
#036 inc dword ptr es:[ebx+KPCR_CONTEXT_SWITCHES]
#037
保存当前线程的运行环境到当前线程栈里。
#038 /* Save the Exception list */
#039 push [ebx+KPCR_EXCEPTION_LIST]
#040
#041 /* Check for WMI */
#042 cmp dword ptr [ebx+KPCR_PERF_GLOBAL_GROUP_MASK], 0
#043 jnz WmiTrace
#044
#045 AfterTrace:
#046 #ifdef CONFIG_SMP
#047 #ifdef DBG
#048 /* Assert that we're on the right CPU */
#049 mov cl, [esi+KTHREAD_NEXT_PROCESSOR]
#050 cmp cl, [ebx+KPCR_PROCESSOR_NUMBER]
#051 jnz WrongCpu
#052 #endif
#053 #endif
#054
#055 /* Get CR0 and save it */
#056 mov ebp, cr0
#057 mov edx, ebp
#058
#059 #ifdef CONFIG_SMP
#060 /* Check NPX State */
#061 cmp byte ptr [edi+KTHREAD_NPX_STATE], NPX_STATE_LOADED
#062 jz NpxLoaded
#063 #endif
#064
#065 SetStack:
保存当前线程的栈。
#066 /* Set new stack */
#067 mov [edi+KTHREAD_KERNEL_STACK], esp
#068
#069 /* Checking NPX, disable interrupts now */
#070 mov eax, [esi+KTHREAD_INITIAL_STACK]
#071 cli
#072
#073 /* Get the NPX State */
#074 movzx ecx, byte ptr [esi+KTHREAD_NPX_STATE]
#075
#076 /* Clear the other bits, merge in CR0, merge in FPU CR0 bits and compare */
#077 and edx, ~(CR0_MP + CR0_EM + CR0_TS)
#078 or ecx, edx
#079 or ecx, [eax - (NPX_FRAME_LENGTH - FN_CR0_NPX_STATE)]
#080 cmp ebp, ecx
#081 jnz NewCr0
#082
#083 StackOk:
开中断,并切换到新的栈上。
#084 /* Enable interrupts and set the current stack */
#085 sti
#086 mov esp, [esi+KTHREAD_KERNEL_STACK]
#087
检查当前线程和下一个运行线程是否同一个进程空间。
#088 /* Check if address space switch is needed */
#089 mov ebp, [esi+KTHREAD_APCSTATE_PROCESS]
#090 mov eax, [edi+KTHREAD_APCSTATE_PROCESS]
#091 cmp ebp, eax
跳到同一个进程处理。
#092 jz SameProcess
#093
#094 #ifdef CONFIG_SMP
#095 /* Get the active processors and XOR with the process' */
#096 mov ecx, [ebx+KPCR_SET_MEMBER_COPY]
#097 lock xor [ebp+KPROCESS_ACTIVE_PROCESSORS], ecx
#098 lock xor [eax+KPROCESS_ACTIVE_PROCESSORS], ecx
#099
#100 /* Assert change went ok */
#101 #ifdef DBG
#102 test [ebp+KPROCESS_ACTIVE_PROCESSORS], ecx
#103 jz WrongActiveCpu
#104 test [eax+KPROCESS_ACTIVE_PROCESSORS], ecx
#105 jz WrongActiveCpu
#106 #endif
#107 #endif
#108
检查是否需要加载LDT。
#109 /* Check if we need an LDT */
#110 mov ecx, [ebp+KPROCESS_LDT_DESCRIPTOR0]
#111 or ecx, [eax+KPROCESS_LDT_DESCRIPTOR0]
#112 jnz LdtReload
#113
更新CR3寄存器,以便更新进程的地址空间。其实就是更新内存的页寄存目录。
#114 UpdateCr3:
#115 /* Switch address space */
#116 mov eax, [ebp+KPROCESS_DIRECTORY_TABLE_BASE]
#117 mov cr3, eax
#118
同一个进程地址空间。
#119 SameProcess:
#120
#121 #ifdef CONFIG_SMP
#122 /* Release swap lock */
#123 and byte ptr [edi+KTHREAD_SWAP_BUSY], 0
#124 #endif
#125
#126 /* Clear gs */
#127 xor eax, eax
#128 mov gs, ax
#129
设置下一个线程运行的TEB。
#130 /* Set the TEB */
#131 mov eax, [esi+KTHREAD_TEB]
#132 mov [ebx+KPCR_TEB], eax
#133 mov ecx, [ebx+KPCR_GDT]
#134 mov [ecx+0x3A], ax
#135 shr eax, 16
#136 mov [ecx+0x3C], al
#137 mov [ecx+0x3F], ah
#138
获取下一个线程的栈指针。
#139 /* Get stack pointer */
#140 mov eax, [esi+KTHREAD_INITIAL_STACK]
#141
计算下一个线程运行的栈空间大小。
#142 /* Make space for the NPX Frame */
#143 sub eax, NPX_FRAME_LENGTH
#144
检查是否为虚拟86的运行模式。
#145 /* Check if this isn't V86 Mode, so we can bias the Esp0 */
#146 test dword ptr [eax - KTRAP_FRAME_SIZE + KTRAP_FRAME_EFLAGS], EFLAGS_V86_MASK
#147 jnz NoAdjust
#148
#149 /* Bias esp */
#150 sub eax, KTRAP_FRAME_V86_GS - KTRAP_FRAME_SS
#151
#152 NoAdjust:
#153
设置下一个运行线程的任务段TSS。
#154 /* Set new ESP0 */
#155 mov ecx, [ebx+KPCR_TSS]
#156 mov [ecx+KTSS_ESP0], eax
#157
#158 /* Set current IOPM offset in the TSS */
#159 mov ax, [ebp+KPROCESS_IOPM_OFFSET]
#160 mov [ecx+KTSS_IOMAPBASE], ax
#161
#162 /* Increase context switches */
#163 inc dword ptr [esi+KTHREAD_CONTEXT_SWITCHES]
#164
从下一个线程的栈里获取将要运行的环境。
#165 /* Restore exception list */
#166 pop [ebx+KPCR_EXCEPTION_LIST]
#167
#168 /* Restore IRQL */
#169 pop ecx
#170
#171 /* DPC shouldn't be active */
#172 cmp byte ptr [ebx+KPCR_PRCB_DPC_ROUTINE_ACTIVE], 0
#173 jnz BugCheckDpc
#174
#175 /* Check if kernel APCs are pending */
#176 cmp byte ptr [esi+KTHREAD_PENDING_KERNEL_APC], 0
#177 jnz CheckApc
#178
如果没有异步调用APC,就直接返回。
#179 /* No APCs, return */
#180 xor eax, eax
#181 ret
#182
下面检查异步调用APC。
#183 CheckApc:
#184
#185 /* Check if they're disabled */
#186 cmp word ptr [esi+KTHREAD_SPECIAL_APC_DISABLE], 0
#187 jnz ApcReturn
#188 test cl, cl
#189 jz ApcReturn
#190
#191 /* Request APC Delivery */
#192 mov cl, APC_LEVEL
#193 call @HalRequestSoftwareInterrupt@4
#194 or eax, esp
#195
#196 ApcReturn:
#197
#198 /* Return with APC pending */
#199 setz al
#200 ret
#201
需要重新加局部描述符表LDT。
#202 LdtReload:
#203 /* Check if it's empty */
#204 mov eax, [ebp+KPROCESS_LDT_DESCRIPTOR0]
#205 test eax, eax
#206 jz LoadLdt
#207
#208 /* Write the LDT Selector */
#209 mov ecx, [ebx+KPCR_GDT]
#210 mov [ecx+KGDT_LDT], eax
#211 mov eax, [ebp+KPROCESS_LDT_DESCRIPTOR1]
#212 mov [ecx+KGDT_LDT+4], eax
#213
#214 /* Write the INT21 handler */
#215 mov ecx, [ebx+KPCR_IDT]
#216 mov eax, [ebp+KPROCESS_INT21_DESCRIPTOR0]
#217 mov [ecx+0x108], eax
#218 mov eax, [ebp+KPROCESS_INT21_DESCRIPTOR1]
#219 mov [ecx+0x10C], eax
#220
#221 /* Save LDT Selector */
#222 mov eax, KGDT_LDT
#223
#224 LoadLdt:
#225 lldt ax
#226 jmp UpdateCr3
#227
需要重新计算CR0寄存器值。
#228 NewCr0:
#229
#230 #ifdef DBG
#231 /* Assert NPX State */
#232 test byte ptr [esi+KTHREAD_NPX_STATE], ~(NPX_STATE_NOT_LOADED)
#233 jnz InvalidNpx
#234 test dword ptr [eax - (NPX_FRAME_LENGTH - FN_CR0_NPX_STATE)], ~(CR0_PE + CR0_MP + CR0_EM + CR0_TS)
#235 jnz InvalidNpx
#236 #endif
#237
#238 /* Update CR0 */
#239 mov cr0, ecx
#240 jmp StackOk
#241
#242 #ifdef CONFIG_SMP
#243 NpxLoaded:
#244
#245 /* FIXME: TODO */
#246 int 3
#247
#248 /* Jump back */
#249 jmp SetStack
#250 #endif
#251
下面出错处理。
#252 WmiTrace:
#253
#254 /* No WMI support yet */
#255 int 3
#256
#257 /* Jump back */
#258 jmp AfterTrace
#259
#260 BugCheckDpc:
#261
#262 /* Bugcheck the machine, printing out the threads being switched */
#263 mov eax, [edi+KTHREAD_INITIAL_STACK]
#264 push 0
#265 push eax
#266 push esi
#267 push edi
#268 push ATTEMPTED_SWITCH_FROM_DPC
#269 call _KeBugCheckEx@20
#270
#271 #ifdef DBG
#272 InvalidNpx:
#273 int 3
#274 WrongActiveCpu:
#275 int 3
#276 WrongCpu:
#277 int 3
#278 #endif
#279 .endfunc
通过上面的函数分析,可以了解到线程的环境切换,主要就是线程的页面切换(CR3),线程的环境块切换(TEB),任务段切换ESP0(TSS)。