Linux内核架构读书笔记 - 2.5.4 核心调度器
什么是核心调度器?
参考前面的博文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 模式
浮点寄存器,除非有程序使用,负责不会保存,此外除非有应用程序需要,负责这些寄存器不会恢复