今天要说的,是系统挂起问题。许多做嵌入式软件的团队其实并不了解嵌入式软件的特点,在他们的眼里,只有软件问题;因此出了问题的时候,才开始抱怨这个没有,那个也没有,总之,能在电脑上用的工具方法都没有。其实这些只能怨自己,嵌入式系统不能是个黑盒子,否则建立在上面的应用程序迟早会出问题。
先说说系统挂起有哪些特征。
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个字节)就足够勾画出程序调用关系了!
下次,要讲讲内存泄漏或是动态加载、动态不定等问题。