diff --git a/study/kernel/01-process/05-schedule/09-tuning/README.md b/study/kernel/01-process/05-schedule/09-tuning/README.md index 99a5dd6..8064679 100644 --- a/study/kernel/01-process/05-schedule/09-tuning/README.md +++ b/study/kernel/01-process/05-schedule/09-tuning/README.md @@ -525,7 +525,170 @@ curr->vruntime - p->vruntime > calc_delta_fair(sysctl_sched_wakeup_granularity, -#5 `sysctl_sched_child_runs_first` + +#5 `sysctl_sched_tunable_scaling` +------- + + +| 内核参数 | 位置 | 内核默认值 | 描述 | +|:------------:|:------:|:---------------:|:------:| +| [`sysctl_sched_tunable_scaling`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/fair.c#L68) | `/proc/sys/kernel/sched_tunable_scaling` | 1 | 当内核试图调整sched_min_granularity,sched_latency和sched_wakeup_granularity这三个值的时候所使用的更新方法,0为不调整,1为按照cpu个数以2为底的对数值进行调整,2为按照cpu的个数进行线性比例的调整 | + + + +##5.1 参数背景 +------- + +内核中有有几个调度参数都是描述的进程运行时间 + + +* 进程最小运行时间 `sched_min_granularity` + +* 调度周期 `sched_latency` + +* `sysctl_sched_wakeup_granularity` 唤醒后运行时间基数 + + + +```cpp +// https://elixir.bootlin.com/linux/v4.14.14/source/include/linux/sched/sysctl.h#L27 +enum sched_tunable_scaling { + SCHED_TUNABLESCALING_NONE, + SCHED_TUNABLESCALING_LOG, + SCHED_TUNABLESCALING_LINEAR, + SCHED_TUNABLESCALING_END, +}; + +// https://elixir.bootlin.com/linux/v4.16-rc1/source/kernel/sched/fair.c#L58 +/* + * The initial- and re-scaling of tunables is configurable + * + * Options are: + * + * SCHED_TUNABLESCALING_NONE - unscaled, always *1 + * SCHED_TUNABLESCALING_LOG - scaled logarithmical, *1+ilog(ncpus) + * SCHED_TUNABLESCALING_LINEAR - scaled linear, *ncpus + * + * (default SCHED_TUNABLESCALING_LOG = *(1+ilog(ncpus)) + */ +enum sched_tunable_scaling sysctl_sched_tunable_scaling = SCHED_TUNABLESCALING_LOG; +``` + +```cpp +// https://elixir.bootlin.com/linux/v4.16-rc1/source/kernel/sched/fair.c#L153 +/* + * Increase the granularity value when there are more CPUs, + * because with more CPUs the 'effective latency' as visible + * to users decreases. But the relationship is not linear, + * so pick a second-best guess by going with the log2 of the + * number of CPUs. + * + * This idea comes from the SD scheduler of Con Kolivas: + */ +static unsigned int get_update_sysctl_factor(void) +{ + unsigned int cpus = min_t(unsigned int, num_online_cpus(), 8); + unsigned int factor; + + switch (sysctl_sched_tunable_scaling) { + case SCHED_TUNABLESCALING_NONE: + factor = 1; + break; + case SCHED_TUNABLESCALING_LINEAR: + factor = cpus; + break; + case SCHED_TUNABLESCALING_LOG: + default: + factor = 1 + ilog2(cpus); + break; + } + + return factor; +} + +static void update_sysctl(void) +{ + unsigned int factor = get_update_sysctl_factor(); + +#define SET_SYSCTL(name) \ + (sysctl_##name = (factor) * normalized_sysctl_##name) + SET_SYSCTL(sched_min_granularity); + SET_SYSCTL(sched_latency); + SET_SYSCTL(sched_wakeup_granularity); +#undef SET_SYSCTL +} +``` + + +``` +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/fair.c#L195 +void sched_init_granularity(void) +{ + update_sysctl(); +} + + +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/fair.c#L9024 +static void rq_online_fair(struct rq *rq) +{ + update_sysctl(); + + update_runtime_enabled(rq); +} + + + +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/fair.c#L9051 +static void rq_offline_fair(struct rq *rq) +{ + update_sysctl(); + + /* Ensure any throttled groups are reachable by pick_next_task */ + unthrottle_offline_cfs_rqs(rq); +} +``` + + + +```cpp +// https://elixir.bootlin.com/linux/v4.16-rc1/source/kernel/sched/fair.c#L154 +/************************************************************** + * Scheduling class statistics methods: + */ +int sched_proc_update_handler(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, + loff_t *ppos) +{ + int ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + unsigned int factor = get_update_sysctl_factor(); + + if (ret || !write) + return ret; + + sched_nr_latency = DIV_ROUND_UP(sysctl_sched_latency, + sysctl_sched_min_granularity); + +#define WRT_SYSCTL(name) \ + (normalized_sysctl_##name = sysctl_##name / (factor)) + WRT_SYSCTL(sched_min_granularity); + WRT_SYSCTL(sched_latency); + WRT_SYSCTL(sched_wakeup_granularity); +#undef WRT_SYSCTL + + return 0; +} +``` + + +##5.2 参数详解 +------- + + +##5.3 接口详解 +------- + + +#6 `sysctl_sched_child_runs_first` ------- @@ -534,7 +697,7 @@ curr->vruntime - p->vruntime > calc_delta_fair(sysctl_sched_wakeup_granularity, | [`sysctl_sched_child_runs_first`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/fair.c#L87) | `/proc/sys/kernel/sched_child_runs_first` | 0 | 该变量表示在创建子进程的时候是否让子进程抢占父进程,即使父进程的vruntime小于子进程,这个会减少公平性,但是可以降低write_on_copy,具体要根据系统的应用情况来考量使用哪种方式(见task_fork_fair过程) | -##5.1 参数背景 +##6.1 参数背景 ------- @@ -544,7 +707,7 @@ curr->vruntime - p->vruntime > calc_delta_fair(sysctl_sched_wakeup_granularity, 该变量表示在创建子进程的时候是否让子进程抢占父进程,即使父进程的 `vruntime` 小于子进程,这个会减少公平性,但是可以降低 `write_on_copy`,具体要根据系统的应用情况来考量使用哪种方式(见 `task_fork_fair` 过程) -##5.2 `sysctl_sched_child_runs_first` 保证子进程先运行 +##6.2 `sysctl_sched_child_runs_first` 保证子进程先运行 ------- @@ -618,13 +781,285 @@ static void task_fork_fair(struct task_struct *p) 如果参数 `sysctl_sched_child_runs_first` 被设置, 同时 `curr` 进程的虚拟运行时间比创建的子进程 `se`, 就交换它们的虚拟运行时间. 通过这种方式来保证子进程的虚拟运行时间比父进程的小, 从而保证子进程优先运行. -##5.3 `sysctl_sched_child_runs_first` 接口 +##6.3 `sysctl_sched_child_runs_first` 接口 ------- -#6 +#7 `sysctl_sched_migration_cost` +------- -#2 调度器特性 features + +| 内核参数 | 位置 | 内核默认值 | 描述 | +|:------------:|:------:|:---------------:|:------:| +| [`sysctl_sched_migration_cost`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/fair.c#L101) | `/proc/sys/kernel/sched_migration_cost` | `500000ns` | 该变量用来判断一个进程是否还是 `hot`,如果进程的运行时间 (`now - p->se.exec_start`) 小于它,那么内核认为它的 `code` 还在 `cache` 里,所以该进程还是 `hot`,那么在迁移的时候就不会考虑它 | + + +##7.1 参数背景 +------- + + +`CPU` 负载平衡有两种方式 : `pull` 和 `push`, 即 + +* 空闲 `CPU` 从其他忙的 `CPU` 队列中拉一个进程到当前 `CPU` 队列, 通过 `idle_balance` 完成; + +* 或者忙的CPU队列将一个进程推送到空闲的 `CPU` 队列中. + + +`idle_balance` 干的则是 `pull` 的事情, `idle_balance` 会 `for_each_domain(this_cpu, sd)` 则是遍历当前 `CPU`所在的调度域, 维持调度域内的核间平衡. + +但是负载平衡有一个矛盾就是 : 负载平衡的频度和 `CPU cache` 的命中率是矛盾的, `CPU` 调度域就是将各个 `CPU` 分成层次不同的组, 低层次搞定的平衡就绝不上升到高层次处理, 避免影响 `cache` 的命中率. + + +* 调度器通过 `sysctl_sched_migration_cost` 这个 `proc` 阈值判断进程来判断进程是否是 `cache hot` 的, 如果是 `hot` 的则不会进行负载均衡迁移. + +* 同时通过该阈值控制当前 `CPU` 是否可以 `pull`. `sysctl_sched_migration_cost` 对应 `proc` 控制文件是 `/proc/sys/kernel/sched_migration_cost`, 开关代表如果 `CPU` 队列空闲了 `500000ns = 500us`(`sysctl_sched_migration_cost` 默认值) 以上, 则进行 `pull`, 否则则返回. + + +##7.2 `sysctl_sched_migration_cost` 实现详解 +------- + + +###7.2.1 判断进程是否是 `cache-hot` +------- + + +调度器通过 [`task_hot`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/fair.c#L6596) 函数检查一个进程是否是 `cache-hot` 的. 如果该进程还是 `cache-hot`,那么在迁移的时候就不会考虑它. + + +* 如果 `sysctl_sched_migration_cost` 被设置为 `-1`, 则 `task_hot` 永远返回 `1`, 即认为进程永远是 `cache-hot` 的. + + +* 如果 `sysctl_sched_migration_cost` 被设置为 `0`, 则禁用了该配置, `task_hot` 永远返回 `0`. + + +* 如果进程的运行时间 (`now - p->se.exec_start`) 小于 `sysctl_sched_migration_cost`,那么内核认为它的 `code` 还在 `cache` 里. + + + +```cpp +// http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/fair.c#L6596 +/* + * Is this task likely cache-hot: + */ +static int task_hot(struct task_struct *p, struct lb_env *env) +{ + // ...... + + if (sysctl_sched_migration_cost == -1) + return 1; + if (sysctl_sched_migration_cost == 0) + return 0; + + delta = rq_clock_task(env->src_rq) - p->se.exec_start; + + return delta < (s64)sysctl_sched_migration_cost; +} +``` + +注意 + +`task_hot(cahce-hot)` 只是禁止进程迁移的充分条件, 而不是必要条件. + +具体的信息请参见 [`can_migrate_task`](https://elixir.bootlin.com/linux/latest/source/kernel/sched/fair.c#L7063) 函数. + + +```cpp +// (https://elixir.bootlin.com/linux/latest/source/kernel/sched/fair.c#L7063 +/* + * can_migrate_task - may task p from runqueue rq be migrated to this_cpu? + */ +static +int can_migrate_task(struct task_struct *p, struct lb_env *env) +{ + int tsk_cache_hot; + + lockdep_assert_held(&env->src_rq->lock); + + /* + * We do not migrate tasks that are: + * 1) throttled_lb_pair, or + * 2) cannot be migrated to this CPU due to cpus_allowed, or + * 3) running (obviously), or + * 4) are cache-hot on their current CPU. + */ + if (throttled_lb_pair(task_group(p), env->src_cpu, env->dst_cpu)) + return 0; + + if (!cpumask_test_cpu(env->dst_cpu, &p->cpus_allowed)) { + // ...... + return 0; + } + + /* Record that we found atleast one task that could run on dst_cpu */ + env->flags &= ~LBF_ALL_PINNED; + + if (task_running(env->src_rq, p)) { + // ...... + return 0; + } + + /* + * Aggressive migration if: + * 1) destination numa is preferred + * 2) task is cache cold, or + * 3) too many balance attempts have failed. + */ + tsk_cache_hot = migrate_degrades_locality(p, env); + if (tsk_cache_hot == -1) + tsk_cache_hot = task_hot(p, env); + + if (tsk_cache_hot <= 0 || + env->sd->nr_balance_failed > env->sd->cache_nice_tries) { + if (tsk_cache_hot == 1) { + schedstat_inc(env->sd->lb_hot_gained[env->idle]); + schedstat_inc(p->se.statistics.nr_forced_migrations); + } + return 1; + } + + schedstat_inc(p->se.statistics.nr_failed_migrations_hot); + return 0; +} +``` + + + +###7.2.2 `idle_balance` 是否需要 `pull` +------- + + +当一个 `CPU` 进入 `idle` 状态时, 将通过 `idle_balance` 从其他繁忙的 `CPU` 上 `pull` 一个进程下来. + +如果 `CPU` 的运行队列 `rq` 空闲时间超过 `sysctl_sched_migration_cost` 以上, 才会从其他 `CPU` 运行队列上 `pull`, 否则则返回, 不会进行 `pull` 操作. + + +```cpp +// http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/fair.c#L8374 +/* + * idle_balance is called by schedule() if this_cpu is about to become + * idle. Attempts to pull tasks from other CPUs. + */ +static int idle_balance(struct rq *this_rq, struct rq_flags *rf) +{ + // ...... + + /* + * This is OK, because current is on_cpu, which avoids it being picked + * for load-balance and preemption/IRQs are still disabled avoiding + * further scheduler activity on it and we're being very careful to + * re-start the picking loop. + */ + rq_unpin_lock(this_rq, rf); + + if (this_rq->avg_idle < sysctl_sched_migration_cost || + !this_rq->rd->overload) { + rcu_read_lock(); + sd = rcu_dereference_check_sched_domain(this_rq->sd); + if (sd) + update_next_balance(sd, &next_balance); + rcu_read_unlock(); + + goto out; + } + + // ...... + +out: + /* Move the next balance forward */ + if (time_after(this_rq->next_balance, next_balance)) + this_rq->next_balance = next_balance; + + /* Is there a task of a high priority class? */ + if (this_rq->nr_running != this_rq->cfs.h_nr_running) + pulled_task = -1; + + if (pulled_task) + this_rq->idle_stamp = 0; + + rq_repin_lock(this_rq, rf); + + return pulled_task; +} +``` + + +##7.3 `sysctl_sched_migration_cost` 接口详解 +------- + + + +#8 sysctl_sched_rt_period 与 sysctl_sched_rt_runtime +------- + +| 内核参数 | 位置 | 内核默认值 | 描述 | +|:------------:|:------:|:---------------:|:------:| +| [`sysctl_sched_rt_period`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/core.c#L76) | `/proc/sys/kernel/sched_rt_period_us` | `1000000us` | 该参数与下面的sysctl_sched_rt_runtime一起决定了实时进程在以sysctl_sched_rt_period为周期的时间内,实时进程最多能够运行的总的时间不能超过sysctl_sched_rt_runtime(代码见sched_rt_global_constraints | +| 内核参数 | 位置 | 内核默认值 | 描述 | +|:------------:|:------:|:---------------:|:------:| +| [`sysctl_sched_rt_runtime`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/core.c#L84) | `/proc/sys/kernel/sched_rt_runtime_us` | `950000us` | 见上 `sysctl_sched_rt_period` 变量的解释 | + + + +```cpp +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/core.c#L72 +/* + * period over which we measure -rt task CPU usage in us. + * default: 1s + */ +unsigned int sysctl_sched_rt_period = 1000000; + + +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/core.c#L80 +/* + * part of the period that we allow rt tasks to run in us. + * default: 0.95s + */ +int sysctl_sched_rt_runtime = 950000; +``` + + +#9 sysctl_sched_rt_runtime +------- + +```cpp +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/sched.h#L1264 +static inline u64 global_rt_period(void) +{ + return (u64)sysctl_sched_rt_period * NSEC_PER_USEC; +} + +static inline u64 global_rt_runtime(void) +{ + if (sysctl_sched_rt_runtime < 0) + return RUNTIME_INF; + + return (u64)sysctl_sched_rt_runtime * NSEC_PER_USEC; +} +``` + + +#10 +------- + + +```cpp +// https://elixir.bootlin.com/linux/v4.14.14/source/kernel/sched/rt.c#L12 +int sched_rr_timeslice = RR_TIMESLICE; +int sysctl_sched_rr_timeslice = (MSEC_PER_SEC / HZ) * RR_TIMESLICE; +``` + + +#10 sysctl_sched_compat_yield +------- + + +| 内核参数 | 位置 | 内核默认值 | 描述 | +|:------------:|:------:|:---------------:|:------:| +| [`sysctl_sched_compat_yield`]() | `/proc/sys/kernel/sched_compat_yield` | 0 | 该参数可以让 `sched_yield()` 系统调用更加有效,让它使用更少的cpu,对于那些依赖sched_yield来获得更好性能的应用可以考虑设置它为1 | + + + +#11 调度器特性 features ------- @@ -662,8 +1097,12 @@ const_debug unsigned int sysctl_sched_features = #undef SCHED_FEAT ``` + + 首先定义了一个 `SCHED_FEAT` 宏, 然后定义并初始化 `sysctl_sched_features` 的时候通过包含 `features.h` 完成. 该头文件的定义在 [`kernel/sched/features.h, v4.14.14`](http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/features.h). + + ```cpp // http://elixir.free-electrons.com/linux/v4.14.14/source/kernel/sched/features.h SCHED_FEAT(GENTLE_FAIR_SLEEPERS, true) @@ -733,6 +1172,7 @@ extern struct static_key sched_feat_keys[__SCHED_FEAT_NR]; ##2.2 调度器特性 sched_features ------- + 每个特性对应 `sysctl_sched_features` 中的一位, 通过 `__SCHED_FEAT_XXX` 宏定义 @@ -776,3 +1216,4 @@ extern struct static_key sched_feat_keys[__SCHED_FEAT_NR]; * 基于本文修改后的作品务必以相同的许可发布. 如有任何疑问,请与我联系. +` \ No newline at end of file