转自:https://blog.csdn.net/u012728256/article/details/72639612

Linux上调度策略为SCHED_FIFO的实时进程是根据优先级抢占运行的。当没有更高优先级的实时进程抢占,而此进程又由于bug等原因长时间运行,不调度其它进程,系统就会出现无响应。这里要分析的RT throttling就是针对此种情况的,它通过限制每个单位时间内分配给实时进程的CPU运行时间,来防止上述情况的出现。

标准的设置是1s的时间内,实时进程的运行时间是950ms,其余的50ms时间给normal进程使用。

sched_rt_period_us值为1000000us=1s,表示单位时间为1s

sched_rt_runtime_us值为950000us=0.95s,表示实时进程的运行时间为0.95s。

这两个接口的实现代码如下:

Kernel/sysctl.c的kern_table片段

{

.procname = "sched_rt_period_us",

.data   = &sysctl_sched_rt_period,

.maxlen   = sizeof(unsigned int),

.mode   = 0644,

.proc_handler = sched_rt_handler,

},

{

.procname = "sched_rt_runtime_us",

.data   = &sysctl_sched_rt_runtime,

.maxlen   = sizeof(int),

.mode   = 0644,

.proc_handler = sched_rt_handler,

},

sched_rt_period_us接口设置的是sysctl_sched_rt_period变量,sched_rt_runtime_us接口设置的是sysctl_sched_rt_runtime变量。读写的实现都是通过sched_rt_handler函数,这里就不具体分析了。

我们知道了如何设置RT throttling,那么它是如何工作的呢?在实时进程的运行时间超出设定的阈值是如何处理的?

static void update_curr_rt(struct rq *rq)

{

if (curr->sched_class != &rt_sched_class) /*判断当前进程调度类*/

return;

/*运行队列现在的时间与当前进程开始运行时间之差* */

delta_exec= rq_clock_task(rq)-  curr->se.exec_start;

curr->se.sum_exec_runtime +=  delta_exec;  /*更新进程的真实运行时间*/

curr->se.exec_start =  rq_clock_task(rq);

if (!rt_bandwidth_enabled())  /*判断RT throttling是否开启*/

return;

for_each_sched_rt_entity(rt_se) { /*/*遍历此实时进程的调度单元*/*/

struct rt_rq*rt_rq =rt_rq_of_se(rt_se);

if (sched_rt_runtime(rt_rq)!=  RUNTIME_INF) {

rt_rq->rt_time +=  delta_exec;

/*rt_rq的运行时间是否超过了分配给它的时间片*/

if (sched_rt_runtime_exceeded(rt_rq))

resched_task(curr);

}

}

}

update_curr_rt函数用来更新当前实时进程的运行时间统计值,如果当前进程不是实时进程,即调度类不为rt_sched_class,则直接返回。

delta_exec值为此运行队列的当前时间与此进程开始运行时间之差,也即是此进程此次调度运行的时长。然后更新进程的真实运行时间和开始运行时间。

rt_bandwidth_enabled函数判断sysctl_sched_rt_runtime变量值是否大于0,如果此变量值设置为RUNTIME_INF(很大的负数),就关掉了RT throttling功能,这里就会直接返回。

然后遍历此实时进程的调度实体,找到相应的就绪队列,更新运行时间后,通过sched_rt_runtime_exceeded函数判断是否此实时进程是否超过了分配给它的时间片。

sched_rt_runtime_exceeded代码片段:

u64 runtime =sched_rt_runtime(rt_rq);   /*获取当前队列的最大运行时间*/

if (rt_rq->rt_throttled)    /*当前队列的实时调度受到限制*/

return rt_rq_throttled(rt_rq);

/*当前队列的最大运行时间大于当前队列的调度周期时间*/

if (runtime>= sched_rt_period(rt_rq))

return 0;

balance_runtime(rt_rq);

runtime= sched_rt_runtime(rt_rq);  /*重新获取当前队列的最大运行时间*/

if (runtime== RUNTIME_INF) /*关闭了RT throttling*/

return 0;

runtime值为当前队列的最大运行时间rt_runtime。rt_throttled字段表示当前队列的实时调度是否受到限制,如果受到限制了,就直接返回1,在update_curr_rt函数中就会调用resched_task函数执行进程切换,让出cpu。

如果当前队列的最大运行时间大于当前队列的调度周期时间,则返回0,这样此运行队列上的任务还能够继续运行。

balance_runtime函数在RT_RUNTIME_SHARE特性使能的情况下,如果当前队列的运行时间超过了最大运行时间,则可以从其他cpu上借用时间。具体代码这里先不分析,后面分析。

重新获取当前队列的最大运行时间runtime,如果值等于RUNTIME_INF说明关闭了RT throttling,则直接返回0。

sched_rt_runtime_exceeded代码片段:

if (rt_rq->rt_time >  runtime) {  /*累计运行时间大于最大运行时间*/

struct rt_bandwidth *rt_b =sched_rt_bandwidth(rt_rq);

if (likely(rt_b->rt_runtime)) {

rt_rq->rt_throttled =  1;

printk_deferred_once("sched: RT throttling activated\n");

} else {

rt_rq->rt_time =  0;

}

if (rt_rq_throttled(rt_rq)) { /*检查队列的实时调度是否受到限制*/

sched_rt_rq_dequeue(rt_rq); /*将调度实体从实时运行队列中删除*/

return 1;

}

}

如果累计运行时间大于最大运行时间,就会执行上面的代码片段。rt_b为运行队列rt_rq的进程组带宽控制结构体指针,如果rt_runtime即此进程组的任务运行时间额度值有效,则设置rt_throttled为1,表明此队列的实时调度受到限制,并打印出“sched: RT throttling activated”信息。接着检查队列的实时调度如果受到限制,则返回1,在update_curr_rt函数中让出cpu。

在前面讲到balance_runtime在当前队列运行时间超过最大运行时间后,可以从其他cpu上借用时间,下面具体分析代码看下是如何实现的。

static int balance_runtime(struct rt_rq *rt_rq)

{

if (!sched_feat(RT_RUNTIME_SHARE))   /*RT_RUNTIME_SHARE支持多个cpu间的rt_runtime共享*/

return more;

if (rt_rq->rt_time >  rt_rq->rt_runtime) {

raw_spin_unlock(&rt_rq->rt_runtime_lock);

more =do_balance_runtime(rt_rq);

raw_spin_lock(&rt_rq->rt_runtime_lock);

}

}

RT_RUNTIME_SHARE默认是使能的(见kernel/sched/features.h文件)

SCHED_FEAT(RT_RUNTIME_SHARE, true)

它表示支持多个cpu间的rt_runtime共享。如果不支持的话,就直接返回。

如果当前队列的累计运行时间大于最大运行时间,则调用do_balance_runtime函数。

do_balance_runtime函数代码:

struct rt_bandwidth *rt_b =sched_rt_bandwidth(rt_rq);

struct root_domain *rd =rq_of_rt_rq(rt_rq)->rd;

weight= cpumask_weight(rd->span);

rt_period =ktime_to_ns(rt_b->rt_period);  /*任务组一个控制周期的时间*/

for_each_cpu(i,rd->span) {

/*找到在另一个cpu上运行的同一任务组的运行队列*/

struct rt_rq *iter =sched_rt_period_rt_rq(rt_b,i);

if (iter== rt_rq) /*同一运行队列则跳过*/

continue;

if (iter->rt_runtime ==  RUNTIME_INF) /*RT throttling关闭,不允许借用时间*/

goto  next;

diff =iter->rt_runtime -iter->rt_time;  /*最大能够借用时间*/

if (diff> 0) {

diff =div_u64((u64)diff,weight);

if (rt_rq->rt_runtime +  diff> rt_period)

diff =rt_period -  rt_rq->rt_runtime;  /*修正后可借用*/

iter->rt_runtime -=diff;

rt_rq->rt_runtime +=diff;

more =1;

if (rt_rq->rt_runtime ==rt_period) {/*满足条件退出,否则继续从其他cpu借用*/

break;

}

}

next:

}

rd->span表示此调度域的rq可运行的cpu的一个mask,这里会遍历此mask上的cpu,如果对应的cpu的rq和当前的rq是同一运行队列,则直接跳过;如果对应的cpu的rq已关闭RT throttling功能,则不允许借用时间。内核中关于这块代码的注释是:

Either all rqs have inf runtime and there's nothing to steal or __disable_runtime() below sets a specific rq to inf to indicate its been disabled and disalow stealing.

大概意思是如果所有的运行队列都设置为RUNTIME_INF即关闭了RT throttling功能,则没有时间可以借用。或者某个指定的运行队列调用__disable_runtime()函数,则不允许别的借用自己的时间。

diff是iter运行队列最大能够借用的时间,后面经过修正后,将diff加入到rt_rq的最大可运行时间上。如果新的最大可运行时间等于此任务组的控制周期的时间,则不需要接着再从其他的CPU上借用时间,就直接break退出。

实时进程所在的cpu占用超时,可以向其他的CPU借用,将其他CPU的时间借用过来,这样此实时进程所在的CPU占有率达到100%,这样做的目的是为了避免实时进程由于缺少CPU时间而向其他的CPU迁移,减少不必要的迁移成本。此cpu上为绑定核的普通进程可以迁移到其他cpu上,这样就会得到调度。但是如果此CPU上有进程绑定核了,那么就只有在这里饿死了。

RT throttling分析【转】的更多相关文章

  1. cpu进程调度---RT Throttling【转】

    转自:http://book.2cto.com/201302/16291.html RT Throttling是对分配给实时进程的CPU时间进行限制的功能.使用实时调度策略的进程由于bug等出现不可控 ...

  2. 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #9 RT Group Scheduling 与RT Throttling

    HACK #9 RT Group Scheduling 与RT Throttling 本节介绍对实时进程所使用的CPU时间进行限制的功能RT Group Scheduling和RT Throttlin ...

  3. Linux进程组调度机制分析【转】

    转自:http://oenhan.com/task-group-sched 又碰到一个神奇的进程调度问题,在系统重启过程中,发现系统挂住了,过了30s后才重新复位,真正系统复位的原因是硬件看门狗重启的 ...

  4. Linux进程管理 (9)实时调度类分析,以及FIFO和RR对比实验

    关键词:rt_sched_class.SCHED_FIFO.SCHED_RR.sched_setscheduler().sched_setaffinity().RR_TIMESLICE. 本文主要关注 ...

  5. bug:进程可调用函数而子线程调用报错

    在调试摄像头时遇到问题:在主进程里调用下述函数能够成功,但在子线程里创建时总是失败,错误打印为 sched: RT throttling activated. UniqueObj<OutputS ...

  6. 《Linux内核精髓:精通Linux内核必会的75个绝技》目录

    1章 内核入门HACK #1 如何获取Linux内核HACK #2 如何编译Linux内核HACK #3 如何编写内核模块HACK #4 如何使用GitHACK #5 使用checkpatch.pl检 ...

  7. Unity Shader:Blur

    花了一晚上的时间终于看懂Image Effect中的Blur,其实很简单,就是一下子没有理解到. 原理:使用两个一维[1*7]的高斯滤波模板,一个用在x方向,另一个用在y方向.高斯滤波有模糊的效果. ...

  8. tcprstat的使用方式

    两种使用方式:1)本机直接在线采集:2)分析tcpdump采集到的离线pcap文件   1. 本机直接在线采集 参数:   -p :指定只采集此TCP port的请求   -t  : 采集输出的时间间 ...

  9. 2018CCPC-女生专场

    (咕咕咕,咕了快一年的bu题.. A.CCPC直播 传送:http://acm.hdu.edu.cn/showproblem.php?pid=6297 题意:rt. 分析:模拟. #include&l ...

随机推荐

  1. PHP性能优化:in_array和isset 在大数组查询中耗时相差巨大,以及巧妙使用array_flip

    今天在PHP业务开发中,发现了一个问题. 两个较大数组(20万+元素),遍历其中一个$a,另一个数组$b用于查找元素. 比如 foreach($a as $val){ if(in_array($xx, ...

  2. 【接口时序】6、IIC总线的原理与Verilog实现

    一. 软件平台与硬件平台 软件平台: 1.操作系统:Windows-8.1 2.开发套件:ISE14.7 3.仿真工具:ModelSim-10.4-SE .ChipScope 硬件平台: 1. FPG ...

  3. 【Swift 3.0】iOS 国际化切换语言

    有的 App 可能有切换语言的选项,结合系统自动切换最简单的办法: fileprivate var localizedBundle: Bundle = { return Bundle(path: Bu ...

  4. alert执行时机和js线性模型 事件循环

    <div class="test">测试内容</div> <script> $('.test').text('内容改变') alert($('. ...

  5. Flask上下文管理源码分析 ——(3)

    引出的问题 Flask如何使用上下文临时把某些对象变为全局可访问 首先我们做如下的几种情况的假设 情况一:单进程单线程 这种情况可以基于全局变量存储临时的对象 情况二:单进程多线程 这种情况会出现多个 ...

  6. Data Governance

    https://erwin.com/blog/data-preparation-mapping/

  7. Vuex、axios、跨域请求处理和import/export的注意问题

    一.Vuex 1.介绍 vuex是一个专门为Vue.js设计的集中式状态管理架构. 对于状态,我们把它理解为在data中需要共享给其他组件使用的部分数据. Vuex和单纯的全局对象有以下不同: 1. ...

  8. Django之缓存、信号和图片验证码

    一. 缓存 1. 介绍 缓存通俗来说:就是把数据先保存在某个地方,下次再读取的时候不用再去原位置读取,让访问速度更快. 缓存机制图解 2.Django中提供了6种缓存方式 1. 开发调试 2. 内存 ...

  9. 【Spring】Spring bean的实例化

    Spring实现HelloWord 前提: 1.已经在工程中定义了Spring配置文件beans.xml 2.写好了一个测试类HelloWorld,里面有方法getMessage()用于输出" ...

  10. 【JVM】深度分析Java的ClassLoader机制(源码级别)

    原文:深度分析Java的ClassLoader机制(源码级别) 为了更好的理解类的加载机制,我们来深入研究一下ClassLoader和他的loadClass()方法. 源码分析 public abst ...