linux进程管理 - 核心调度器

来源:互联网 发布:阿里妈妈淘宝客采集器 编辑:程序博客网 时间:2024/05/21 06:15

1966     /* Here we just switch the register state and the stack. */1967     switch_to(prev, next, prev);1968 1969     barrier();

linux内核进程调度器基于两个函数:周期性调度器函数和主调度器函数.

周期性调度器

所谓周期性调度器就是scheduler_tick中实现。如果系统正在活动中,内核会按照HZ自动调用这个函数。实际上在每个滴答的handler中会调用这个函数。如果在没有进程等待调度,那么在计算机电力供应不足的情况下,也可以关闭该调度器以减少电能消耗。该函数会激活负责当前进程的调度类的周期性调度方法。

        if (curr != rq->idle) /* FIXME: needed? */                curr->sched_class->task_tick(rq, curr);

由于调度器的模块化结构,调度器本身实现比较简单,因为主要的工作完全可以委托给特定调度器类的方法。

task_tick的实现完全依赖于底层的调度器类。例如,CFS调度器类会在方法中检测进程是否已经运行太长时间,以避免过长的延迟。如果需要调度,那么会调用set_tsk_need_resched函数来设置TIF_NEED_RESCHED标志,以表示该请求。


主调度器

在内核的许多地方,如果需要将CPU分配给与当前活动进程不同的另一个进程,都会直接调用主调度器函数schedule。在从系统调用返回后,内核会检查当前进程是否设置了重新调度标志TIF_NEED_RESCHED标志(例如上面提到的 scheduler_tick就可能会设置这个标志),如果设置了,则调用schedule函数。

我们现在看一下schedule的实现

3616 /*3617  * schedule() is the main scheduler function.3618  */3619 asmlinkage void __sched schedule(void)3620 {3621         struct task_struct *prev, *next;3622         long *switch_count;3623         struct rq *rq;3624         int cpu;3625 3626 need_resched:3627         preempt_disable();3628         cpu = smp_processor_id();3629         rq = cpu_rq(cpu);3630         rcu_qsctr_inc(cpu);3631         prev = rq->curr;3632         switch_count = &prev->nivcsw;

3630 先获得当前正在运行的进程,保存在prev中。

3639         /*3640          * Do the rq-clock update outside the rq lock:3641          */3642         local_irq_disable();3643         __update_rq_clock(rq);3644         spin_lock(&rq->lock);3645         clear_tsk_need_resched(prev);

3643 更新rq->prev_clock_raw和rq->clock

3645 清除TIF_NEED_SCHED标志

3647         if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {3648                 if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&3649                                 unlikely(signal_pending(prev)))) {3650                         prev->state = TASK_RUNNING;3651                 } else {3652                         deactivate_task(rq, prev, 1);3653                 }3654                 switch_count = &prev->nvcsw;3655         }

如果当前进程处在可中断睡眠状态,那么必须再次提升为运行进程。否则调用deactivate_task使得进程停止活动

3660         prev->sched_class->put_prev_task(rq, prev);3661         next = pick_next_task(rq, prev);

调用调度器类的put_prev_task通知调度器类当前的进程要被另外一个进程取代。这个操作并不意味着prev从就绪队列移除,而是提供了一个时机,执行一些记账工作。

pick_next_task选择下一个应该执行的进程。注意新选择的进程有可能就是原来的进程,比如就绪队列中仅有一个进程的情况。

3665         if (likely(prev != next)) {3666                 rq->nr_switches++;3667                 rq->curr = next;3668                 ++*switch_count;3669 3670                 context_switch(rq, prev, next); /* unlocks the rq */3671         } else3672                 spin_unlock_irq(&rq->lock);

如果新进程不等于旧的进程,那么我们调用context_switch进行进程上下文的切换。


上下文切换

1929 static inline void1930 context_switch(struct rq *rq, struct task_struct *prev,1931            struct task_struct *next)1932 {...............          1945     if (unlikely(!mm)) {1946         next->active_mm = oldmm;1947         atomic_inc(&oldmm->mm_count);1948         enter_lazy_tlb(oldmm, next);1949     } else1950         switch_mm(oldmm, mm, next);

switch_mm是一个体系结构特定的函数,前换页全局目录以安装一个新的地址空间。对于arm平台来说,就是设置CP15协处理器TTB寄存器为新的pgd;对于X86来说,则是设置CR3寄存器为新的pgd。有人会问,这里切换了新的pgd,那么代码执行会不会不连续了? 没关系,因为现在是内核空间,内核地址空间的页面映射不会随着pgd而改变。

1966     /* Here we just switch the register state and the stack. */1967     switch_to(prev, next, prev);1968 1969     barrier();1970     /*1971      * this_rq must be evaluated again because prev may have moved1972      * CPUs since it called schedule(), thus the 'rq' on its stack1973      * frame will be invalid.1974      */1975     finish_task_switch(this_rq(), prev);1976 } 

进程切换的硬件上下文是共享的CPU 寄存器,进程在切换时,硬件上下文保存在task_struct->thread字段中,注意thread是体系结构特定的,所以不是每个体系结构都需要保存寄存器到这个结构中的。swtich之后的代码,只有在当前进程下一次被选择执行时才会执行。

barrier确保switch_to和后面的finish_task_switch不会乱序执行。






原创粉丝点击