在操作系统运行过程中,由于CPU bound和I/O bound,进行进程的调度自然是常事。进行进程调度时,操作系统使用某些特定算法(如FIFO、SCBF、轮转法等)在进程队列中选出一个进程作为下一个运行的进程,调用schedule。进行进程调用的时机有以下几种:
相对于可以高风亮节主动让出系统资源的内核态进程,用户态进程只能接受被动调度。而被动调度分为抢占式调度和强制调度。
schedule函数进行调度时,首先选择一个新的进程来运行,然后调用context_switch进行上下文的切换,这个宏又调用switch_to进行关键上下文的切换。
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
sched_submit_work(tsk);
__schedule();
}
下面来看一个最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程
下面是switch_to的代码:
#define switch_to(prev, next, last)
do {
/*
* Context-switching clobbers all registers, so we clobber
* them explicitly, via unused output variables.
* (EAX and EBP is not listed because EBP is saved/restored
* explicitly for wchan access and EAX is the return value of
* __switch_to())
*/
unsigned long ebx, ecx, edx, esi, edi;
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 */
__switch_canary
"jmp __switch_to\n" /* regparm 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)
注意switch_to是一个宏而不是一个函数,因此它的参数prev, next, last不是值拷贝,而是它的调用者context_switch()中的局部变量。而调用switch_to时,也并不是通过普通的call来实现,而是直接jmp到switch_to。
当然,进程调度后,当前的prev就变为了等待态,直到资源到位而转为就绪态,esp指针再次指回这个进程的堆栈时,这个进程又重新开始运行。
陈政/arc001 原创作品转载请注明出处 《Linux内核分析》MOOC课程