我今天分析一下进程的上下文切换,也就是进程调度时,怎么由当前进程切换到另外一个进程的。linux
一、概述算法
进程调度的时机,也就是进程是在啥时候切换,触发因数是什么。函数
中断发生时,进入中断处理中断服务程序——好比咱们前面讲的系统调用,会直接调用schedule(),或者返回用户态时根据need_resched标志调用scheduleurl
内核线程能够直接调用schedule(),从而主动调度。.net
用户态进程没法调用到内存函数schedule,因此他是没法进行主动调度的,他只能因为某些缘由致使陷入内核态时才会被调度——好比中断。线程
二、进程调度schedule()函数的跟踪3d
如上图,schedule——>__schedule——>pick_next_task().,pick_next_task这个函数封装了进程调度算法,他返回下一个须要调度的进程。rest
,
code
当找到须要调度的函数后,经过调用 context_switch——>switch_to进行切换。下面咱们看下switch_to这个汇编宏blog
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)
三、jmp __switch_to
jmp是不会讲下一条指令push到堆栈中的,这样的话他返回时就返回的是nexp_ip。 若是用call,就会将下一条指令1: 压栈,这样返回时就必定会返回到这里。
若是新的进程是刚建立的,他的next_ip不是1:, 而是 ret_from_fork,那么若是用call不就有问题了