周期性调度器是在scheduler_tick中实现。如果系统正在活动中,内核会按照频率HZ自动调用该函数。如果没有进程在等待调度,那么在计算机电力供应不足的情况下,也可以关闭该调度器以减少电能消耗。
3469 /*
3470 * This function gets called by the timer code, with HZ frequency.
3471 * We call it with interrupts disabled.
3472 *
3473 * It also gets called by the fork code, when changing the parent's
3474 * timeslices.
3475 */
3476 void scheduler_tick(void)
3477 {
3478 int cpu = smp_processor_id();
3479 struct rq *rq = cpu_rq(cpu);
3480 struct task_struct *curr = rq->curr;
3481 u64 next_tick = rq->tick_timestamp + TICK_NSEC;
3482
3483 spin_lock(&rq->lock);
3484 __update_rq_clock(rq);
3485 /*
3486 * Let rq->clock advance by at least TICK_NSEC:
3487 */
3488 if (unlikely(rq->clock < next_tick))
3489 rq->clock = next_tick;
3490 rq->tick_timestamp = rq->clock;
3491 update_cpu_load(rq);
3492 if (curr != rq->idle) /* FIXME: needed? */
3493 curr->sched_class->task_tick(rq, curr);
3494 spin_unlock(&rq->lock);
3495
3496 #ifdef CONFIG_SMP
3497 rq->idle_at_tick = idle_cpu(cpu);
3498 trigger_load_balance(rq, cpu);
3499 #endif
3500 }
3484 __update_rq_clock 处理就绪队列时钟的更新,在正常情况下,这个函数就是更新rq->clock到当前的时钟tick
3488 ~3499 正常情况下,我们期望新的时钟应该是原时钟增加一个嘀哒,但是__update_rq_clock得到的时钟值步伐小于一个嘀哒,那么就设置为一个嘀哒。
3490 把当前时钟也保存在tick_timestamp,这个值和clock有些冗余,完全是为了处理方便而增加的一个成员变量。
3491 update_cpu_load用来更新rq数据结构中的cpu_load数组字段,这个cpu_load在负载迁移时会用到。关于cpu load,参见linux进程调度 - 负载均衡
3493 task_tick的实现方式取决于底层的调度器类。对于完全公平调度器会在该方法中检测是否有进程运行太长时间,以避免过长的延迟。
__update_rq_clock
382 /*
383 * Update the per-runqueue clock, as finegrained as the platform can give
384 * us, but without assuming monotonicity, etc.:
385 */
386 static void __update_rq_clock(struct rq *rq)
387 {
388 u64 prev_raw = rq->prev_clock_raw;
389 u64 now = sched_clock();
390 s64 delta = now - prev_raw;
391 u64 clock = rq->clock;
392
393 #ifdef CONFIG_SCHED_DEBUG
394 WARN_ON_ONCE(cpu_of(rq) != smp_processor_id());
395 #endif
396 /*
397 * Protect against sched_clock() occasionally going backwards:
398 */
399 if (unlikely(delta < 0)) {
400 clock++;
401 rq->clock_warps++;
402 } else {
403 /*
404 * Catch too large forward jumps too:
405 */
406 if (unlikely(clock + delta > rq->tick_timestamp + TICK_NSEC)) {
407 if (clock < rq->tick_timestamp + TICK_NSEC)
408 clock = rq->tick_timestamp + TICK_NSEC;
409 else
410 clock++;
411 rq->clock_overflows++;
412 } else {
413 if (unlikely(delta > rq->clock_max_delta))
414 rq->clock_max_delta = delta;
415 clock += delta;
416 }
417 }
418
419 rq->prev_clock_raw = now;
420 rq->clock = clock;
421 }
该函数更新per-CPU运行队列的rq->prev_clock_raw和rq->clock,这里的时钟都是实时时钟。
388 prev_raw 上一次调用__update_req_clock的时钟;now是当前的系统时间
399 ~401 防止时钟偶尔会跑错,那么简单对clock加1,然后增加统计量clock_warps
403 ~ 411 时钟也可能会发生前跳,那么在这种情况下就要修正clock的值,并增加统计量clock_overflows
task_tick_fair
/*
* scheduler tick hitting a task of our scheduling class:
*/
static void task_tick_fair(struct rq *rq, struct task_struct *curr)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &curr->se;
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
entity_tick(cfs_rq, se);
}
}
这个函数非常简单,实际工作是由entity_tick完成的
645 static void entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
646 {
647 /*
648 * Update run-time statistics of the 'current'.
649 */
650 update_curr(cfs_rq);
651
652 if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
653 check_preempt_tick(cfs_rq, curr);
654 }
650 update_curr 更新当前
652 只有可运行进程数目大于1时,才需要考虑进程抢占的问题,否则一个进程是不需要抢占的。
653 check_preempt_tick该函数判断进程运行的时间是否超过了延迟周期,如果超过了延迟周期,那么就要重新调度(设置TIF_NEED_RESCHED标志),这样核心调度器会在一个合适的地方重新调度。比如在系统调用返回之后,会检查TIF_NEED_RESCHED标志,如果置位,内核会调用主调度器schedule;此外当设备驱动程序执行长而重复的任务时,在每次反复循环中,驱动程序都检查TIF_NEED_RESCHED的值,如果必要,则调用调度程序schedule主动放弃CPU。