zoukankan      html  css  js  c++  java
  • 嵌入式系统如何解决死循环类挂起

    今天要说的,是系统挂起问题。许多做嵌入式软件的团队其实并不了解嵌入式软件的特点,在他们的眼里,只有软件问题;因此出了问题的时候,才开始抱怨这个没有,那个也没有,总之,能在电脑上用的工具方法都没有。其实这些只能怨自己,嵌入式系统不能是个黑盒子,否则建立在上面的应用程序迟早会出问题。

    先说说系统挂起有哪些特征。

    1. 系统crash,但因为没使能狗,系统未复位。
    2. 硬件故障,同样没使能狗,系统陷在某个中断处理中,未复位。
    3. 软件代码死循环,可能是局部的低级编码错误,也可能是任务某个判断条件失败导致的异常分支。
    4. 软件设计不合理导致某些处理过程,连续占用太多CPU时间,导致系统假死,卡死。

    Case 1适用Coredump方法解决,Case 2比较复杂,需要根据实际情况处理。我要说的,是针对Case 3 & 4的通用解决方法。

    开始之前,得先说说多任务系统的切换。所以能切换,离不开两个条件:当前任务主动弃权;当前任务被剥权而强制切换。

    主动弃权,就是执行系统调用,比如msgq_send,sendto,malloc,fread等函数,这类函数会涉及系统核心或是信号量处理,因此CPU会在用户态和系统态间切换。调用发生时,系统从用户态转为系统态,当前任务即被挂起,放回调度队列;当CPU离开系统态时,调度就能够检查调度队列,激活任务等待队列最前面的任务。而这个最前面的任务,很多时候并不是原来的应用。最典型的,就是msgq_send调用。平常做功能测试时,不会有并发的消息,整个处理过程都是线性化的,一个任务处理完,发消息给下一个任务处理,就这样,一个任务接着一个任务,直到处理结束。这时候的系统,任务优先级其实没什么用的,因为任务运行的触发事件就一个,除了那个正在做事的,大家都在等,也只能等。

    强制切换,需要系统中断来实现,可以是外部硬件中断,也可以是CPU内部的时钟中断。外部硬件中断,比如网口接收到新的数据,就会以中断方式通知CPU接收。中断发生后,当前应用就被被挂起,送回调度队列。中断处理完成后,CPU转入系统模式,而不是回到用户模式,此时自然可以执行调度处理。当然,凡事没有绝对,比如快速中断就不一定是这么做,且各个OS对细节有自己的权衡。时钟中断,包括提供给应用程序的定时器时钟和Tick时钟。定时器时钟是涉及比较多的,一般微小系统里,会不作封装,直接使用。Tick时钟,就是这里要讲的关键了。

    Tick中断是提供给OS进行任务调度切换用的,以避免某些任务长期占据CPU,实现软件任务间的切换。要知道,中断并不是总有的,主动弃权也不应总发生,总之,OS调度还得给自己留一手,在每个Tick到来的时候,睁眼看看要不要做点啥。

    好了,哥掏出个大盒子,打开一层又一层,现在,终于到最后火柴盒。现在,回到怎么解决系统挂起问题。

    嵌入式系统OS一般都会提供TickAnnounce这样的接口或是钩子。没有也没关系,直接挂Tick中断,不过别忘记在自定义的TickAnnounce里面调用原系统的TickAnnounce。TickAnnounce是OS任务调度睁眼后必做的,当然还做什么,就是TickAnnounce决定的了!

    我们要让TickAnnounce做什么呢?计数。
    总共有三组数据需要统计:系统态计数,中断态计数,以及用户任务计数。准确点说,就是TickAnnounce每次运行的时候,先判断Tick发生时的模式,并将对应的计数+1。当然,每个用户任务都是一个独立的计数,TickAnnounce直接取当前任务的TCB获得对应的计数器。嵌入式系统TCB总是开放给用户可见的,且里面总会提供几个用户自定义字段。我们要做的,就是将其中一个用户自定义数据字段,作为我们的计数器使用。
    这里有两点需要注意:计数器是需要初始化和重置的,正常情况下,千万别在应用任务栈上对其进行修改,而应该是用一个简单的0/1开关通知TickAnnounce来完成;TickAnnounce里不可以出现任何打印,必要时可以有极少发生的msgq_send,计数器的读出,也应该尽量由TickAnnounce写入特定的内存后,应用从对应内存块获取。

    做完上面的工作,我们就有了个CPU Utilization工具了。接着,我们需要定义一个策略,让系统自动定期检测高负载任务或是中断处理。哥喜欢用过载门限来决定是否有异常,就是额外增加一个总TickAnnounce计数,在总TickAnnounce进入一个门限区间时,连续检查每次Tick计数对象,当达到过载门限时,TickAnnounce即记录当前模式及任务堆栈信息。跟coredump所不同的是,不能只抓一次现场,而需要抓很多次。当然,还有处理超过统计区间后,重新开始统计,否则因为时间跨度变大后,峰值数据就不明显了!

    这时候,我们又要用SRAM了。嗯,SRAM真的是太重要了,哥做的很多嵌入式系统都离不开它!
    为啥不能用RAM呢?TickAnnounce肯定不能写flash,不解释,不懂的回头看上面。写内存是挺好的,但那样又有什么用呢,系统都挂起了,RAM里的内容,也没法看啊,最后只能是白忙活了!写SRAM就不一样了,系统只有不掉电,总还是在那,系统重启后,想怎么读出来都成。

    哦,好像忘记说,抓n多次现场后,记得触发CPU复位!当然,如果系统有狗,那就在被狗咬死前,尽可能多的抓取现场信息。


    后记:
    1,SRAM总是很有限的,才数K空间,记得这个和coredump不会同时发生,要重用那个空间;
    2,堆栈完整信息,一次就能填满SRAM,这当然不行,抓取时,应该只关注顶部的数个函数调用,一般3~4个(一次现场大概16~20个字节)就足够勾画出程序调用关系了!

    下次,要讲讲内存泄漏或是动态加载、动态不定等问题。

  • 相关阅读:
    再见了亲爱的学生们,再见了敬爱的同事们,再见了信狮
    #if defined 和#ifdef的区别
    C# ASP.NET Core开发学生信息管理系统(二)
    modelsim与debussy联合的问题
    C#批量生成大数据量无重复随机数据的另类高效实现
    DataColumn.Caption属性应用到DataGridView.HeaderText的方法
    [转载]安装网银安全控件
    近期学习SQL Server,收藏的几个学习教程网址备忘
    asp.net根据域名查ip C#版
    asp.net 2.0教程 成员资格和角色管理
  • 原文地址:https://www.cnblogs.com/hhao020/p/5015957.html
Copyright © 2011-2022 走看看