来源:公众号【鱼鹰谈单片机】作者:鱼鹰OspreyID   :emOsprey这篇笔记有如下内容:1、为什么需要计算各个线程的CPU使用率?2、该如何计算线程CPU使用率?3、FreeRTOS线程计算的弊端?如何打破 FreeRTOS 线程计算方式的时间限制?4、关键代码介绍。上次介绍了如何计算整个系统的CPU使用率:《单片机里面的CPU使用率是什么鬼?》《实操RT-Thread系统CPU利用率功能添加》但是却没有介绍该如何计算每个线程(任务)的CPU使用率。为什么要计算线程CPU使用率?首先要问的是,为什么要计算线程的CPU使用率,有啥用?我们知道系统的CPU使用率关注的是整个系统的使用情况,使用率越低,表示越能更及时的响应外部情况,整个系统的性能也会越好。但这是从系统整体考量的,并不能反映单个线程的执行情况。比如虽然整体的CPU使用率是30%,但是有一个线程占据了25%的使用率,一个线程使用率是5%,那么你肯定会想,为啥这个线程需要占用这么高的CPU使用率,是不是代码写的有问题,是不是代码可以优化一下?当系统运行时,如果你能实时观察各个线程的CPU使用率,那么你就能知道平时这个线程的CPU使用情况是怎样的,为什么后来又高那么多,那么你就可以由此分析出这个线程可能出现了问题,也就可以针对性的进行检查了。这点对于合作开发的项目更是明显,很多时候因为有些线程的代码不是自己写的,所以根本不知道代码执行情况,一旦系统出现问题,那么可能就是互相甩锅了。而当计算了线程的CPU使用率,一旦发现某个线程执行异常,那么就能交给负责的人去查看了。所以说,使用操作系统的项目是非常有必要计算各个线程(任务)的CPU使用率的。就好比你的电脑,风扇嗡嗡响(CPU高负荷运行),如果只有一个系统CPU使用率,发现高达90%,但是你却根本不知道为什么这么高,所以只能重启。而一旦有了进程CPU使用率,查看一下哪个进程CPU使用率高,把对应的进程关闭就行了,根本不需要重启电脑。

如何计算线程CPU使用率?那么现在就来看看该如何计算各个线程的CPU使用率。从前面的笔记,我们其实也可以猜测该如何计算,无非就是获取每个线程的执行时间罢了。比如,1秒时间内,空闲任务执行700毫秒,任务1执行200毫秒,任务2执行100毫秒,那么各个任务的CPU使用率分别是 70%、20%、10%。以前计算系统的CPU使用率的时候,采用了软件方法计算空闲任务的运行时间,这必然是不够准确的,所以最好的方式是采用硬件计时。因为鱼鹰采用STM32F103进行测试,所以使用DWT外设进行精确计时,不过麻烦的是,在KEIL 软件仿真情况下,DWT外设是无法工作的,所以如果要测试的话,必须使用硬件仿真的方式,不过如果真要KEIL软件仿真的话,也不是没有办法,就是使用硬件定时器,这个按下不表。毕竟,DWT外设的功能在这里说白了也就是个定时器而已。既然要获取线程的执行时间,关键一点就是,我们要知道操作系统什么时候会切换到某一个线程运行,什么时候又会从这个线程切出,到另一个线程执行呢?这个关键还是在系统内置的钩子函数。上次的笔记鱼鹰介绍过空闲钩子函数,今天介绍另一个钩子,任务切换钩子函数。这个钩子函数的特点就是,每当系统需要切换到下一个任务时,就会先执行这个函数。这个函数一般有两个参数,当前任务和即将切换的任务。只要设置任务切换的钩子函数,并且有时间戳,那么计算一个任务的执行时间也就不那么困难了。比如,操作系统在时刻12345 ms 切换到空闲任务执行,突然一个任务就绪,开始准备执行,所以在时刻12445切换到那个就绪任务执行,那么空闲任务的执行时间我们也就可以准确计算出来了。12445 – 12345 = 100 ms也就是说,这一次空闲任务执行了 100 毫秒。如果我们要计算单位时间(比如1秒内)空闲任务的执行时间,我们只要在每次运行到空闲任务时累计时间即可。比如1秒内,空闲任务执行了 5 次,分别是 10、200、100、200、50,累计时间为10 + 200 + 100 + 200 + 50 = 560毫秒由此,可计算空闲任务的CPU使用率为 56%,从而可计算出系统的CPU使用率是44%。是的,通过线程的CPU使用率方法,我们其实也可以计算整个系统的CPU使用率。而且这种计算方式比前面所说的计算方法更准确,更科学。前面采用时间戳进行计算,但是时间戳是会溢出的,那个时候,你的时间计算还是准确的吗?FreeRTOS线程计算限制?现在鱼鹰就来说说第三个问题,FreeRTOS线程计算的弊端?如何打破 FreeRTOS 线程计算方式的时间限制?从网上查找FreeRTOS任务CPU计算相关的资料,可以得到以下信息:1、需要开一个定时器,这个定时器中断频率是操作系统时钟的十几倍(为了保证计算精度)。2、一个64 位的变量在定时器自加更新,一旦变量溢出,时间计算就会出现问题。(相关细节可查看安富莱教程)第一个问题会导致系统性能下降(中断频率太高,一般是微秒级别的),而第二个问题导致在一段时间内(小时级别)线程CPU使用率计算准确,超出时间后,计算会有问题,所以教程中不建议在正式版本加入此功能。第一个问题其实很好解决,就是使用硬件定时器,不再由CPU去更新时间,这样不会占用CPU时间,第二个问题其实也非常好解决,就是通过《延时功能进化论(合集)》的方式解决溢出问题,这里不再展开说其中的奥妙。任务切换钩子函数的实现总之,鱼鹰接下来的实现方式解决了以上两个痛点,即使无限执行下去,也不会影响到计算精度问题,唯一对系统产生的一点影响,只有在任务切换时消耗的一点计算时间(微秒级别)。那么先上任务切换钩子函数关键实现代码(RT-Thread):

void thread_stats_scheduler_hook(struct rt_thread *from, struct rt_thread *to){    static uint32_t schedule_last_time;        uint32_t time;        time = get_curr_time();        from->user_data   += (time - schedule_last_time);    schedule_last_time = time;}

如何将这个函数注册到操作系统中被系统调用呢?通过这个函数即可:那么现在来分析这个钩子函数实现:一个静态变量,用于记录切换时的时间戳。每次任务开始切换时,更新这个时间戳,同时累积时间,这个时间保存在当前任务的user_data里面。难理解?看下图就清楚了。假设系统调度是从任务1切换到任务2,即from为任务1,to为任务2,此时获取的时间戳为 T1。上一次的时间戳我们已经通过静态变量保留了,这里为T0,那么T1-T0就是from任务即任务1在本次运行的时间,只要下次运行任务1时继续不断的累积这个时间,那么就可以得到任务1的总运行时间。任务2同理。当然我们不可能一直累积下去,不然肯定会溢出,所以隔一段时间就需要清零,这个时间其实就是线程CPU计算的周期。    这里还有一个函数没有说,就是 get_curr_time(),在这里使用DWT,为了可以重新实现该函数,鱼鹰使用了弱属性 weak(关于这个看参考:《困惑多年,为什么 printf 可以重定向? 》)。

__weakuint32_t get_curr_time() {    return DWT->CYCCNT; // don't use the function rt_tick_get()}

这里可以看到有个注释,不要使用 rt_tick_get 函数,为啥?精度太低,有些任务本来执行了的,但是因为执行时间小于操作系统的时钟(比如1毫秒),那么就无法累积时间了,那么即使这个任务运行再多,时间累积也为 0,这肯定是我们不希望看到的。然后再说一个点,为了简化代码(钩子函数代码只有短短几行),鱼鹰这样的实现是有两个问题的。1、首次运行计算有误,因为静态变量应该在运行任务之前就初始化的(不应该初始化为 0),而钩子函数是在任务运行之后才调用的,所以从开机以来的时间被累加到第一个运行任务中了,这肯定是有问题的,不过后面随着系统的运行,静态变量被持续更新,就不会再出现这个问题了。2、为了减少修改,鱼鹰把线程的use_data当成一个变量使用了,实际上这个变量的功能应该是存储线程私有变量地址的,但是因为鱼鹰懒得修改太多代码,所以直接拿来用了。正因为如此,所以鱼鹰添加线程CPU计算时,只要修改很少的代码就可以了。线程CPU的计算目前我们已经能够通过钩子函数获取各个线程的CPU执行时间,现在就看该如何计算了。为了计算各个线程的CPU使用率,我们需要确定计算周期,这里我们可以设置1秒计算一次。其次,我们需要确定在哪个任务执行计算。原理上来说,可以是系统中的任何一个任务,但是为了减少对系统的干扰,可以将计算工作放到优先级比较低的任务中进行,比如空闲任务。现在,看看函数是如何实现的:

// can call the function 1 s (max 60s when stm32f1xx because of dwt)void thread_cal_usage(thread_run_info_def *run_info){    static uint32_t total_time_last;         uint32_t time, total_time;    struct rt_list_node *node;    struct rt_list_node *list;    struct rt_thread *thread;    uint32_t i;     rt_enter_critical(); // 关闭系统调度,防止在计算过程中更新线程时间,影响计算    time = get_curr_time(); // 获取当前时间戳    total_time             = time - total_time_last;   // 计算运行总时间    total_time_last = time; // 更新时间        list = &(rt_object_get_information(RT_Object_Class_Thread)->object_list); // 获取线程列表指针  // 搜索类别    for(i = 0, node = list->next; (node != list) && i < THREAD_NBR_MAX; node = node->next, i++){        thread            = rt_list_entry(node, struct rt_thread, list); // 获取线程地址        run_info[i].name  = thread->name;     // 保存线程名        run_info[i].time  = thread->user_data;  // 保存线程执行时间        thread->user_data = 0;          // 清除线程执行时间    }            rt_exit_critical();// 开启系统调度      // 计算各个线程的 CPU 使用率    total_time /= 100;     if(total_time > 0){        for(uint32_t j = i, i = 0; i < j; i++)        {            run_info[i].usage = run_info[i].time / total_time;        }    }}

注释已经很详尽了,所以不多做讨论。主要说以下几点:1、为什么需要关闭调度器,可以使用关中断吗?关调度器是为了防止在获取各个线程执行时间时,因为系统调度而导致执行时间被更新,从而导致计算有误,所以需要关闭调度器。那么为什么不使用关中断的方式呢?没有必要。一旦关中断,那么中断就无法响应了,所以在可以关调度器的情况下满足要求,就不应该关中断。2、为什么分两步计算,为什么不将最终的计算放在第一个循环中执行呢?节省时间,为了尽量减少关调度器的时间,能省一点是一点。毕竟只要能获取到关键信息,啥时候计算都一样。3、因为线程CPU计算周期是自动计算的,所以,计算周期其实就是该函数的调用周期,即2秒调用一次,那么线程CPU计算周期就是2秒,但是需要注意的是,调用周期必须小于定时器的溢出时间,即当你使用 DWT 时,调用周期应该在 60 秒以下(72 M 系统时钟),否则计算是有问题的。现在我们已经算是完成了线程CPU计算问题,但为了使用方便,我们需要把它打印出来,或者把这些信息字符串化:

void thread_stats_print(void){    thread_run_info_def run_info[THREAD_NBR_MAX] = {0};    thread_run_info_def *p_info;    thread_cal_usage(run_info);        rt_kprintf("thread\t\t\ttime\t usage\n");    for(uint32_t i = 0; i < THREAD_NBR_MAX; i++)    {        p_info = &run_info[i];         if(p_info->name != NULL)        {            if(p_info->usage > 0)  // CPU 使用率大于 1 %            {                    rt_kprintf("%-16s\t%u\t%2u%%\n",  p_info->name,                                                      (uint32_t)p_info->time,                                                      (uint32_t)p_info->usage);            }            else              {                    rt_kprintf("%-16s\t%u\t<1%%\n", p_info->name,                                                    (uint32_t)p_info->time,                                                     (uint32_t)p_info->usage);            }        }        else        {            break;        }    }}

这里将线程名、线程执行时间、线程使用率都打印出来了,但是需要注意的是,这里的time 时间单位是定时器的单位,而不是微秒、毫秒,比如如果使用 DWT,那么单位就是 1/72 微秒,即如果 time 值为 1000,那么换算到微秒,应该是 1000/72 秒,当然了,你也可以在打印的同时就把时间换算一下,这个自由发挥就好。最后,鱼鹰将代码提交到了 RT-Thread 官方工程里面,这是鱼鹰第一次使用 Git 提交开源项目(操作不熟练,还是上网搜的教程),也不知道最后合并了没有。如果对完整代码感兴趣的,也可以在后台回复关键字领取。

推荐阅读:

终极串口接收方式,极致效率

为什么说你一定要掌握 KEIL 调试方法?

延时功能进化论(合集)

指针,很难吗?| 解析指针的过程与意义(一)

如何写一个健壮且高效的串口接收程序?

KIEL 调试那些事儿之窗口展示——变量(二)

打了多年的单片机调试断点到底应该怎么设置?| 颠覆认知

-THE END-


如果对你有帮助,记得转发分享哦

微信公众号「鱼鹰谈单片机」

每周一更单片机知识

长按后前往图中包含的公众号关注

鱼鹰,一个被嵌入式耽误的畅销书作家

个人微信「EmbeddedOsprey」

长按后打开对方的名片关注

cpu使用率_线程CPU使用率到底该如何计算?相关推荐

  1. 计算机CPU哪家好,2019年电脑cpu排行榜_电脑CPU哪个好 电脑CPU排行榜2019

    电脑CPU哪个好 电脑CPU排行榜2019 JPG,1905x4429,231KB,249_583 电脑处理器排行榜2019 JPG,1080x2000,231KB,500_925 2019年最新台式 ...

  2. c++ 多核cpu序列号_关于 CPU 的一些基本知识总结

    优质文章,及时送达 作者 | 骏马金龙 链接 | cnblogs.com/f-ck-need-u/p/11141636.htm 关于CPU和程序的执行 CPU是计算机的大脑. 1.程序的运行过程,实际 ...

  3. mysql监控内存cpu使用率_监控 cpu 内存 网卡的使用情况的一个命令 比较实用

    sar -r -u -n DEV 1 1000 >/data/200.sql  2>&1 & 监控结果: 02:51:11 PM kbmemfree kbmemused  ...

  4. 台式电脑cpu排行榜_台式电脑CPU性能天梯图 AMD性能首次反超intel

    随着手机.平板.笔记本电脑的流行,台式电脑被谈起的几率越来越少,可以说是越来越不受重视了. 不过就总体性能来说,还是台式电脑性能更强悍.台式电脑以更强悍的性能,更大的屏幕显示器,在办公和游戏方面,仍然 ...

  5. amd cpu排行_最新CPU性能排行榜,兼顾游戏和专业的究极神器

    是的,你们没有看错!那个帅气的我,又来了.这次给大家带来了最新(目前主流最强)的CPU排行榜.这个榜单说真的还不是我自己弄的.而是由德国一家评测媒体PCGH公布的相对权威排行榜,涵盖了AMD.Inte ...

  6. 易语言mysql线程池数量_线程池最佳线程数量到底要如何配置?

    前言 对应从事后端开发的同学来说,线程是必须要使用了,因为使用它可以提升系统的性能.但是,创建线程和销毁线程都是比较耗时的操作,频繁的创建和销毁线程会浪费很多CPU的资源.此外,如果每个任务都创建一个 ...

  7. 线程CPU使用率到底该如何计算?

    来源:公众号[鱼鹰谈单片机] 作者:鱼鹰Osprey ID   :emOsprey 这篇笔记有如下内容: 1.为什么需要计算各个线程的CPU使用率? 2.该如何计算线程CPU使用率? 3.FreeRT ...

  8. java获取cpu使用率_再一次生产 CPU 高负载排查实践

    前言 前几日早上打开邮箱收到一封监控报警邮件:某某 ip 服务器 CPU 负载较高,请研发尽快排查解决,发送时间正好是凌晨. 其实早在去年我也处理过类似的问题,并记录下来:<一次生产 CPU 1 ...

  9. qt获取cpu使用率_又一次生产 CPU 高负载排查实践

    以下文章来源于crossoverJie ,作者crossoverJie 前言 前几日早上打开邮箱收到一封监控报警邮件:某某 ip 服务器 CPU 负载较高,请研发尽快排查解决,发送时间正好是凌晨. 其 ...

最新文章

  1. Faster-RCNN
  2. 成功解决ModuleNotFoundError: No module named 'keras_retinanet'
  3. Linux定制登录欢迎语
  4. VIO-slam 系统构建
  5. 修改linux资源限制参数命令,linux passwd命令参数及用法详解--linux修改用户密码命令...
  6. php数组10000分割1000_PHP切割整数工具类似微信红包金额分配的思路详解
  7. undo_management设置与隐含参数*._offline_rollback_segments和*._corrupted_rollback_segments关系...
  8. dede 验证码不显示 vdimgck.php,织梦(dedecms)后台登录验证码不显示或不正常的解决方法...
  9. android 图标错误的是什么,如何修复:android.app.RemoteServiceException:从包中发布的错误通知*:无法创建图标:StatusBarIcon...
  10. Vue中无法更改element ui组件样式问题
  11. 重置物体的position, rotation, scale,复制物体的组件
  12. 解决mescroll固定位置上拉加载无效果?真的只需一步
  13. QNX系统MfgTool烧写工具脚本说明
  14. 程序员有必要掌握 TDD 吗?
  15. 带“小弟”其实是一种投资
  16. 代码复杂度分析——时间、空间复杂度
  17. 如何打开不同格式的图片?图片格式转换的方法又有哪些?
  18. B站和字节跳动,必有一战
  19. 大数据告诉你哪部电影最有影响力
  20. 利用python把dcm格式转化为jpg格式

热门文章

  1. 【算法】剑指 Offer 53 - II. 0~n-1中缺失的数字
  2. 20-100-040-安装-Centos 7.5 安装MYSQL
  3. 【elasticsearch】elasticsearch 熔断器
  4. JavaCC报错: JavaCC reported exit code 1: [-LOOKAHEAD=1, -STATIC=false
  5. Kafka : kafka重启报错 ZkClient allready closed
  6. scala学习-scala中的特殊符号使用
  7. linux ls 目录结构,linux 系统目录结构 ls命令 文件类型 alias命令
  8. Java 线程池的实现原理,你真的理解吗?
  9. 小妙招:如何防止你的 jar 包被反编译?
  10. 感觉Swagger功能不够强大?knife4j这款神器了解一下....