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]