在学习linux进程管理CFS调度类的过程中,有一个变量sysctl_sched_min_granularity,在源码中定义如下:
// https://elixir.bootlin.com/linux/v4.9.81/source/kernel/sched/fair.c#L69
/* CPU 密集型任务的最小抢占粒度
* Minimal preemption granularity for CPU-bound tasks:
*
* (default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds)
*/
unsigned int sysctl_sched_min_granularity = 750000ULL;
在看源码的过程中,产生一个疑问,sysctl_sched_min_granularity是否是CFS中进程占用CPU的最小时间(或者说最小调度粒度时间)呢?产生这个疑问的原因:
-
- 之前在阅读linux源码v4.9.81时,在网上搜索关于
sysctl_sched_min_granularity变量的含义,基本都是说代表了进程占用CPU运行的最小时间。
- 之前在阅读linux源码v4.9.81时,在网上搜索关于
-
- 但是在源码中,没有找到其表示进程占用CPU运行最小时间的代码。
-
- 偶然间看到一篇文章,写到
sysctl_sched_min_granularity表示"当一个task错过了唤醒抢占,确保它不会等待一个调度周期才能够被调度.目的是减少调度时延"。而我在源码中,也是只能发现这一层含义。
- 偶然间看到一篇文章,写到
-
- 最近在学习张师傅"Linux性能分析之CPU篇"课程中进程调度部分时,听到他也是认为
sysctl_sched_min_granularity表示进程占用CPU运行的最小时间,这就与我所理解的有一些出入了,于是开始搜集资料找答案。
- 最近在学习张师傅"Linux性能分析之CPU篇"课程中进程调度部分时,听到他也是认为
1.linux源码中的描述
linux源码v4.9.81关于sysctl_sched_min_granularity的使用有如下几个地方:
/*
* Minimal preemption granularity for CPU-bound tasks:
* (default: 0.75 msec * (1 + ilog(ncpus)), units: nanoseconds)
*/
unsigned int sysctl_sched_min_granularity = 750000ULL;
//debug用,可以先不看
#ifdef CONFIG_SCHED_DEBUG
int sched_proc_update_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp,
loff_t *ppos)
{
......
sched_nr_latency = DIV_ROUND_UP(sysctl_sched_latency,
sysctl_sched_min_granularity);
......
}
#endif
//当有太多任务(sched_nr_latency)时,我们必须延长这段时间,否则切片会变得太小
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;
}
//判断是否CFS运行队列中有调度实体能抢占当前调度实体
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;
//curr在一个调度周期内理论上应该运行的真实时间(非虚拟时间)
ideal_runtime = sched_slice(cfs_rq, curr);
//实际上curr已经运行的实际真实时间(非虚拟时间)
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
//实际运行时间比理论上应该运行的时间长,表示时间片用完
if (delta_exec > ideal_runtime) {
resched_curr(rq_of(cfs_rq));
return;
}
//确保以微弱优势错过唤醒抢占的任务不必等待完整的切片。
if (delta_exec < sysctl_sched_min_granularity)
return;
//CFS运行队列红黑树最左端调度实体se
se = __pick_first_entity(cfs_rq);
//curr与se的虚拟时间相减
delta = curr->vruntime - se->vruntime;
//curr更小,则curr继续执行
if (delta < 0)
return;
//se的虚拟时间偏小,且delta > ideal_runtime,
//其中ideal_runtime是真实时间,希望权重小(ideal_runtime小)的任务更容易被抢占
if (delta > ideal_runtime)
resched_curr(rq_of(cfs_rq));
}
从上述关于sysctl_sched_min_granularity的代码,得不出sysctl_sched_min_granularity表示进程占用CPU运行的最小时间的结论。只能得出在check_preempt_tick()函数中,sysctl_sched_min_granularity可用来表示:delta_exec <= ideal_runtime && delta_exec > sysctl_sched_min_granularity成立,即一个调度实体运行时间未达到理论运行时间且运行时间大于sysctl_sched_min_granularity,则可以判断是否可以进行抢占,确保以错过唤醒抢占的任务不必等待完整的切片。 原因如下:
ideal_runtime < delta_exec < sysctl_sched_min_granularity的话,发生抢占,sysctl_sched_min_granularity就不是最小调度粒度时间了。举例如下:
假设一个CFS队列中有:1个nice值-20的进程(权重88761),9个nice值为19的进程(权重15),
sysctl_sched_min_granularit = 0.75ms
总权重:total_weight = 88761 * 1 + 15 * 9 = 88896
调度周期:total_time = nr_running * sysctl_sched_min_granularity = (1 + 9) * 0.75ms = 7.5ms
每个nice值为19的进程的时间片:
ideal_runtime_19 = (weight_19 * total_time) / total_weight = (15 * 7.5ms) / 88,896 = 0.0013ms
nice值-20的进程的时间片:
ideal_runtime_-20 = (weight_-20 * total_time) / total_weight = (88761 * 7.5ms) / 88,896 = 7.4886ms
ideal_runtime_19 = 0.0013ms < sysctl_sched_min_granularit = 0.75ms
nr_running > 8
则total_time = nr_running * sysctl_sched_min_granularity
假设x个进程nice值小于0,平均执行时间为y1;(nr_running - x)个进程nice值大于0,平均执行时间为y2
x * y + (nr_running - x) * y2 = total_time
则
y1 > sysctl_sched_min_granularity成立
y2 < sysctl_sched_min_granularity成立
另外,尝试去搜了一下最新版本的linux源码,发现v5.12.3关于sysctl_sched_min_granularity的使用有改变,新增了如下使用:
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
unsigned int nr_running = cfs_rq->nr_running;
//表示调度实体在一个调度周期中的理论真实运行时间,省略了计算过程
u64 slice;
......
//如果sched_feature有设置BASE_SLICE,则强制slice最小为sysctl_sched_min_granularity
//则表示进程占用CPU的最小运行时间为sysctl_sched_min_granularity
if (sched_feat(BASE_SLICE))
slice = max(slice, (u64)sysctl_sched_min_granularity);
return slice;
}
有了这一部分新增的代码,在sched_feature有设置BASE_SLICE的时候,进程占用CPU的最小运行时间为sysctl_sched_min_granularity。
2.Android 11中的描述
在目前我使用的Android11中,内核版本为5.4,是没有上面的这个新增的patch的,故sysctl_sched_min_granularity还不是表示进程占用CPU的最小运行时间。在/sys/kernel/debug/sched_features中也没有设置BASE_SLICE这一项。
3.下一步计划
1.是否在linux内核v5.12.3版本之后的系统上,sched_feature默认有设置BASE_SLICE,需要在实际系统上进行验证?
待验证
2.是否张师傅所说的sysctl_sched_min_granularity表示进程占用CPU的最小运行时间指的是v5.12.3之后的版本?
与张师傅沟通,经过一番讨论,张师傅也认为sysctl_sched_min_granularity表示进程占用CPU的最小运行时间的结论是有一定问题的。在此,也特别感谢张师傅百忙之中,不厌其烦地给我答疑解惑!
真正理解linux内核源码,对一切抱有好奇、怀疑的态度,从源码中寻找答案,确实是一件振奋人心、让人开心的事情!