我今天分析一下进程的上下文切换,也就是进程调度时,怎么由当前进程切换到另一个进程的。
1、概述
进程调度的时机,也就是进程是在啥时候切换,触发因数是什么。
中断发生时,进入中断处理中断服务程序——比如我们前面讲的系统调用,会直接调用schedule(),或者返回用户态时根据need_resched标志调用schedule
内核线程可以直接调用schedule(),从而主动调度。
用户态进程无法调用到内存函数schedule,所以他是无法进行主动调度的,他只能由于某些原因导致陷入内核态时才会被调度——比如中断。
2、进程调度schedule()函数的跟踪
如上图,schedule——>__schedule——>pick_next_task().,pick_next_task这个函数封装了进程调度算法,他返回下一个需要调度的进程。
,
当找到需要调度的函数后,通过调用 context_switch——>switch_to进行切换。下面我们看下switch_to这个汇编宏
asm volatile("pushfl
" /* save flags */
"pushl %%ebp
" /* save EBP */
"movl %%esp,%[prev_sp]
" /* save ESP */ ——到这里将堆栈老进程堆栈保存起来了
"movl %[next_sp],%%esp
" /* restore ESP */ ——切换到新进程堆栈
"movl $1f,%[prev_ip]
" /* save EIP */
"pushl %[next_ip]
" /* restore EIP */ ——准备eip
__switch_canary
"jmp __switch_to
" /* regparm call */ ——新进程的eip 写入eip寄存器,下面就正式切换到新进程了 ,这个为啥jmp __switch_to,不用call?
"1: "
"popl %%ebp
" /* restore EBP */
"popfl
" /* restore flags */
/* output parameters */
: [prev_sp] "=m" (prev->thread.sp),
[prev_ip] "=m" (prev->thread.ip),
"=a" (last),
/* clobbered output registers: */
"=b" (ebx), "=c" (ecx), "=d" (edx),
"=S" (esi), "=D" (edi)
__switch_canary_oparam
/* input parameters: */
: [next_sp] "m" (next->thread.sp),
[next_ip] "m" (next->thread.ip),
/* regparm parameters for __switch_to(): */
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
: /* reloaded segment registers */
"memory");
} while (0)
3、jmp __switch_to
jmp是不会讲下一条指令push到堆栈中的,这样的话他返回时就返回的是nexp_ip。 如果用call,就会将下一条指令1: 压栈,这样返回时就一定会返回到这里。
如果新的进程是刚创建的,他的next_ip不是1:, 而是 ret_from_fork,那么如果用call不就有问题了