上次主要讲了 CFS 调度的基本原理,并且没有分析有唤醒和进程迁移时候的调度流程,所以本文主要 从内核中几个重要的调度点来详细的分析一下调度的基本流程,主要以流程图的形式给出。
内核中主要有以下几个重要的切入点 :
(1)tick 相关,即时钟中断
这就是上篇文章中讲的每次中断中,更 新 vruntime 的整个过程,可以理解为是在中断的上半部分做的,很显然我们会想到前一篇文章中 讲到的检查 TIF_NEED_SCHED 位并显示调用 schedule() 的地方就是中断的下半部分了,为了更好的理解我把中断的整个处理流程也做了个流程图 ( 以时钟中断为例 ) :
( 这些流程图很大的,如果看不清可以下载下来放大看,应该相当清晰 )
与中断相关的部分,我会在以后的文章里详细介绍,在这里就不具体接讲解了。
现在来总结一下这个切入点的整个过程:
当来一个时钟中断的时候,概括来说内核做了两个操作 ( 参见上图中 ) :
(1)do_timer(): 它主要来更新系统的时间
(2)update_progress_times(): 一方面去执行我们上面谈到的 scheduler_tick() ,另一方面他又去触发一个软中断
执行完上面几个步骤后,时钟中断就要返回了,因为其他的任务留给软中断做了。具体怎么做我会在中断一部分在分析。我们来看看中断返回时的一段 代码:
call do_IRQ
jmp ret_from_intr # 执行完 do_IRQ 后跳到 ret_from_intr
ret_from_intr:
..............
cmpl $USER_RPL, %eax # 判断返回用户态还是内核态
jb resume_kernel # not returning to v8086 or userspace
ENTRY(resume_userspace)
..............
jne work_pending
jmp restore_all
ENTRY(resume_kernel)
..........
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ?
testb $_TIF_NEED_RESCHED, %cl # 检查 TIF_NEED_SCHED 有没有被置位
jz restore_all
testl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?
jz restore_all
call preempt_schedule_irq # 在这里显示调用 了 schedule()
jmp need_resched
到这里我们已经跟踪了在时钟中断里面的一个完整的调度过程。
(2) 当前进程主动放弃 CPU
既然是主动放弃,直接主动调用 schedule() 就行了,内核里面的很多地方都是这么做的,只要搜一下 schedule 的调用点就会看到。下面是从 schedule() 开始的一个调用
流程图:
总结一下执行的流程:
(1) 清除 TIF_NEED_SCHED
(2) 判断一下当前进程应该被继续设置为 TASK_RUNNING 状态,如果不是的话就把它从运行队列中删除
(3) 看一下当前运行队列是否还有可运行进 程,没有的话,调用 idle_balance(cpu, rq) 平衡负载
(4)把当前进程放回运行队列,并设置当前 进程的指针为空。
(5)选择下一个进程作为当前进程
(6)判断如果当前进程不等于选择的进程的 话,就进行进程切换,执行swtich_to,这部分将在下次讲解。
先说这么多,因为有很多细节我也不怎么理解,下面提出几个问题,希望能和大家一块讨论:
(1)schedule()函数中的这段代码及其展开
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)){
/*prev->state >0 即当前进程处在stopped状态下,而且可抢占的情况下*/
if (unlikely(signal_pending_state(prev->state, prev)))
prev->state = TASK_RUNNING;/**/
else
deactivate_task(rq, prev, 1) ;/*从红黑树中删除prev*/
switch_count = &prev->nvcsw;
这部分的意思是如果当前进程还需要呆在运行队列中就设置其prev->state = TASK_RUNNING,否则把它从
红黑树中删除。
问题一:继续留在运行队列的那个判断条件具体是什么意思?
if (unlikely(signal_pending_state(prev->state, prev)))
猜测应该 是,TASK_INTERRUPTIBLE这种状态时,可随时接受信号来继续运行。
问题二: 如果不留在运行队列就要从红黑树种删除,跟踪删除函数最后到了dequeue_entity()的这段代码
/*清除伙伴*/
clear_buddies(cfs_rq, se);
/*大部分情况下二者是相等的,不执行,
因为当前进程一直就不在运行队列中
问题的关键是什么情况下执行呢?*/
if (se != cfs_rq->curr)
__dequeue_entity(cfs_rq, se);
这个判断条件是什么意思?se不就是当前进程吗?什么情况下这个判断条件才能成立,最好能有个具体的例子。
我是这么想的,大部分情况下se = cfs_rq->curr,那么出队操作就不执行,因为当前进程是不在运行队列中的,这一点可以从新选择一个进程来运行的时候要先把它出列得到印 证,具体可见pick_next_task().