- 论坛徽章:
- 0
|
本帖最后由 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[2]的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's bit 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
复制代码 |
|