嵌入式操作系统 uC/OS-II V2.86 版本的bug

时间:2021-02-20 20:12:30

暑假做项目时,发现莫名奇妙会卡死,情况基本是:应用中有两个任务是通过等待信号量来触发的,调试时发现只要让其中优先级低的那个任务不要与一个周期性触发的频率很高的串口中断撞在一起就不会卡死了,百思不得其解。

结果一不小心搜到了一个论坛讨论,说的是V2.86版本的一个bug,在2.88中解决了。然后一对比,哇靠,正好可以和我的情况对应上,而且我用的版本正好是V2.86。。。估计就是这个原因,但是暂时没有办法重新搭建出实验环境进行验证。不管怎么样,先记录下来,省的以后再坑自己。


出处:https://www.amobbs.com/thread-5635959-1-1.html

ucos2.86在cortex-M3出现的bug,具体现象,可参考下面的链接。
ucos中的这个问题(BUG),
[url]http://blog.csdn.net/sunlei_telchina/article/details/6205515

2.88修正了bug,下面列出2.88和2.86的不同点。
2.86中的代码是这样的:

     if (OSLockNesting == 0) {                      /* ... scheduler is not locked                  */
OS_SchedNew();
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}

2.88中的代码是这样的:

    if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}

原文是这样的

OS_CORE.C:

OSIntExit() and OS_Sched() have changed slightly because of a boundary condition found with the Cortex-M3 port. Specifically, we needed to move the statement:

OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];

Before testing for the priority.


下面开始分析原因 xcj 2015/10/31 15:52

假设有两个任务程序 A 和 B,任务A的优先级比任务B的优先级要高。

正在运行的任务A执行了一个OSTimeDly(1)来延时,OSTimeDly(1)调用OS_Sched()


OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) {
if (OSLockNesting == 0u) {
OS_SchedNew(); // <----执行完该语句后,OSPrioHighRdy = B,
if (OSPrioHighRdy != OSPrioCur) //<------由于OSPrioCur =A,与OSPrioHighRdy不相等
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; //<----所以,OSTcbHighRdy = B
OSCtxSwCtr++;
OS_TASK_SW(); //<-- 在Cortex-M3上会触发一个PendSV,在PendSV中执行任务切换。
} //<----由于中断关闭,所以必须等中断打开才能
} //<---假设刚好运行到此处发生SysTick中断,但是由于前面已经关闭中断了,所以不会响应SysTick中断。
} // <------此时,有两个中断在排队了,SysTick中断和PendSV,假设SysTick中断的优先级比PendSV的高。
OS_ENTER_CRITICAL(); // <------一旦执行了该行,意味着立马进入SysTick中断。

记住此时:OSPrioCur = A , OSTCBCur = A
OSPrioHighRdy = B, OSTCBHighRdy = B

在SysTick中会发现任务A的延时到期了。任务A又会进入就绪表,所以在OSIntExit()调用OS_Sched()时

  OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) {
if (OSLockNesting == 0u) {
OS_SchedNew(); <---由于任务A延时到了,所以OSPrioHighRdy = A,
if (OSPrioHighRdy != OSPrioCur) <-----而此时OSPrioCur =A,与OSPrioHighRdy相等所以不会执行下面括号里的语句。
{
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
OSCtxSwCtr++;
OSIntCtxSw(); <-- 该句子不执行,不触发PendSV
} <----此时,OSTCBHighRdy =B
}
}
OS_ENTER_CRITICAL(); <-----从SysTick退出后,会立马进入PendSV中断(由A.OSTimeDly(1)触发的)

注意此时:
OSPrioCur = A, OSTCBCur = A
OSPrioHighRdy = A, OSTCBHighRdy = B

在PendSV的程序中,只执行任务切换

OSPrioCur  <= OSPrioHighRdy;
OSTCBCur <= OSTCBHighRdy;

任务寄存器出栈。
跑到任务程序里面发现:
OSPrioCur = A, OSTCBCur =B;

也就是说虽然当前运行的实际任务是B,但是当前的优先级却是A。这也就是是为什么最后程序运行到一个低级的程序出不来。高级的程序无法运行的原因。

同样的情况也可能发生在下面这种情况:
(1)正在运行的A调用系统函数SemPend(),semPend()执行完后会OSPrioHighRdy = B, OSTCBHighRdy = B。
(2) 在SemPend()中执行OSched()中过程中发生了外部中断。OSChed()运行完立即进去外部中断程序(而不是PendSV程序)。
(3)而在外部中断中调用了SemPost(),又让A就绪了。执行外部中断的SemPost的OSChed()中会让OSPrioRdy = A,OSTCBHighRdy = B(因为OSPrioHighRdy≠OSPrioCur不成立)
(4)从外部中断后退出,直接进入PendSV中断,PendSV中断直接将 OSPrioCur <= OSPrioHighRdy; OSTCBCur <= OSTCBHighRdy;
(5)PendSV返回后,实际运行的任务堆栈OSTCBCur =B,而其优先级OSPrioCur =A(任务B占着A的优先级)。

这样任务A永远无法运行(即使任务B延迟自己)。只能让更低优先级任务B运行。

把程序修改一下,使OSTCBHighRdy与OSPrioHighRdy同步更新。就不会出现上述的bug。这里就留给坛友去分析了。

  if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}

如果我有分析错误的地方,还请坛友指正。