编辑
2026-02-01
记录知识
0
请注意,本文编写于 98 天前,最后修改于 98 天前,其中某些信息可能已经过时。

目录

调度周期的实现
怎么保证周期性
抢占点1
抢占点2
注意点1
总结

CFS为了局部的公平性, 引入了一个概念叫做调度周期,什么是调度周期,就是每个进程在一个周期内至少运行一次。本文说明一下CFS的调度周期

调度周期的实现

调度周期是为了保证CFS调度器上的进程在一个周期内至少运行一次,那么怎么保证的呢?我们跟踪一下代码

if (td->mode == TICKDEV_MODE_PERIODIC) tick_setup_periodic(newdev, 0);

这里调度周期来自timer的periodic模式

tick_setup_periodic tick_set_periodic_handler dev->event_handler = tick_handle_periodic

这里tick_handle_periodic的流程

tick_handle_periodic tick_periodic update_process_times scheduler_tick curr->sched_class->task_tick(rq, curr, 0); task_tick_fair entity_tick check_preempt_tick sched_slice __sched_period

这里__sched_period是最终的实现,其返回了调度周期,实现如下

/* * The idea is to set a period in which each task runs once. * * When there are too many tasks (sched_nr_latency) we have to stretch * this period because otherwise the slices get too small. * * p = (nr <= nl) ? l : l*nr/nl */ static u64 __sched_period(unsigned long nr_running) { if (unlikely(nr_running > sched_nr_latency)) return nr_running * sysctl_sched_min_granularity; else return sysctl_sched_latency; }

我们查看这三个值

  • sched_nr_latency
  • sysctl_sched_latency
  • sysctl_sched_min_granularity
static unsigned int sched_nr_latency = 8; unsigned int sysctl_sched_latency = 6000000ULL; unsigned int sysctl_sched_min_granularity = 750000ULL;

这代表什么意思呢?

  1. 核心上的任务临界值是8(经验值)
  2. 少于等于8个任务的时候按照6ms周期
  3. 大于8个任务的时候按照0.75ms * 任务数量作为周期

值得注意的是,这个计算公式是纯经验值,任何人都可以手动sysctl修改看看效果。

怎么保证周期性

上面知道了调度周期的计算方式,实际上就是在进行抢占来实现的,简单来说就是通过update_curr计算好vruntime,然后用 check_preempt_tick 判断是否超周期了,如果超周期了,那么就实施抢占。我们看看check_preempt_tick的策略

static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr) { unsigned long ideal_runtime, delta_exec; struct sched_entity *se; s64 delta; bool skip_preempt = false; ideal_runtime = sched_slice(cfs_rq, curr); delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime; trace_android_rvh_check_preempt_tick(current, &ideal_runtime, &skip_preempt, delta_exec, cfs_rq, curr, sysctl_sched_min_granularity); if (skip_preempt) return; if (delta_exec > ideal_runtime) { resched_curr(rq_of(cfs_rq)); /* * The current task ran long enough, ensure it doesn't get * re-elected due to buddy favours. */ clear_buddies(cfs_rq, curr); return; } /* * Ensure that a task that missed wakeup preemption by a * narrow margin doesn't have to wait for a full slice. * This also mitigates buddy induced latencies under load. */ if (delta_exec < sysctl_sched_min_granularity) return; se = __pick_first_entity(cfs_rq); delta = curr->vruntime - se->vruntime; if (delta < 0) return; if (delta > ideal_runtime) resched_curr(rq_of(cfs_rq)); }

首先我们知道resched_curr就是抢占操作,那么代码有两个点是运行抢占的,如下

抢占点1

if (delta_exec > ideal_runtime)

如果任务跑的时机大于理想认为的周期时间了,那么抢占

抢占点2

if (delta > ideal_runtime)

这段逻辑需要结合一下上下文代码,如下

se = __pick_first_entity(cfs_rq); delta = curr->vruntime - se->vruntime;

因为cfs是利用rb-tree构建,rb-tree可以查看我之前的文章。所以这里se就是最小vruntime的调度实体。
那么delta就是当前时间减去最应该被调度的任务,如果大于周期,那么强行抢占。

可以看到,抢占点2其实就是一段客制化代码,其用于避免cfs的starvation问题(饿死问题)

注意点1

除了两个抢占点,check_preempt_tick还有一个特殊return,那就是

if (delta_exec < sysctl_sched_min_granularity) return;

这里意思也很明确,就是当前任务如果运行时间太短,那就不抢占了。这笔内容其实就是缓解调度的高开销。

总结

本文详细的介绍了CFS的另一个重要概念就是调度周期,CFS为了维持局部的公平性,专门引入的调度周期,这样使得任务不会被饿死。而实现所谓的调度周期的办法就是增加抢占点,让满足调度周期的任务强行被抢占了。