首页 > 技术文章 > 理解进程调度时机跟踪分析进程调度与进程切换的过程

chuishi 2016-04-17 14:01 原文

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

20135224陈实 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

第一部分---调度时机

发生方式:

1 中断处理过程(时钟中断、I/O中断、系统调用和异常)中或返回用户态根据need_resched标记调用

2 内核线程直接调用进行进程切换,也可以在中断处理过程中进行调度,内核线程可以主动调度,也可以被动调度

3 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,在中断处理过程中调度

调度中介:schedule()函数

1 schedule()函数开始,禁用内核抢占并初始化一些局部变量,此时,schedule()检查运行队列中剩余的可运行进程数,如果有可调用进程,开始调用。

schedule()函数中在switch_to宏之后紧接着的指令并不由next进程立即执行,而是稍后当调度程序选择prev又执行时由prev执行。 prev局部变量并不指向开始描述schedule()时所替换出去的原来的那个进程,而是指向prev被调度时由prev替换出的原来那个进程。

3 紧接着context_switch()函数调用之后,宏barrier()产生一个代码优化屏障。执行finish_task_switch()函数。

4 schedule()函数最后执行的是:重新获得大内核锁,重新启动内核抢占,并检查是否其他进程设置了当前进程的TIF_RESCHED标志,如果是,整个schedule()函数重新开始执行

swtich_to关键代码

"movl $1f,%[prev_ip]\n\t"保存当前进程的eip,恢复的时候从prev_ip来恢复eip

"pushl %[next_ip]\n\t"把下个进程的起点ip位置压到next进程堆栈栈顶(起点)

"jmp __switch_to\n" jmp通过prevnext寄存器的方式传递参数

"popl %%ebp\n\t"恢复上下文,next曾经pushebp

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 继续运行用户态进程

 

推荐阅读