|
1 | 1 |
|
2 |
| -### CFS调度器相关的`sched_latency_ns`、`sched_min_granularity_ns` 和 `sched_wakeup_granularity_ns` |
| 2 | +## CFS调度器相关的`sched_latency_ns`、`sched_min_granularity_ns`和 `sched_wakeup_granularity_ns`参数 |
3 | 3 |
|
| 4 | +### 初始化 |
4 | 5 | * 与 CFS 调度器工作密切相关的几个参数值在不同机器上看到的值可能会不一样,但是看内核源代码,内核的`.config`,`/etc/sysctl.conf`,`/etc/sysctl.d/*`以及其他能想到会配置`sysctl`的地方却找不到有哪里修改了它们。
|
5 | 6 | * 这几个变量在内核中的缺省值:
|
6 | 7 | * kernel/sched/fair.c
|
@@ -54,6 +55,7 @@ kernel.sched_wakeup_granularity_ns = 4000000
|
54 | 55 | * 为什么不采用固定值,或者随着 CPU 数目线性增长,而是将`log(ncpus)`作为因子?
|
55 | 56 | * 简单的说,就是随着 CPU 性能的增多,调度的“有效延迟”肯定会减小,但减小的幅度却不可能是线性的。
|
56 | 57 | * 可以想象,到后来即使加入更多的 CPU,调度因此而获得的收益会愈不明显,所以也就没有必要再返回更大的 factor 了。
|
| 58 | +* kernel/sched/fair.c |
57 | 59 | ```c
|
58 | 60 | /*
|
59 | 61 | * Increase the granularity value when there are more CPUs,
|
@@ -110,3 +112,125 @@ start_kernel()
|
110 | 112 | -> update_sysctl()
|
111 | 113 | -> get_update_sysctl_factor()
|
112 | 114 | ```
|
| 115 | + |
| 116 | +### 设置 |
| 117 | + |
| 118 | +* 而设置这几个参数则通过回调函数`sched_proc_update_handler()`修改 |
| 119 | +```c |
| 120 | +int sched_proc_update_handler(struct ctl_table *table, int write, |
| 121 | + void __user *buffer, size_t *lenp, |
| 122 | + loff_t *ppos) |
| 123 | +{ |
| 124 | + int ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); |
| 125 | + unsigned int factor = get_update_sysctl_factor(); |
| 126 | + |
| 127 | + if (ret || !write) |
| 128 | + return ret; |
| 129 | + |
| 130 | + sched_nr_latency = DIV_ROUND_UP(sysctl_sched_latency, |
| 131 | + sysctl_sched_min_granularity); |
| 132 | + |
| 133 | +#define WRT_SYSCTL(name) \ |
| 134 | + (normalized_sysctl_##name = sysctl_##name / (factor)) |
| 135 | + WRT_SYSCTL(sched_min_granularity); |
| 136 | + WRT_SYSCTL(sched_latency); |
| 137 | + WRT_SYSCTL(sched_wakeup_granularity); |
| 138 | +#undef WRT_SYSCTL |
| 139 | + |
| 140 | + return 0; |
| 141 | +} |
| 142 | +#endif |
| 143 | +...__``` |
| 144 | +``` |
| 145 | +* 注意,全局变量`sched_nr_latency`除了初始化外,唯一被赋值的地方就是这里,公式为: |
| 146 | + ```c |
| 147 | + sched_nr_latency = ceil( sysctl_sched_latency / sysctl_sched_min_granularity ) |
| 148 | + ``` |
| 149 | + |
| 150 | +### 微调 |
| 151 | +* 关于调节`sched_latency_ns`和`sched_min_granularity_ns`会对系统,或者说抢占,造成的影响,主要考察`sched_latency_ns`的计算公式和以下几个函数: |
| 152 | + ```c |
| 153 | + check_preempt_tick() |
| 154 | + -> sched_slice() |
| 155 | + -> __sched_period() |
| 156 | + ``` |
| 157 | +#### 计算调度周期__sched_period |
| 158 | +* 首先是看计算 **调度周期**(也叫 *目标延迟* 或者 *调度延迟*)的函数`__sched_period()` |
| 159 | +* 该延迟使得 CFS 不必每个 tick 都去检查是否需要调度切换,而是延迟到一定程度再去检查 |
| 160 | +* 在调度延迟这个时间片内,`cfs_rq`中的每个进程以求优先级为权重瓜分时间 |
| 161 | +```c |
| 162 | +/* |
| 163 | + * The idea is to set a period in which each task runs once. |
| 164 | + * |
| 165 | + * When there are too many tasks (sched_nr_latency) we have to stretch |
| 166 | + * this period because otherwise the slices get too small. |
| 167 | + * |
| 168 | + * p = (nr <= nl) ? l : l*nr/nl |
| 169 | + */ |
| 170 | +static u64 __sched_period(unsigned long nr_running) |
| 171 | +{ |
| 172 | + if (unlikely(nr_running > sched_nr_latency)) |
| 173 | + return nr_running * sysctl_sched_min_granularity; |
| 174 | + else |
| 175 | + return sysctl_sched_latency; |
| 176 | +} |
| 177 | +``` |
| 178 | +* `nr_running`通常是就绪队列上的进程数,由此可以看到,调度周期同时受`nr_running`,`sysctl_sched_min_granularity`,`sysctl_sched_latency`三者影响,`sched_nr_latency`是根据后二者计算出来的 |
| 179 | +* 我们将这两个分支称为 **条件1** 和 **条件2** |
| 180 | +1. **条件1**: `nr_running > sched_nr_latency`:排队的就绪进程较多,每个进程分得的时间按权重比例划分`nr_running * sysctl_sched_min_granularity` |
| 181 | +2. **条件2**:`nr_running > sched_nr_latency`:排队的就绪进程较少,每个进程分得的时间按权重比例划分`sysctl_sched_latency` |
| 182 | +
|
| 183 | +```c |
| 184 | +/* |
| 185 | + * We calculate the wall-time slice from the period by taking a part |
| 186 | + * proportional to the weight. |
| 187 | + * |
| 188 | + * s = p*P[w/rw] |
| 189 | + */ |
| 190 | +static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se) |
| 191 | +{ |
| 192 | + u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq); |
| 193 | +
|
| 194 | + for_each_sched_entity(se) { |
| 195 | + struct load_weight *load; |
| 196 | + struct load_weight lw; |
| 197 | +
|
| 198 | + cfs_rq = cfs_rq_of(se); |
| 199 | + load = &cfs_rq->load; |
| 200 | +
|
| 201 | + if (unlikely(!se->on_rq)) { |
| 202 | + lw = cfs_rq->load; |
| 203 | +
|
| 204 | + update_load_add(&lw, se->load.weight); |
| 205 | + load = &lw; |
| 206 | + } |
| 207 | + slice = __calc_delta(slice, se->load.weight, load); |
| 208 | + } |
| 209 | + return slice; |
| 210 | +} |
| 211 | +...*``` |
| 212 | +``` |
| 213 | +* `sched_slice()`是按权重比例划分调度周期为时间片的过程 |
| 214 | +* 注意,这里的时间片是墙上时间(实际时间),不是虚拟时间 |
| 215 | +* `check_preempt_tick()`详见 [周期性调度检查check_preempt_tick](#sched_cfs.md#周期性调度检查check_preempt_tick) |
| 216 | + |
| 217 | +#### `sysctl_sched_latency`不变,只减小`sysctl_sched_min_granularity` |
| 218 | +* 根据公式,`sched_nr_latency`会比较大,因此容易进入 **条件2** |
| 219 | +* 有可能用更长的调度周期`sysctl_sched_latency`,而不是`nr_running * sysctl_sched_min_granularity` |
| 220 | +* 虽然调度最小粒度比较小,但比改动前的理想运行时间会更长 |
| 221 | +* 在`check_preempt_tick()`时会因`if (delta_exec > ideal_runtime)` 不易达成变得不易被抢占 |
| 222 | +#### `sysctl_sched_min_granularity`不变,只减小`sysctl_sched_latency` |
| 223 | +* 根据公式,`sched_nr_latency`会比较小,因此容易进入 **条件1** |
| 224 | +* 以调度最小粒度换算后进行调度 |
| 225 | +* 在`check_preempt_tick()`时,会使检查调度最小粒度的条件不容易通过,因此变得易被抢占 |
| 226 | + ```c |
| 227 | + if (delta_exec < sysctl_sched_min_granularity) |
| 228 | + return; |
| 229 | + ``` |
| 230 | +#### 同时减小`sysctl_sched_latency`和`sysctl_sched_min_granularity` |
| 231 | +* 根据公式,`sched_nr_latency`不变,因此容易进入 **条件1** 和 **条件2** 的机会和调整之前想当 |
| 232 | +* 然而,不论是进入哪个条件,调度周期都变短了,见`__sched_period()` |
| 233 | +* 因此,理想运行时间也变短了,见`sched_slice()` |
| 234 | +* 在`check_preempt_tick()`时, |
| 235 | + * `if (delta_exec > ideal_runtime)` 容易达成,易被抢占 |
| 236 | + * `if (delta_exec < sysctl_sched_min_granularity)` 不易达成,易被抢占 |
0 commit comments