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),任务段切换ESP0TSS)。

你可能感兴趣的:(thread,exception,list,byte,Descriptor,printing)