什么是核心调度器?
参考前面的博文http://www.cnblogs.com/songbingyu/p/3696414.html
1 周期性调度器
作用:
- 管理内核中与整个系统和各个进程的调度相关的统计量
- 负责当前调度类的周期性调度方法
kernel/sched.c
1 /*
2 * This function gets called by the timer code, with HZ frequency.
3 * We call it with interrupts disabled.
4 *
5 * It also gets called by the fork code, when changing the parent‘s
6 * timeslices.
7 */
8 void scheduler_tick(void)
9 {
10 int cpu = smp_processor_id();
11 struct rq *rq = cpu_rq(cpu);
12 struct task_struct *curr = rq->curr;
13 u64 next_tick = rq->tick_timestamp + TICK_NSEC;
14
15 spin_lock(&rq->lock);
16 __update_rq_clock(rq);
17 /*
18 * Let rq->clock advance by at least TICK_NSEC:
19 */
20 if (unlikely(rq->clock < next_tick))
21 rq->clock = next_tick;
22 rq->tick_timestamp = rq->clock;
23 update_cpu_load(rq);
24 ....
_update_rq_clock 处理就绪队列时钟更新,本质上增加struct rq 的时间戳
update_cpu_load 负责更新就绪队列的cpu_load[]
数组,本质上相当与将数组中先前存储的负载值向后移动一个位置,将当前就绪队列的负载值计入数组的第一个位置、、、
kernel/sched.c
1 if (curr != rq->idle) /* FIXME: needed? */
2 curr->sched_class->task_tick(rq, curr);
task_tick 实现取决与底层的调度器类
注意:
如果进程应该被重新调度,调度器类会在task_struct中设置TIF_NEED_RESCHED标志,已表示该请求,内核会载接下来的适当时机完成该请求
2 主调度器
在内核的许多地方,如果要将CPU 分配给当前活动进程不同的另一个进程,都会直接调用主调度器函数(schedule)
kernel/sched.c
1 /*
2 * schedule() is the main scheduler function.
3 */
4 asmlinkage void __sched schedule(void)
5 {
6 struct task_struct *prev, *next;
7 long *switch_count;
8 struct rq *rq;
9 int cpu;
10
11 need_resched:
12 preempt_disable();
13 cpu = smp_processor_id();
14 rq = cpu_rq(cpu);
15 rcu_qsctr_inc(cpu);
16 prev = rq->curr;
17 switch_count = &prev->nivcsw;
类似与周期性调度器,内核也利用该时机来更新就绪队列的时钟,并清楚当前运行进程中的重调度标志TIF_NEED_RESCHED
kernel/sched.c
1 __update_rq_clock(rq);
2 spin_lock(&rq->lock);
3 clear_tsk_need_resched(prev);
如果当前进程原来处于可中断睡眠状态但现在接受到信号,,那吗它必须再次提升为运行进程,负责相应调度器类的方法使进程停止活动(deactivate_task实质上最终调用sched_class->dequeue_task)
1 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
2 if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
3 unlikely(signal_pending(prev)))) {
4 prev->state = TASK_RUNNING;
5 } else {
6 deactivate_task(rq, prev, 1);
7 }
8 switch_count = &prev->nvcsw;
9 }
put_prev_task同志调度器类当前进程将要被另一个进程代替,不是从就绪队列移除,而是提供一个时机进行一些统计工作
pick_next_task 选择下一个进程
1 prev->sched_class->put_prev_task(rq, prev);
2 next = pick_next_task(rq, prev);
不见得必然选择一个进程,有可能其他进程都在睡眠
kernel/sched.c
1 if (likely(prev != next)) {
2 rq->nr_switches++;
3 rq->curr = next;
4 ++*switch_count;
5
6 context_switch(rq, prev, next); /* unlocks the rq */
content_switch 一个接口,供访问特定与体系结构的方法
下面代码检测当前的重调度位是否设置,并跳转
1 if (unlikely(test_thread_flag(TIF_NEED_RESCHED)))
2 goto need_resched;
注意:上述代码可能载两个上下文中执行 无上下文切换,schedule 末尾直接执行,如果执行上下文切换,在新的上下文执行,所以需要current 和
test_thread_flag 找到当前进程
3 与fork 交互
使用fork或其变体建立新进程时候,调度器有机会用sched_fork 挂钩到该进程
再单处理器上,执行三个操作,初始化新进程与调度相关字段,建立数据结构 确定进程的动态优先级
kernel/sched.c/sched_fork
/*
* Make sure we do not leak PI boosting priority to the child:
*/
p->prio = current->normal_prio;
if (!rt_prio(p->prio))
p->sched_class = &fair_sched_class;
在使用wake_up_new_task 唤醒新的进程,是调度器与创建逻辑交互的第二时机,内核会调用task_new
函数,将进程加入到相应的就绪队列
4 上下文切换
content_switch
kernel/sched.c
1 /*
2 * context_switch - switch to the new MM and the new
3 * thread‘s register state.
4 */
5 static inline void
6 context_switch(struct rq *rq, struct task_struct *prev,
7 struct task_struct *next)
8 {
9 struct mm_struct *mm, *oldmm;
10
11 prepare_task_switch(rq, prev, next);
12 mm = next->mm;
13 oldmm = prev->active_mm;
上下文切换调用两个特定与体系结构的的函数
1 switch_mm
2 switch_to
kernel/sched.c
1 if (unlikely(!mm)) {
2 next->active_mm = oldmm;
3 atomic_inc(&oldmm->mm_count);
4 enter_lazy_tlb(oldmm, next);
5 } else
6 switch_mm(oldmm, mm, next);
entry_lazy_tlb 通知底层结构体系不许要切换虚拟地址空间的用户部分
如果前一进程是内核进程(prev-》mm 为 null ) ,其 active_mm 必须重置为null
kernel/sched.c
1 if (unlikely(!prev->mm)) {
2 prev->active_mm = NULL;
3 rq->prev_mm = oldmm;
4 }
最后用switch_to 完成进程切换
switch_to 之后的代码只有当前进程下一次被选择运行是才会运行
finish_task_switch 完成一些清理工作
1 /* Here we just switch the register state and the stack. */
2 switch_to(prev, next, prev);
3
4 barrier();
5 /*
6 * this_rq must be evaluated again because prev may have moved
7 * CPUs since it called schedule(), thus the ‘rq‘ on its stack
8 * frame will be invalid.
9 */
10 finish_task_switch(this_rq(), prev);
- switch_to 的复杂之处
finish_task_switch 特点,调度器可能选择了一个新的进程,但是清理则是针对此前的活动进程
eg
其实上面只是表达一个意思
在新的进程被再次执行时候,获得上一次运行的是那一个进程
还没想明白。。。mark
- 惰性FPU 模式
浮点寄存器,除非有程序使用,负责不会保存,此外除非有应用程序需要,负责这些寄存器不会恢复
Linux内核架构读书笔记 - 2.5.4 核心调度器,码迷,mamicode.com