上次主要讲了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().