Linux 进程管理之进程调度与切换( 二 )


Linux 进程管理之进程调度与切换

文章插图
系统中所有的运行队列都在 runqueues 数组中,该数组的每个元素分别对应于系统中的一个 CPU 。在单处理器系统中,由于只需要一个就绪队列,因此数组只有一个元素 。
static DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);内核也定义了一下便利的宏,其含义很明显 。
#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu)))#define this_rq() (&__get_cpu_var(runqueues))#define task_rq(p) cpu_rq(task_cpu(p))#define cpu_curr(cpu) (cpu_rq(cpu)->curr)进程调度与切换在分析调度流程之前,我们先来看在什么情况下要执行调度程序,我们把这种情况叫做调度时机 。
Linux 调度时机主要有 。
  1. 进程状态转换的时刻:进程终止、进程睡眠;
  2. 当前进程的时间片用完时(current->counter=0);
  3. 设备驱动程序;
  4. 进程从中断、异常及系统调用返回到用户态时 。
时机1,进程要调用 sleep() 或 exit() 等函数进行状态转换,这些函数会主动调用调度程序进行进程调度 。
时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4 是一样的 。
时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序 。在每次反复循环中,驱动程序都检查 need_resched 的值,如果必要,则调用调度程序 schedule() 主动放弃 CPU 。
时机4 ,如前所述,不管是从中断、异常还是系统调用返回,最终都调用 ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序 。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑 。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完 。
Linux 进程管理之进程调度与切换

文章插图
Linux 的调度程序是一个叫 Schedule() 的函数,这个函数来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等 。
Schedule 的实现asmlinkage void __sched schedule(void){/*prev 表示调度之前的进程, next 表示调度之后的进程 */struct task_struct *prev, *next;long *switch_count;struct rq *rq;int cpu;need_resched:preempt_disable(); //关闭内核抢占cpu = smp_processor_id(); //获取所在的cpurq = cpu_rq(cpu); //获取cpu对应的运行队列rcu_qsctr_inc(cpu);prev = rq->curr; /*让prev 成为当前进程 */switch_count = &prev->nivcsw;/释放全局内核锁,并开this_cpu 的中断/release_kernel_lock(prev);need_resched_nonpreemptible:__update_rq_clock(rq); //更新运行队列的时钟值...if (unlikely(!rq->nr_running))idle_balance(cpu, rq);// 对应到CFS,则为 put_prev_task_fairprev->sched_class->put_prev_task(rq, prev); //通知调度器类当前运行进程要被另一个进程取代/pick_next_task以优先级从高到底依次检查每个调度类,从最高优先级的调度类中选择最高优先级的进程作为下一个应执行进程(若其余都睡眠,则只有当前进程可运行,就跳过下面了)/next = pick_next_task(rq, prev); //选择需要进行切换的task//进程prev和进程next切换前更新各自的sched_infosched_info_switch(prev, next);if (likely(prev != next)) {rq->nr_switches++;rq->curr = next;++switch_count;//完成进程切换(上下文切换)context_switch(rq, prev, next); / unlocks the rq */} elsespin_unlock_irq(&rq->lock);if (unlikely(reacquire_kernel_lock(current) < 0)) {cpu = smp_processor_id();rq = cpu_rq(cpu);goto need_resched_nonpreemptible;}preempt_enable_no_resched();if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))goto need_resched;}从代码分析来看,Schedule 主要完成了2个功能:
  • pick_next_task 以优先级从高到底依次检查每个调度类,从最高优先级的调度类中选择最高优先级的进程作为下一个应执行进程 。
  • context_switch 完成进程的上下文切换 。
进程上下文切换static inline voidcontext_switch(struct rq *rq, struct task_struct *prev, 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 字段//mm为空,说明是一个内核线程if (unlikely(!mm)) {//内核线程共享上一个运行进程的mmnext->active_mm = oldmm; //借用切换出去进程的 mm_struct//增加引用计数atomic_inc(&oldmm->mm_count);//惰性TLB,因为内核线程没有虚拟地址空间的用户空间部分,告诉底层体系结构无须切换enter_lazy_tlb(oldmm, next);} else//进程切换包括进程的执行环境切换和运行空间的切换 。运行空间的切换是有switch_mm完成的 。若是用户进程,则切换运行空间switch_mm(oldmm, mm, next);//若上一个运行进程是内核线程if (unlikely(!prev->mm)) {prev->active_mm = NULL; //断开内核线程与之前借用的地址空间联系//更新运行队列的prev_mm成员,为之后归还借用的mm_struct做准备rq->prev_mm = oldmm;}/* Here we just switch the register state and the stack. *///切换进程的执行环境switch_to(prev, next, prev);barrier();//进程切换之后的处理工作finish_task_switch(this_rq(), prev);}


推荐阅读