reactos操作系统实现(46)
在线程调度里可以看到,需要调用函数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)。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |