原文网址:
http://blog.chinaunix.net/uid-26817832-id-3146430.html
每个进程拥有一个内核栈,调度切换至一个新进程时,会根据task_struct->stack(struct thread_info 结构体类型) 的值设置 *kernelsp(当前正在运行进程之内核栈栈顶),将kernelsp的值设置为 task_struct->stack + THREAD_SIZE - 32(MIPS 下,使用set_saved_sp 宏来完成设置)。
内核栈主要用于进程运行时产生异常,将异常的现场保存在内核栈上(使用SAVE_ALL宏,SAVE_ALL宏包括SAVE_SOME宏)。
二. 异常产生时现场的保存
使用SAVE_SOME 保存上下文时,如发现从用户态切入核心态,则首先用 get_saved_sp 宏将*kernelsp 置入sp,也就是说:
若是用户态 --> 内核态,则 k0 = sp, sp = *kernelsp - PT_SIZE,store k0, PT_R29(sp),保存其它寄存器。
若是内核态 --> 内核态,直接 k0 = sp, sp = sp - PT_SIZE,store k0, PT_R29(sp),然后保存其它寄存器。
本质是内核栈上分配 PT_SIZE(=sizeof(struct pt_regs))大小的空间,作为上下文的保存空间。保存时所有数据精心组织,最后就是一个 struct pt_regs 结构。
三. 进程的切换过程
进程A --> 时钟中断 --> schedule --> switch_to(resume) --> schedule 返回 --> ret_from_irq --> 进程B
1、时钟中断后使用 SAVE_ALL 在内核栈上保存 $0, $2, $3, $4~$7, $8~$9(64bit), $25, $28, $29, $31, STATUS, CAUSE, EPC。
2、随后调用 switch_to,switch_to调用叶函数resume,完成两个工作:
1)、保存正在运行任务的上下文:
保存 STATUS,使用cpu_save_nonscratch 保存$16~$23, $29(sp), $30,以及 $31, 有FPU还要fpu_save_double 保存FPU的寄存器。所有都保存于thread_struct 结构中,该结构为 task_struct 的一部分。
这些保存的是 switch_to 前后的上下文
2)、将要运行的任务上下文加载:
$28 <---- &thread_info
cpu_restore_nonscratch 恢复 $16~$23, $29(sp), $30
*(kernelsp) <---- &thread_info + THREAD_SIZE - 32
恢复 thread_struct 中保存的 STATUS(bit 0, bit 8~15 用当前STATUS值替换)
新进程加载也是 switch_to 前后上下文。
3、do_IRQ 返回后,回到handle_int宏执行ref_from_irq,此时sp指向新进程的 pt_regs 结构(即*kernelsp - PT_SIZE + 压入栈中寄存器个数*每个寄存器所占字节数)。
ref_from_irq调用RESTORE_ALL从新进程的 pt_regs 结构中弹出新进程产生时钟中断的时候调用SAVE_ALL保存到pt_regs 结构中的值,并把这些寄存器的值放到cpu中,最后ref_from_irq调用eret返回用户态。