一个static引发的*
在一个阳光明媚休假的周末,一个static引发了一场就惊天动地的*……
案情回顾
周末在家接到同事电话说跑在Vx下的程序刚启动不久就导致操作系统重启。明明周五下班前自己是调试过没有问题的。周一一大早来到联试现场,启动程序,的确不到一分钟操作系统就自己重启了。反复试验数次,均复现。既然能复现就好办了。找到上周五提交的代码,简单分析后初步怀疑是一个RapidIO接收回调函数导致系统重启了。
案情还原
本应用是运行在VxWorks系统下的一个C++应用程序,底层通过注册回调函数的形式从RIO驱动收取消息。问题就出在了这个回调函数。通过底层驱动及本应用的打印日志可以看出,程序只要执行到这个回调函数即会造成系统自动重启。将此函数体全部注释屏蔽,则问题消失。该函数只完成一个功能,从RIO收到数据后放入解析消息的任务队列。此前一直正常工作。周五在该回调函数头上添加了几行time stamp的语句,就导致系统重启。因此着重调查相关语句。
案情调查
在回调函数头打时间stamp的语句如下:
void rioRcvHandler(void)
{
static int freq = sysClkRateGet();
static int now = tickGet();
int d = tickGet() - now;
now += d;
logMsg((char*)"%s recv data(d=%dticks=%dms, now=%dticks.)\n", \
(int)__FUNCTION__, d, d*1000/freq, now);
...
}
因为调试过程中发现该函数经常在同一个tick时刻连续收到完全相同的两包数据,故加上了以上代码检测函数被调用频率。 sysClkRateGet只调用一次计算系统时钟频率。前后两次tickGet()的差值换算成时间单位ms。给静态变量赋一次初值,这是C++程序很常用的语法,编译通过,上周五也能正常运行。为何过了一晚上就会莫名造成系统重启。后经排查发现程序运行的目标板由A厂家的PPC换成B厂家的PPC。也就是说其实应用程序本身没有问题,而是目标板也就是系统驱动造成的?可为何将以上打时间stamp的语句注释掉后问题就消失了?
经过咨询B厂家的驱动开发负责人,了解到A厂家的驱动是在task context中调用的应用程序回调函数,而B厂家的驱动则是在interrupt context中调用应用程序回调函数。这样问题就简单一些了。也就是说本应用中的回调函数在中断服务上下文中会造成系统重启。换句话说以上代码在中断上下文中不符合规范。
后来翻阅VxWorks的手册,关于编写中断服务函数及中断服务函数的限制,有这么一段:
也就是说,由于VxWorks的中断服务程序并不在任务上下文环境运行,即所有中断服务函数共享一个stack空间,因此中断服务函数的一个基本限制就是其不能调用可能导致中断函数阻塞的函数。例如,不能在ISRs中take信号量。
同时由于malloc()和free()函数都要take信号量,因此也不能在ISRs中被直接或间接调用。同样,也不能在ISRs中创建或删除任何的C++对象了,因为C++对象的构造和析构函数。
故C++代码有以下一些限制:
- intConnect() 连接的中断服务函数一定不能是非静态函数或对象的成员函数
- 中断服务代码中不能创建或删除对象
- 中断服务代码中的C++必须使用嵌入式C++代码,没有异常处理,没有运行时类型识别
解决方法
其实在一开始,案发现场的代码是可以有很多方式可以避免发生的。但 static int n = GetNum();
是一种很普遍并且符合C++语法的写法。然而并不是一种很Embeded C++的写法。
solution 1
void ISR1(void)
{
static int freq = 0;
freq = sysClkRateGet();
...
}
显然案发现场的freq
是在运行时才分配的内存,按solution 1的方案可以解决该问题,但却违背了coder的初衷。freq = sysClkRateGet()
在每一次中断都会执行。
更多的思考
由于一开始在调查阶段并不知道具体的事故原因,我验证了
void ISR2(void)
{
static int n = func();
}
会导致系统重启,而
void ISR3(void)
{
static int n = 0; // 1,2,3,4,..., any const number is OK
n = func();
}
则不会产生任何问题。也就是说 static int n = func();
中n
的内存分配发生在运行时刻,而static int n = 0;//1,2,3,...
则可能是在编译阶段就已经分配好空间。
为了证明以上猜想,使用带构造函数和不带构造函数的简单结构体静态变量进行测试:
typedef struct _tagTest
{
_tagTest()
{
}
int a;
int b;
}ST_TEST;
void ISR4(void)
{
static ST_TEST test;
...
test.a = 3;
...
}
以上代码仍会导致系统重启,而去掉构造函数则程序可以正常运行。至少也证实了手册中说的不能在ISR中使用C++对象。
ToDo
想要证明static int n = func();
在ISR中何时n
进行的内存分配,仍需要更进一步的证据…
上一篇: 一个HelloWorld引发的疑问
下一篇: 一个"/"引发的血案