我今天分析一下进程的上下文切换,也就是进程调度时,怎么由当前进程切换到另一个进程的。
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\n\t" /* save flags */ \
"pushl %%ebp\n\t" /* save EBP */ \
"movl %%esp,%[prev_sp]\n\t" /* save ESP */ \ ——到这里将堆栈老进程堆栈保存起来了
"movl %[next_sp],%%esp\n\t" /* restore ESP */ \ ——切换到新进程堆栈
"movl $1f,%[prev_ip]\n\t" /* save EIP */ \
"pushl %[next_ip]\n\t" /* restore EIP */ \ ——准备eip
__switch_canary \
"jmp __switch_to\n" /* regparm call */ \ ——新进程的eip 写入eip寄存器,下面就正式切换到新进程了 ,这个为啥jmp __switch_to,不用call?
"1:\t" \
"popl %%ebp\n\t" /* restore EBP */ \
"popfl\n" /* 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不就有问题了