linux进程调度 - 周期性调度器

周期性调度器是在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。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值