一、背景
之前的博客讲解了调度时延的观测 调度时延的观测-CSDN博客,当前这篇博客会讲解调度里的另外一个常用可观测点,调度时间片。
调度时间片相比调度时延更加好理解,表示的是某个任务被系统切换进来执行到被系统切换出去这个时间。从切换出去的目的来说分为两种情况,一种是任务主动交出cpu比如sleep这种函数,第二种是因为时间片到期或者cgroup cpu限额被动地被系统切换出去。
在之前的另外一篇博客 CFS及RT调度整体介绍_cfs rt 调度-CSDN博客 里我们有阐述过,linux里有个 sched_min_granlarity_ns 参数(在5.15内核及以后的版本里需要在/sys/kernel/debug/sched/下看,叫min_granlarity_ns),用来确保cfs的每个任务每次被调度到执行时有一定的时间片上的最小的保障,如下图(注意,要记得把cfs的唤醒抢占的场景排除,关于cfs的唤醒抢占特性,见 内核cfs的唤醒抢占特性-CSDN博客,还得排除rt的进程抢占fs的情形):
上图的这个参数只是试图去保障一个最小时间片,但是时间片最长会多少,事实上,它可以无限上,或者说可以非常非常长,举一个很极端的例子,就是某个核上只运行了一个进程,这个进程就是普通的cfs进程绑核到这个运行的这个核上,程序是一个死循环,而且这个核上也切好没有运行一些其他的内核线程或者触发一些中断或者软中断这些逻辑,那么这个线程是可以一直运行下去的,在第二章里会去讲有哪些情况会造成时间片过长。
在第三章里,我们会讲如何不改内核通过系统已有的机制来监测时间片,但是这是有缺陷的,有很多限制,第四章里,我们会讲修改内核的方法来确定性的监测时间片
二、哪些情况会导致时间片“过长”
如何来界定时间片过长,是一个技术活。
暂且,我们以4ms作为一个阈值,超过4ms的时间片情况,我们在这一章节里来讨论,就是说,我们暂且认为超过4ms的时间片”过长“,来讲解有哪些正常或不正常的情况。需要强调的是,线程的调度时间片超过4ms在很多情况下都是一个很正常的现象。
大致有以下几种情况会导致某个线程的时间片”过长“,按照cfs相关、rt相关、系统异常三大类来分类。
2.1 cfs调度逻辑相关的时间片过长情况
2.1.1 核上只有这一个cfs线程需要跑
如果这个核上只有一个线程需要运行,其他线程都因为某些原因是D或S的状态,那么这个核会一直执行这个cfs的线程
另外,需要注意的是,cfs不像rt有一个1秒种的执行时间上限的限制,所以,cfs的时间片,在这种特殊情形下,它是可以长时间占用100%的cpu的,不像rt线程在不修改刚说的1秒种的rt时间阈值上限的话(95%)永远不可能在一个核上执行超95%的
2.1.2 核上runnable的线程比较少
关于runnable的概念,参考我之前的博文 调度时延的观测-CSDN博客 里的解释
核上的runnable的线程比较少,会导致__sched_period在计算调度周期时会让每个runnable的线程得到的时间片变长一些,具体多长详细看里的逻辑,可参考 CFS及RT调度整体介绍_cfs rt 调度-CSDN博客 里的1.4.1 __sched_period 一节。
2.1.3 核上的正在运行的这个cfs线程的weight要比其他runnable的线程的weight都要高很多
weight高会导致cfs调度器给它更长的时间片,weight是权重的意思,具体参考之前我的博文 CFS及RT调度整体介绍_cfs rt 调度-CSDN博客 里的介绍,里面cfs的优先级权重和组调度章节都可以帮助理解这个概念,后面在讲cgroupv2和v1区别的博文里会讲一个实例来再次说明这个weight引起的若干现象,可以留意,这里并不展开
2.2 rt调度逻辑相关的时间片过长情况
rt线程在和cfs线程或者别的rt线程竞争时的逻辑比较简单,就是拼优先级。
我们假设线程A和线程B都在核C上,如果rt线程A的优先级高于rt线程B,或者线程A是rt,线程B是普通的cfs线程,那么如果A想执行,它会一直执行下去,这时候B就得不到调度的机会。这里面当然还是要排除掉刚才说的rt线程的1秒种的总运行时间超过了0.95秒这种情况(按照系统默认配置的话),如果rt线程的1秒种里的总运行时间超过0.95秒,那么剩下的0.05秒里系统并不会让任何rt线程执行会留给cfs线程执行。
2.2.1 某个rt线程比其他线程的优先级都高,且它一直需要执行
这个比较好理解,就不解释了
2.2.2 某段时间内,某个rt线程与该核上其他runnable的线程的优先级一样,且这个线程设的是FIFO,且当前得到了调度,那么它的一次时间片是0.95s(默认配置)
同样的实时优先级下,FF要比RR优先级更高,相关的实验结果在 CFS及RT调度整体介绍_cfs rt 调度-CSDN博客 里的 3. RT调度 里有。
2.2.3 某段时间内,某个rt线程与该核上其他runnable的线程的优先级一样,且都设的RR,那么它的一次时间片是100ms(默认配置)
同样的实时优先级下,多个RR的线程会轮转,轮转周期是100ms(默认配置)
2.3 系统异常
还有一种会导致某线程的调度时间片过长,是因为该线程使用了spinlock或其他函数关了抢占,导致系统不能按需调度。
具体由哪些情况会关抢占呢,我们会在后面硬中断软中断栏目里的博文里去详细展开,我们已经有一些常用的工具来监测这些异常的情况
三、使用系统已有的机制进行时间片监测
这一章并不是我们这边博文的重点,下面 3.1 里介绍的/proc/<pid>/schedstat 其实在 调度时延的观测-CSDN博客 的博文里已经介绍过了。3.2 里介绍了系统里默认并不开启但是可以动态开启的调度统计信息,但是这个统计信息它并不监控所有的情形,它是有条件的。
3.1 使用/proc/<pid>/schedstat进行时间片的模糊的监测
为什么说是模糊的监测,是因为这个节点它统计的是时间片的累加总值。你可以缩短周期,去多查询,从而计算出一个一段时间里的平均时间片长度。
/proc/<pid>/schedstat里有三个数值,第一个数值就是时间片的累加总值,第三个数值是时间片的总次数。详细细节及内核实现见 调度时延的观测-CSDN博客
3.2 使能kernel.sched_schedstats功能通过/proc/<pid>/sched查看最大时间片统计值
内核里有sched_schedstats这个动态开关来使能或者关闭调度统计值功能。这些统计值对应的内核结构体是:
上图中红色框出的就是最长时间片的统计值
3.2.1 开启sched_schedstats的方法
开启的方式比较简单:
sysctl -w kernel.sched_schedstats=1
查询的方式:
sysctl kernel.sched_schedstats
如果打开的话是下图的结果:
3.2.2 查看线程的调度统计值
默认情况下,sched_schedstats功能是关闭的,如果sched_schedstats功能是关闭的,那么/proc/<pid>/sched里并不会有这些统计值的栏目:
下图是没开sched_schedstats时cat /proc/1/sched的样子(下图并没有slice_max等调度统计信息):
如果开启后就可以看到slice_max等这些调度统计信息了:
3.2.3 调度统计信息里的时间的单位
我们以slice_max这个统计项来跟踪一下它的显示单位
我们先看slice_max在哪里设的,其实它只会在cfs里才被使用:
下图可以看到全局搜slice_max,在rt.c等cfs以外的调度器的代码里并没有slice_max:
回到slice_max的赋值逻辑,用的是:
而sum_exec_runtime的相关更新逻辑如下,用的是rq_clock_task,最终也是用local_clock也就是纳秒单位
所以,slice_max在记录时也是纳秒单位。
那么显示呢?
显示的逻辑的核心代码如下:
用的是PN_SCHEDSTAT宏,PN_SCHEDSTAT宏用到了__PSN宏
__PSN宏用到了SPLIT_NS宏
SPLIT_NS宏用了nsec_high和nsec_low
nsec_high和nsec_low在换算时除的1000000,那么显示单位就是ms,毫秒。
3.2.4 为什么slice_max在已经使能sched_schedstats看cfs线程也常常是显示0?
如下图:
跟代码可以看到,要满足下面红色框框的逻辑才会统计slice_max:
这个红色框框是什么意思?其实就是下图中的weight表里的数值,nice如果是4,则是423,nice如果是3,则是526:
所以,如果我们设了nice 3来启动程序,slice_max没有统计:
而我们设了nice 4来启动程序,slice_max就会有统计:
为什么要有这么一个判断,才去记录slice_max呢,其实逻辑上也很好理解,就是如果权重比较高,那么它的时间片长一些并没有什么关系,只有在线程的权重比较低时,记录这个slice_max用于衡量比较“nice”的任务是否执行了“过长”的时间。
3.2.5 调度统计信息里的exec_max和slice_max的区别
exec_max是每次调度的tick进来以后,更新调度信息时的和上一次更新的差值的统计,而一次时间片可能中间发生了多次tick,所以,这个统计项其实没什么意义。不过exec_max对于rt线程也会进行统计:
四、修改内核来监控每一次的时间片情况
这里说的是每一次,但是实际代码当然可以按需避开一些条件。
当然,我们可能确实在一些需求下,需要去监控每一次的时间片,这种监控老实说它虽然有影响还是影响的程序还是可控的,只要我们设置一些合适的阈值,避免大量的事件上报
4.1 普通cfs线程的时间片监控
其实上面在分析系统里自带的slice_max的统计的逻辑里,就可以看到cfs就有这样的逻辑,就是下图:
prev_sum_exec_runtime如下图,在cfs选择下一个调度实体时把当前执行时间统计值给记到prev_sum_exec_runtime里
在这个记录之前,计算出上一次调度时间片
可以看到,系统里的这个统计值的更新是一定程度上滞后的。换句话说,如果这个任务在被调度出去以后,一长段时间都没有得到下一次调度的话,那么这个最后一次时间片的统计值的更新是不会做的。
其实,我们可以通过在trace_sched_stat_runtime的相关逻辑里去提早去判断当前的这次调度的时间片是多少,这样时间片刚刚超出阈值的时候就可以拿到这个异常事件,上报得就很及时。
4.2 实时线程的时间片监控
对于实时线程而言,系统里并没有像cfs那样有slice_max相关的时间片的统计逻辑,我们可以参考cfs的这个slice_max的计算逻辑,把实时线程也记录和监控起来。
通过阅读代码和反复尝试,我们找到了可以在set_next_task_rt函数里保存sum_exec_runtime的数值,因为实时线程并不会使用cfs才会用的task_struct里的sched_entity里的prev_sum_exec_runtime,所以,可以保存到这里。
需要提示的是,实时线程虽然不会使用task_struct.sched_entity.prev_sum_exec_runtime,但是sum_exec_runtime还是会使用的,这也是我们可以这么去记录和计算的原因。
标签:rt,sched,过长,max,调度,线程,时间,cfs From: https://blog.csdn.net/weixin_42766184/article/details/143443569