71v5 发表于 2014-06-23 02:53

freebsd9.2-线程切换阶段02-硬件上下文的切换-cpu_switch函数

本帖最后由 71v5 于 2014-06-25 21:17 编辑

当mi_switch函数调用调度程序相关的sched_switch函数时,函数sched_switch在选择要运行的新thread后,就会调用cpu_switch函数完成线程
硬件上下文的切换,线程硬件上下文保存在struct pcb类型的数据对象中。当cpu_switch函数执行完后,新线程就开始运行。
调用cpu_switch函数时,线程oldtd的内核栈布局如下(运行一段时间的新thread的内核栈类似):******************************* 内核栈顶部 高地址方向
*                           *
*   struct pcb对象            *
*                           *
*                           *
******************************* <--struct thread的td_pcb成员指向这里
*                           *
*   16bytes for vm            *
******************************* <--- 当进行线程切换时,TSS的esp0成员指向这里,内核栈的栈顶                     
*                           *
*                           *
*                           *
*struct trapframe 对象      *
*                           *
******************************* <-- struct thread的td_frame成员指向这里                     
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*******************************
*      newlock                *
******************************* esp+12                        
*      newtd                  *
******************************* esp+8                           
*      oldtd                  *
******************************* esp+4                           
*                           *
* cpu_switch(return address)*
*******************************调用cpu_switch函数时,esp指向这里,当oldtd_thread再次被调度运行时,esp将指向这里
*                           *
*                           *
*                           *
*******************************------ 内核栈底部 低地址方向----[新创建的线程的内核栈如下所示]:******************************* 内核栈顶部 高地址方向
*                           *
*   struct pcb对象            *
*                           *
*                           *
******************************* <--struct thread的td_pcb成员指向这里
*                           *
*   16bytes for vm            *       
*******************************                           
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*硬件上下文               *
*通过struct trapframe对象   *
*访问                     *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
******************************* <-- struct thread的td_frame成员指向这里
*sizeof(void *)             *         
*                           *         
******************************* <--pcb_esp指向这里         
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*                           *
*******************************------ 内核栈底部 低地址方向----将和oldtd相关联的struct pcb对象记为oldtd_pcb。
将和newtd相关联的struct pcb对象记为newtd_pcb。

将和oldtd相关联的struct pmap对象记为oldtd_pmap。
将和newtd相关联的struct pmap对象记为newtd_pmap。

将和oldtd相关联的struct proc对象记为oldtd_proc。
将和newtd相关联的struct proc对象记为newtd_proc。

将和oldtd相关联的struct thread对象记为oldtd_thread。
将和newtd相关联的struct thread对象记为newtd_thread。

将和oldtd相关联的struct vmspace对象记为oldtd_vmspace。
将和newtd相关联的struct vmspace对象记为newtd_vmspace。

运行oldtd时,cr3寄存器中的值为oldtd_cr3.
运行newtd时,cr3寄存器中的值为newtd_cr3.

和oldtd相关联的td_lock为oldtd_td_lock。
和newtd相关联的td_lock为newtd_td_lock。

oldtd使用的struct pcb_ext对象记为oldtd_pcb_ext。
newtd使用的struct pcb_ext对象记为newtd_pcb_ext。

oldtd运行时EFLAGS寄存器记为oldtd_eflags.
newtd运行时EFLAGS寄存器记为newtd_eflags   104        /*
   105       * cpu_switch(old, new)
   106       *
   107       * Save the current thread state, then select the next thread to run
   108       * and load its state.
   109       * 0(%esp) = ret      cpu_switch函数的返回地址,被保存到oldtd_pcb的pcb_eip成员中
   110       * 4(%esp) = oldtd      &oldtd_thread
   111       * 8(%esp) = newtd      &newtd_thread
   112       * 12(%esp) = newlock   oldtd_td_lock
   113       */
   114        ENTRY(cpu_switch)
   115       
   116               
/*****************************************************
* Switch to new thread.First, save context.
                   117:ecx寄存器保存的是&oldtd_thread
**********************************/
   117                movl        4(%esp),%ecx
   118             
/******************************************************
   * 119-122:
                  默认情况下,没有enable INVARIANTS,忽略。
*******************************/
   119        #ifdef INVARIANTS
   120                testl        %ecx,%ecx                        /* no thread? */
   121                jz        badsw2                                /* no, panic */
   122        #endif
   123                /* 124:edx寄存器此时保存的是&oldtd_pcb */
   124                movl        TD_PCB(%ecx),%edx
   125                
/************************************************************
* 127-133:
                   保存oldtd_thread的硬件上下文,将其保存到oldtd_pcb中。
                   126:eax寄存器保存的是cpu_switch(return address),即ret。
                   127:更新oldtd_pcb的pcb_eip
***************************/
   126                movl        (%esp),%eax                        /* Hardware registers */
   127                movl        %eax,PCB_EIP(%edx)
   128                movl        %ebx,PCB_EBX(%edx)
   129                movl        %esp,PCB_ESP(%edx)
   130                movl        %ebp,PCB_EBP(%edx)
   131                movl        %esi,PCB_ESI(%edx)
   132                movl        %edi,PCB_EDI(%edx)
   133                mov        %gs,PCB_GS(%edx)
/***************************************************
* 134-135:将old_eflags保存到oldtd_pcb的pcb_psl
                            成员中。
************************/
   134                pushfl                                        /* PSL */
   135                popl        PCB_PSL(%edx)
   136               
/**********************************************************
* Test if debug registers should be saved.
                   #define PCB_DBREGS 0x02 process using debug registers

                   137-152:如果oldtd_thread使用了调试寄存器,那么此时
                            需要将oldtd_thread的调试寄存器上下文保存到
                            oldtd_pcb中相应的成员中。
*******************************/
   137                testl        $PCB_DBREGS,PCB_FLAGS(%edx)
   138                jz      1f                              /* no, skip over */
   139                movl    %dr7,%eax                     /* yes, do the save */
   140                movl    %eax,PCB_DR7(%edx)
   141                andl    $0x0000fc00, %eax               /* disable all watchpoints */
   142                movl    %eax,%dr7
   143                movl    %dr6,%eax
   144                movl    %eax,PCB_DR6(%edx)
   145                movl    %dr3,%eax
   146                movl    %eax,PCB_DR3(%edx)
   147                movl    %dr2,%eax
   148                movl    %eax,PCB_DR2(%edx)
   149                movl    %dr1,%eax
   150                movl    %eax,PCB_DR1(%edx)
   151                movl    %dr0,%eax
   152                movl    %eax,PCB_DR0(%edx)
   153        1:
   154             
/***************************************************************
* 155-163:
                  在编译了DEV_NPX选项时,检查是否保存浮点寄存器。
**************************/
   155        #ifdef DEV_NPX
   156                /* have we used fp, and need a save? */
   157                cmpl        %ecx,PCPU(FPCURTHREAD)
   158                jne        1f
   159                pushl        PCB_SAVEFPU(%edx)                /* h/w bugs make saving complicated */
   160                call        npxsave                                /* do it in a big C function */
   161                popl        %eax
   162        1:
   163        #endif
   164       
   165               
/**************************************************************
   * Save is done.Now fire up new thread. Leave old vmspace.
                   执行到这里的话,oldtd_thread的硬件上下文已经保存完毕。

                   166:edi寄存器保存的是&oldtd_thread。
                   167:ecx寄存器保存的是&newtd_thread。
                   168:esi寄存器保存的是oldtd_td_lock。
*******************************/
   166                movl        4(%esp),%edi
   167                movl        8(%esp),%ecx                        /* New thread */
   168                movl        12(%esp),%esi                        /* New lock */
                /* 169-171:同上 */
   169        #ifdef INVARIANTS
   170                testl        %ecx,%ecx                        /* no thread? */
   171                jz        badsw3                                /* no, panic */
   172        #endif
                /* edx寄存器此时保存的是&newtd_pcb */
   173                movl        TD_PCB(%ecx),%edx
   174       
   175               
/**************************************************************
* switch address space
                   176:eax寄存器中保存的是newtd_cr3。
                   177-181:是否运行在内核地址空间?典型的是newtd_thread是一个
                            内核线程,比如idle,pagedaemon等内核线程,
                            如果是,就跳转到sw0处。
********************************/
   176                movl        PCB_CR3(%edx),%eax
   177        #ifdef PAE
   178                cmpl        %eax,IdlePDPT                        /* Kernel address space? */
   179        #else
   180                cmpl        %eax,IdlePTD                        /* Kernel address space? */
   181        #endif
   182                je        sw0
/************************************************************
* 如果执行到这里,就表示newtd_thread不是一个内核线程。

                   183-185:
                   此时要检查newtd_thread和oldtd_thread是否共享一个地址空间,
                   典型的是用RFMEM标志调用rfork函数创建的thread。
************************************/
   183                READ_CR3(%ebx)                                /* The same address space? */
   184                cmpl        %ebx,%eax
   185                je        sw0
/***********************************************************
   * 如果执行到这里的话,就表示要安装一个单独的地址空间,即
                   newtd_thread的地址空间。
            
                   #define LOAD_CR3(reg)   movl reg,%cr3;

                   186:重新加载CR3控制寄存器,这个步骤执行后,就开始在
                        newtd_thread的地址空间上操作。

                   187:eax寄存器中保存的是oldtd_td_lock。
                   188:esi寄存器中保存的当前cpu的logicl id。
                   189:交换oldtd_td_lock和newtd_td_lock
********************/
   186                LOAD_CR3(%eax)                                /* new address space */
   187                movl        %esi,%eax
   188                movl        PCPU(CPUID),%esi
   189                SETOP        %eax,TD_LOCK(%edi)                /* Switchout td_lock */
   190       
   191               
/*********************************************************
* Release bit from old pmap->pm_active
                   192:ebx寄存器保存的是&oldtd_pmap
                   196:清除struct pcpu对象的pm_active成员的相应bit位。
                        pm_active:一个cpu的位图,假设cpu的logical id为2,
                        那么如果__pcpu的pc_curpmap成员指向当前的
                        struct pmap对象,那么pm_active的bit2就被设置为1,
            否则相应的bit位为0.
*************************/
   192        movl        PCPU(CURPMAP), %ebx
   193        #ifdef SMP
   194                lock
   195        #endif
   196                btrl        %esi, PM_ACTIVE(%ebx)                /* clear old */
   197       
/**********************************************************
   * Set bit in new pmap->pm_active

                           199:eax寄存器此时保存的是&newtd_proc。
                   200:ebx寄存器此时保存的是&newtd_vmspace。
                   201:ebx寄存器此时保存的是&newtd_pmap。
                   202:更新当前cpu对应的struct pcpu对象的pc_curpmap成员。
                   206:设置pc_curpmap成员的相应bit位。
***************************/
   199                movl        TD_PROC(%ecx),%eax                /* newproc */
   200                movl        P_VMSPACE(%eax), %ebx
   201                addl        $VM_PMAP, %ebx
   202                movl        %ebx, PCPU(CURPMAP)
   203        #ifdef SMP
   204                lock
   205        #endif
   206                btsl        %esi, PM_ACTIVE(%ebx)                /* set new */
   207                jmp        sw1
   208       
   209        sw0:
   210                SETOP        %esi,TD_LOCK(%edi)                /* Switchout td_lock */
   211        sw1:
   212                BLOCK_SPIN(%ecx)
                /* 213-224:支持XEN时,忽略 */
   213        #ifdef XEN
   214                pushl        %eax
   215                pushl        %ecx
   216                pushl        %edx
   217                call        xen_handle_thread_switch
   218                popl        %edx
   219                popl        %ecx
   220                popl        %eax
   221                /*
   222               * XXX set IOPL
   223               */
   224        #else               
/************************************************************************
* At this point, we've switched address spaces and are ready
* to load up the rest of the next context.
   
                   关于tss的说明,系统中至少要定义一个tss,关于tss的说明请参考
                   intel IA-32中的描述,在freebsd中,为每一个cpu定义一个tss,在该
                   cpu上运行的所有thread共用这一个tss,这个tss就为相应struct pcpu
                   对象的pc_common_tss成员描述的tss,在初始化函数init386中,会用描述
                   该tss的Tss Descriptor对应的Segment Selector加载TR,tss中一个重要
                   的字段就是esp0字段,esp0始终指向当前运行的thread的内核栈的栈顶,
                   在每次线程切换时,都会更新esp0,以使其指向相应thread的内核栈的栈顶。

                   如果thread使用自己私有的tss,那么就要使用struct pcb_ext类型的数据对象
                   来描述。

                   此时edx寄存器保存的是&newtd_pcb。

                   228:
                   如果newtd_pcb的pcb_ext成员非空,就表示newtd_thread使用了私有
                   的tss(任务状态段)。
*************************************/
   229                cmpl        $0, PCB_EXT(%edx)                /* has pcb extension? */
   230                je        1f                                /* If not, use the default */
/****************************************************************
* 如果执行到这里,就表示newtd_thread使用了私有的tss。
                   231:将当前cpu对应的struct pcpu对象的pc_private_tss成员设置
                        为1,表示当前正在运行的thread使用自己私有的tss。
                   232:edi寄存器此时保存的是&newtd_pcb_ext。
*************************/
   231                movl        $1, PCPU(PRIVATE_TSS)                 /* mark use of private tss */
   232                movl        PCB_EXT(%edx), %edi                /* new tss descriptor */
   233                jmp        2f                                /* Load it up */
   234       
   235        1:       
/************************************************************************
* Use the common default TSS instead of our own.
* Set our stack pointer into the TSS, it's set to just
* below the PCB.In C, common_tss.tss_esp0 = &pcb - 16;
                   如果执行到这里,就表示newtd_thread使用默认的tss。
                   240:ebx寄存器此时保存的是newtd_thread内核栈的栈顶位置。
                   241:更新默认tss的esp0成员,使其指向将要运行thread的内核栈
                        的栈顶,这里就为newtd_thread的内核栈的栈顶。

                   从上面可知,edx指向newtd_pcb,而newtd_pcb对象保存在newtd_thread的
                   内核栈的顶部,而当cpu陷入内核时,cpu将用TSS描述符中esp0字段的
                   值加载esp寄存器,即线程的内核栈的栈顶,240-241执行完后:

                   ******************************* 内核栈顶部 高地址方向
                   *                           *
                   *   struct pcb对象            *
                   *                           *
                   *                           *
                   ******************************* <--struct thread的td_pcb成员指向这里
                   *                           *
                   *   16bytes for vm            *
                   ******************************* <--- PCPU(COMMON_TSS) + TSS_ESP0                     
                   *                           *
                   *                           *      
*************************************************/
   240                leal        -16(%edx), %ebx                        /* leave space for vm86 */
   241                movl        %ebx, PCPU(COMMON_TSS) + TSS_ESP0
   242       
/***********************************************************
* Test this CPU'sbit in the bitmap to see if this
* CPU was using a private TSS.

       247:检查oldtd_thread是否使用了私有的tss。
         cmpl结果非零,oldtd_thread使用了私有的tss。
         cmpl结果为零,oldtd_thread使用了默认的tss。
***************************************/
   247                cmpl        $0, PCPU(PRIVATE_TSS)                /* Already using the common? */
   248                je        3f                                /* if so, skip reloading */
/**********************************************************************
* 如果执行到这里的话,就表示newtd_thread使用的是默认的tss,
   但是oldtd_thread使用的私有的tss,此时将当前cpu对应的
   struct pcpu对象的pc_private_tss成员设置为0,因为newtd_thread
    使用默认的tss。
***************************/
   249                movl        $0, PCPU(PRIVATE_TSS)
   250                PCPU_ADDR(COMMON_TSSD, %edi)
   251        2:
/*********************************************************************
* Move correct tss descriptor into GDT slot, then reload tr.
                  
                   253-259:
                   如果执行到这里,包括下面两种情况:
                   1:newtd_thread使用了私有的tss,上面233行的jmp指令直接跳转到这里。
                   2:newtd_thread使用的是默认的tss,但是oldtd_thread使用的私有的tss。

                   不管是上面哪一种情况,此时都要重新加载tr。

************************/
   253                movl        PCPU(TSS_GDT), %ebx                /* entry in GDT */
   254                movl        0(%edi), %eax
   255                movl        4(%edi), %esi
   256                movl        %eax, 0(%ebx)
   257                movl        %esi, 4(%ebx)
   258                movl        $GPROC0_SEL*8, %esi                /* GSEL(GPROC0_SEL, SEL_KPL) */
   259                ltr        %si
   260        3:
   261       
/*************************************************************
   *Copy the %fs and %gs selectors into this pcpu gdt
                  在函数init386中,会将每个cpu对应的struct pcpu对象
                  的pc_fsgs_gdt成员设置为相应GDT中fs Descriptor for user
                  的描述符的地址,因为fs和gs相邻,所以通过
                  pc_fsgs_gdt成员也可以访问%gs Descriptor for user。
                  
                  263-272:
                  使用newtd_pcb对象中pcb_fsd和pcb_gsd两个成员的值重新
                  加载:
                  GDT中%fs Descriptor for user
                  GDT中%gs Descriptor for user
******************************/
   263                leal        PCB_FSD(%edx), %esi
   264                movl        PCPU(FSGS_GDT), %edi
   265                movl        0(%esi), %eax                /* %fs selector */
   266                movl        4(%esi), %ebx
   267                movl        %eax, 0(%edi)
   268                movl        %ebx, 4(%edi)
   269                movl        8(%esi), %eax                /* %gs selector, comes straight after */
   270                movl        12(%esi), %ebx
   271                movl        %eax, 8(%edi)
   272                movl        %ebx, 12(%edi)
   273        #endif
/*********************************************************************************
* Restore context.
                   开始恢复newtd_thread的硬件上下文。
               
                   276:执行完后就开始在newtd的内核栈上操作。

                   280-281:
                   修改保存在newtd_thread内核栈上的返回地址:

                   对于已经运行了一段时间的thread,返回地址为cpu_switch(return address),
                   此时newtd_thread就开始从cpu_switch函数后面的代码开始执行。

                   对于刚创建的thread,返回地址为fork_trampoline函数的地址,此时newtd_thread
                   就开始执行fork_trampoline函数,在这种情况下,esi和ebx寄存器的值分下面几种
                   情况:
                   1:对于刚创建的thread:
                      esi:fork_return函数的地址。
                      ebx:child_thread对应的struct thread对象的地址。

                   2:对于创建的idle线程:
                      esi:sched_idletd函数的地址。
                      ebx:NULL。

                   3:对于1号线程:
                      esi:start_init函数的地址。
                      ebx:NULL。

                   282-283:恢复newtd_thread的EFLAGS寄存器。
*****************************************************/
   275                movl        PCB_EBX(%edx),%ebx
   276                movl        PCB_ESP(%edx),%esp
   277                movl        PCB_EBP(%edx),%ebp
   278                movl        PCB_ESI(%edx),%esi
   279                movl        PCB_EDI(%edx),%edi
   280                movl        PCB_EIP(%edx),%eax
   281                movl        %eax,(%esp)
   282                pushl        PCB_PSL(%edx)
   283                popfl
/*******************************************************
* ecx寄存器此时保存的是&newtd_thread。
   edx寄存器此时保存的是&newtd_pcb。

   285:当前cpu对应的srtuct pcpu对象的pc_curpcb成员
         设置为&newtd_pcb。

   287:当前cpu对应的srtuct pcpu对象的pc_curthread成员
         设置为&newtd_thread;curthread宏的实现。
************/
   285                movl        %edx, PCPU(CURPCB)
   286                movl        TD_TID(%ecx),%eax
   287                movl        %ecx, PCPU(CURTHREAD)                /* into next thread */
   288       
/*********************************************************************
* Determine the LDT to use and load it if is the default one and
* that is not the current one.
    293-340:恢复LDT和调试寄存器,这里忽略。
*************/
   293                movl        TD_PROC(%ecx),%eax
   294                cmpl    $0,P_MD+MD_LDT(%eax)
   295                jnz        1f
   296                movl        _default_ldt,%eax
   297                cmpl        PCPU(CURRENTLDT),%eax
   298                je        2f
   299                LLDT(_default_ldt)
   300                movl        %eax,PCPU(CURRENTLDT)
   301                jmp        2f
   302        1:
   303                /* Load the LDT when it is not the default one. */
   304                pushl        %edx                                /* Preserve pointer to pcb. */
   305                addl        $P_MD,%eax                        /* Pointer to mdproc is arg. */
   306                pushl        %eax
   307                call        set_user_ldt
   308                addl        $4,%esp
   309                popl        %edx
   310        2:
   311       
   312                /* This must be done after loading the user LDT. */
   313                .globl        cpu_switch_load_gs
   314        cpu_switch_load_gs:
   315                mov        PCB_GS(%edx),%gs
   316       
   317                /* Test if debug registers should be restored. */
   318                testl        $PCB_DBREGS,PCB_FLAGS(%edx)
   319                jz      1f
   320       
   321                /*
   322               * Restore debug registers.The special code for dr7 is to
   323               * preserve the current values of its reserved bits.
   324               */
   325                movl    PCB_DR6(%edx),%eax
   326                movl    %eax,%dr6
   327                movl    PCB_DR3(%edx),%eax
   328                movl    %eax,%dr3
   329                movl    PCB_DR2(%edx),%eax
   330                movl    %eax,%dr2
   331                movl    PCB_DR1(%edx),%eax
   332                movl    %eax,%dr1
   333                movl    PCB_DR0(%edx),%eax
   334                movl    %eax,%dr0
   335                movl        %dr7,%eax
   336                andl    $0x0000fc00,%eax
   337                movl    PCB_DR7(%edx),%ecx
   338                andl        $~0x0000fc00,%ecx
   339                orl   %ecx,%eax
   340                movl    %eax,%dr7
   341        1:      /* 执行一个ret指令,返回啦 */
   342                ret
页: [1]
查看完整版本: freebsd9.2-线程切换阶段02-硬件上下文的切换-cpu_switch函数