理解进程调度时机跟踪分析进程调度与进程切换的过程
理解进程调度时机跟踪分析进程调度与进程切换的过程

依旧是老的一套clone git的最新版本
cd LinuxKernel
rm -rf menu
git clone https://github.com/mengning/menu.git
make rootfs
关闭窗口大小S 开启追踪gdb
file linux-3.18.6/vmlinux
target remote:1234
设置断点 schedule() switch_to context_switch pick_next_task

继续执行追踪 到pick_next_task(进程调度算法都封装这个函数内)

跟踪到context_switch 进行 进程上下文切换


schedule 关键代码分析
- if (task_on_rq_queued(prev) || rq->skip_clock_update < 0)
- update_rq_clock(rq);
- next = pick_next_task(rq, prev);
- clear_tsk_need_resched(prev);
- clear_preempt_need_resched();
- rq->skip_clock_update = 0;
- if (likely(prev != next)) {
- rq->nr_switches++;
- rq->curr = next;
- ++*switch_count;
- context_switch(rq, prev, next); /* unlocks the rq */
- /*
- * The context switch have flipped the stack from under us
- * and restored the local variables which were saved when
- * this task called schedule() in the past. prev == current
- * is still correct, but it can be moved to another cpu/rq.
- */
- cpu = smp_processor_id();
- rq = cpu_rq(cpu);
- } else
- raw_spin_unlock_irq(&rq->lock);
- post_schedule(rq);
linux调度的核心函数为schedule,schedule函数封装了内核调度的框架。细节实现上调用具体的调度类中的函数实现。schedule函数主要流程为:将当前进程从相应的运行队列中删除,计算和更新调度实体和进程的相关调度信息;将当前进重新插入到调度运行队列中,根据具体的运行时间进行插入而对于实时调度插入到对应优先级队列的队尾,从运行队列中选择运行的下一个进程,进程调度信息和上下文切换。当进程上下文切换后,调度就基本上完成了,当前运行的进程就是切换过来的进程了。
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 */ \
- __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");
当切换进程已经选好后,就开始用户虚拟空间的处理,然后就是进程的切换switch_to()。所谓进程的切换主要就是堆栈的切换,这是由宏操作switch_to()完成的。这里的输出部分有三个参数,表示这段程序执行后有三项数据会有改变。其中[prev_sp]、[prev_ip] 都在内存中分别为prev->thread.sp、prev->thread.ip,而最后一个参数则与寄存器EAX结合,对应于参数中的last。而输入部则有4个参数,其中[next_sp]、[next_ip]在内存中,分别为next->thread.sp 与next->thread.ip,剩余的两个参数则与寄存器EAX,EDX结合,分别对应prev,next。
总结:
一次一般的进程切换过程,其中必须完成的关键操作是:切换地址空间、切换内核堆栈、切换内核控制流程,加上一些必要的寄存器保存和恢复。这里,除去地址空间的切换,其他操作要强调“内核”一词。这是因为,这些操作并非针对用户代码,切换完成后,也没有立即跑到next的用户空间中执行。用户上下文的保存和恢复是通过中断和异常机制,在内核态和用户态相互切换时才发生的。schedule()是内核和其他部分用于调用进程调度器的入口,选择哪个进程可以运行,何时将其投入运行。就如switch_to中的方法,通过压栈出栈交换prev_ip和next_ip。然后返回,从而完成进程调度。而用哪个作为下来的进程,则通过优先级的算法和进程调度算法来决定。
作者:李嘉
原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

浙公网安备 33010602011771号