linux学习15,内核是如何切换进程的
发表于: 2019-01-11 21:42:34 | 已被阅读: 34 | 分类于: Linux笔记
前面两节讨论了 linux 中进程的睡眠与唤醒机制,也介绍了 linux 内核的 cfs 进程调度方法,知道了哪些进程会被挂起,哪些进程会被投入运行。
不过,我们还不知道 linux 内核在进程调度时,是如何切换进程的的。例如,原本进程 A 正在睡眠,进程 B正在运行,现在要将 A 投入运行,将 B 设置为睡眠状态,这一过程是如何实现的呢?本节将讨论这个问题。
抢占和上下文切换
我们将进程运行时的资源(栈信息、寄存器信息等)称作“上下文”,这么一来,任务抢占就可看作是 linux 内核在切换上下文,上下文切换完毕时,内核也就完成了从一个可执行进程切换到另一个可执行进程。
“上下文”这个名字其实挺贴切的,内核在执行进程时,可以看做是内核在阅读一篇文章,如果有进程需要调度,就相当于内核换了一篇文章读。
那么,linux 内核是如何实现上下文切换的呢?这其实可以从进程调度获得入口,因为上下文切换一定发生在进程调度时,查看 schedule() 函数的 C语言代码:
4141 asmlinkage void __sched schedule(void)
- 4142 {
| 4143 struct task_struct *prev, *next;
| 4144 unsigned long *switch_count;
| 4145 struct rq *rq;
| 4146 int cpu;
| 4147
| 4148 need_resched:
| 4149 preempt_disable();
| 4150 cpu = smp_processor_id();
| 4151 rq = cpu_rq(cpu);
| 4152 rcu_qsctr_inc(cpu);
| 4153 prev = rq->curr;
| 4154 switch_count = &prev->nivcsw;
| 4155
| 4156 release_kernel_lock(prev);
....
|- 4190 if (likely(prev != next)) {
|| 4191 sched_info_switch(prev, next);
|| 4192
|| 4193 rq->nr_switches++;
|| 4194 rq->curr = next;
|| 4195 ++*switch_count;
|| 4196
|| 4197 context_switch(rq, prev, next); /* unlocks the rq */
...
2447 static inline void
2448 context_switch(struct rq *rq, struct task_struct *prev,
2449 struct task_struct *next)
- 2450 {
| 2451 struct mm_struct *mm, *oldmm;
| 2452
| 2453 prepare_task_switch(rq, prev, next);
| 2454 mm = next->mm;
| 2455 oldmm = prev->active_mm;
...
|- 2463 if (unlikely(!mm)) {
|| 2464 next->active_mm = oldmm;
|| 2465 atomic_inc(&oldmm->mm_count);
|| 2466 enter_lazy_tlb(oldmm, next);
|| 2467 } else
| 2468 switch_mm(oldmm, mm, next);
...
| 2484 /* Here we just switch the register state and the stack. */
| 2485 switch_to(prev, next, prev);
...
| 2493 finish_task_switch(this_rq(), prev);
| 2494 }
35 static inline void switch_mm(struct mm_struct *prev,
36 struct mm_struct *next,
37 struct task_struct *tsk)
- 38 {
| 39 int cpu = smp_processor_id();
| 40
|- 41 if (likely(prev != next)) {
...
|| 51 load_cr3(next->pgd);
|| 56 if (unlikely(prev->context.ldt != next->context.ldt))
|| 57 load_LDT_nolock(&next->context);
|| 58 }
...
| 73 }
进一步调度
现在已经清楚 linux 内核是如何切换进程的了。之前我们说过 linux 的进程是有优先级的概念的,高优先级的进程总是优先运行。假设某次调度后,进程 A 即将被投入运行,但是这时优先级比进程 A 更高的进程 B 也处于可运行状态了,linux 内核如何处理这种情况呢?
事实上,内核提供了 need_resched 标志位来表明是否需要重新执行一次调度。
那么,linux 内核什么时候重新调度才是安全的呢?只要进程没有持有锁,内核就可以抢占它。所以每个进程的 thread_info 结构体有一个 preempt_count 计数器,它的初始值为 0,每使用一次锁就加 1,释放一次锁就减 1,当该值为 0 的时候,linux 内核就能够抢占它。
linux 的实时调度策略
再啰嗦一下 linux 的两种实时调度策略:SCHED_FIFO 和 SCHED_RR。不特殊指定调度策略的进程一般都是 SCHED_NORMAL 策略。
SCHED_FIFO 调度策略不使用时间片,它使用先进先出的调度算法,处于可运行状态的 SCHED_FIFO 进程会比任何 SCHED_NORMAL 几的进程都先得到调度。一旦一个 SCHED_FIFO 级的进程处于可执行状态,就会一直运行,除非执行完毕或者它自己主动让出 cpu,否则就只有优先级更高的 SCHED_FIFO 和 SCHED_RR 级进程才能抢占它。
SCHED_RR 调度策略与 SCHED_FIFO 调度策略总体相同,只不过 SCHED_RR 调度策略也使用时间片,SCHED_RR级进程消耗完自己的时间片时,由同优先级的其他实时进程抢占。
SCHED_FIFO 和 SCHED_RR 调度策略,高优先级的进程总是立刻抢占低优先级的进程。低优先级进程不会抢占 SCHED_RR 进程,即使它的时间片已经使用完毕。