linux核心调度器主要基于两个函数实现:周期性调度器函数和主调度器函数。这些函数会根据现有进程的优先级分配CPU时间,所以也称“优先调度”
一、周期性调度器
周期性调度器是在函数scheduler_tick(void),如果当前系统正在活动中,那么内核会按照CPU频率HZ自动调用该函数。如果运行队列中没有进程,则等待;如果电力不足,也会关闭该调度器以减少电能消耗。(基于内核版本3.1.6)
该函数有下面两个主要任务。
(1) 管理内核中与整个系统和各个进程的调度相关的统计量。
(2) 激活负责当前进程的调度类的周期性调度方法。
void scheduler_tick(void)
{
int cpu = smp_processor_id(); /* 获取CPU Id号*/
struct rq *rq = cpu_rq(cpu); /* 获取对应的运行队列 */
struct task_struct *curr = rq->curr; /* 获取当前线程描述符 */
sched_clock_tick();
raw_spin_lock(&rq->lock); /* 加锁 */
update_rq_clock(rq); /* 处理就绪队列始终的更新: 本质是增加struct rq 当前实例的时钟时间戳 */
update_cpu_load_active(rq); /* 更新就绪队列的cpu_load[]数据: 本质是讲数组中先前存储的负荷值向后移动一个位置,将当前负荷记入数组的第一个位置 */
curr->sched_class->task_tick(rq, curr, 0); /* 激活当前线程的调度类的周期性调度方法,不同的调度方法有不同的实现 */
raw_spin_unlock(&rq->lock);/* 解锁 */
perf_event_task_tick(); /* 与perf计数事件相关 */
#ifdef CONFIG_SMP
rq->idle_at_tick = idle_cpu(cpu); /* 当前CPU是否空闲 */
trigger_load_balance(rq, cpu); /* 如果到是时候进行周期性负载平衡则触发SCHED_SOFTIRQ */
#endif
}
二、主调度器
周期性调度器是在函数scheduler(void)中实现。
1. 主要功能:从运行的队列中选择一个合适的线程,并进行线程切换;
2. 代码分析:
asmlinkage void __sched schedule(void)
{
struct task_struct *tsk = current; /* 获取当前线程的描述符 */
sched_submit_work(tsk); /* 为了防止死锁 */
__schedule(); /* 真正的主调度去实现方法 */
}
static void __sched __schedule(void)
{
struct task_struct *prev, *next;
unsigned long *switch_count;
struct rq *rq;
int cpu;
need_resched:
preempt_disable();/* 禁止抢占 */
cpu = smp_processor_id(); /* 获取当前CPU ID 号 */
rq = cpu_rq(cpu); /* 获取当前CPU 对应的运行队列 */
rcu_note_context_switch(cpu); /* 与RCU相关 */
prev = rq->curr;
schedule_debug(prev); /* 记录debug信息 */
if (sched_feat(HRTICK))
hrtick_clear(rq);
raw_spin_lock_irq(&rq->lock);
switch_count = &prev->nivcsw;
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
if (unlikely(signal_pending_state(prev->state, prev))) {
prev->state = TASK_RUNNING;
} else {
deactivate_task(rq, prev, DEQUEUE_SLEEP); /* 使进程停止活动,并最终调用具体的dequeue_task()方法 */
prev->on_rq = 0;
/*
* If a worker went to sleep, notify and ask workqueue
* whether it wants to wake up a task to maintain
* concurrency.
*/
if (prev->flags & PF_WQ_WORKER) { /* 一些并发性判断和操作,不影响整体调度流程 */
struct task_struct *to_wakeup;
to_wakeup = wq_worker_sleeping(prev, cpu);
if (to_wakeup)
try_to_wake_up_local(to_wakeup);
}
}
switch_count = &prev->nvcsw;
}
pre_schedule(rq, prev);
if (unlikely(!rq->nr_running))
idle_balance(cpu, rq);/* 当前运行队列为空,调用idle_balance()试图从其他的CPU运行队列拉来一些进程 */
put_prev_task(rq, prev); /* 通知调度器类当前运行的进程将要被另一个进程代替,为某些计数器更新计数提供实际 */
next = pick_next_task(rq);/* 从运行队列中选择下一个运行线程 */
clear_tsk_need_resched(prev);/* 清除当前进程描述符中的重调度标志TIF_NEED_RESCHED */
rq->skip_clock_update = 0;
if (likely(prev != next)) { /* 判断选择的next线程是不是原来的线程 */
rq->nr_switches++;
rq->curr = next;
++*switch_count;
context_switch(rq, prev, next); /* 负责执行底层的上下文切换 */
/*
* The context switch have flipped the stack from under us
* and restored the local variables which were saved when
* this task called schedule() in the past. prev == current
* is still correct, but it can be moved to another cpu/rq.
*/
cpu = smp_processor_id(); /* 更新信息 */
rq = cpu_rq(cpu);
} else
raw_spin_unlock_irq(&rq->lock);/* 如果依旧是原来的线程,则直接解锁 */
post_schedule(rq);
preempt_enable_no_resched();
if (need_resched()) /* 判断是否设置了重调度位,如果设置了则重新搜索新的进程 */
goto need_resched;
}