理解进程调度时机跟踪分析进程调度与进程切换的过程
20135224陈实 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
第一部分---调度时机
发生方式:
1 中断处理过程(时钟中断、I/O中断、系统调用和异常)中或返回用户态根据need_resched标记调用
2 内核线程直接调用进行进程切换,也可以在中断处理过程中进行调度,内核线程可以主动调度,也可以被动调度
3 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,在中断处理过程中调度
调度中介:schedule()函数
1 schedule()函数开始,禁用内核抢占并初始化一些局部变量,此时,schedule()检查运行队列中剩余的可运行进程数,如果有可调用进程,开始调用。
2 schedule()函数中在switch_to宏之后紧接着的指令并不由next进程立即执行,而是稍后当调度程序选择prev又执行时由prev执行。 prev局部变量并不指向开始描述schedule()时所替换出去的原来的那个进程,而是指向prev被调度时由prev替换出的原来那个进程。
3 紧接着context_switch()函数调用之后,宏barrier()产生一个代码优化屏障。执行finish_task_switch()函数。
4 schedule()函数最后执行的是:重新获得大内核锁,重新启动内核抢占,并检查是否其他进程设置了当前进程的TIF_RESCHED标志,如果是,整个schedule()函数重新开始执行
"movl $1f,%[prev_ip]\n\t"保存当前进程的eip,恢复的时候从prev_ip来恢复eip
"pushl %[next_ip]\n\t"把下个进程的起点ip位置压到next进程堆栈栈顶(起点)
"jmp __switch_to\n" jmp通过prev和next寄存器的方式传递参数
"popl %%ebp\n\t"恢复上下文,next曾经push过ebp
context_switch(struct rq *rq,struct task_struct *prev context_switch()上下文切换;建立next的地址空间。
struct task_struct *next)
{
struct mm_struct *mm, *oldmm;
prepare_task_switch(rq, prev, next);
mm= next -> mm; mm字段指向进程所拥有的内存描述符。
oldmm = prev -> active_mm; active_mm字段指向进程所使用的内存描述符。
arch_start_context_switch(prev);
if (!mm) {
next->active_mm = oldmm; 如果next是内核线程,则线程使用prev所使用的地址空间;
atomic_inc(&oldmm->mm_count); schedule()函数把该线程设置为懒惰TLB模式。
enter_lazy_tlb(oldmm,next);
}else
switch_mm(oldmm,mm,next); 如果next是普通进程,schedule()函数用next替换prev的地址空间
if (!prev->mm) {
prev->active_mm = NULL; 如果prev是内核线程或正退出的进程,context_switch()函数就把
rq->prev_mm = oldmm; 指向prev内存描述的支撑保存到运行队列的prev_mm字段中;
} 并且重新设置prev->active_mm。
spin_release(&rq->lock,dep_map,1,_THIS_IP_);
context_tracking_task_switch(prev,next); context_switch()可以调用switch_to执行prev和next之间的进程切换
finish_task_switch(this_rq(),prev);
}
第二部分:GDB跟踪
设置断点——分别schedule,context_switch,switch_to对应断点进行查看
对每个断点的里的switch函数进行单步分析,查看代码
总结:
1 用户需求的进程执行态
2 故意或者系统中断
3 SAVE_ALL
4 调用schedule()
5 执行到switch_to进程上下文切换
6 另一个进程进入并进行
7 restore_all
8 iret - pop cs:eip/ss:esp/eflag from kernel stack
9 继续运行用户态进程