关于uCOS-II的中断服务程序(ISR)中必须加“OSIntNesting == 1”的原因 ==避免调整堆栈指针
问题概述:
自从uCOS-II 版本 v2.04 以后(不含v2.04),在所有的中断服务程序中,当处理最外层中断时,那么必须 保存好现场之后,必须马上
加上如下判断
if( OSIntNesting == 1){
OSTCBCur->OSTCBStkPtr = 当前的堆栈指针
}
以上代码的意思是:当处于最外层中断时,保存被中断任务的堆栈指针。
注意:是所有的ISR,不论是uCOS-II的系统ISR(在v2.04以后的版本,系统的ISR已经加上了),还是用户的ISR
这是做是为了在移植中避免调整堆栈指针,减少移植时所需修改的汇编代码量
问题来源:
出现这个问题的根源是当低优先级的任务被中断,当中断完成后由于有高优先级的任务就绪,则必须调度高优先级的任务原来的低优先
级任务继续被中断着,但是此时的低优先级任务的堆栈已经被破坏,已不能被调度程序直接调度了,要想被调度而必须调整堆栈指针。如下图
所示的场景:
┏━━━━━━━━┓ ┏━━━━━━━━┓
┃ 低优先级任务 ┃ ┃ 低优先级任务 ┃
┗━━━━━━━━┛ ┗━━━━━━━━┛
/ 在低优先级任务执行过程
/中出现中断,任务被打断
/|
┏━━━━━━━━┓
┃中断服务程序ISR ┃(已经是最外层中断)
┗━━━━━━━━┛
/
/ 中断结束后有高优先级任务出现调
/度高优先任务,原来被中断的任务
/| 继续被中断着
┏━━━━━━━━┓
┃ 高优先级的任务┃
┗━━━━━━━━┛
需要调整堆栈指针的场景
问题分析:
要想理解加上上面两句的原因,不妨假设有下面场景出现:
void MyTask(void)
{
...
...
}
该任务在执行过程中被中断打断,下面是它的服务子程序
void MyISR(void)
{
保存现场(PUSHA)
OSIntEnter();
// 此时的堆栈指针是正确的,再往下就不对了,应该在此处保存用户任务堆栈指针
OSIntExit();
恢复现场(POPA)
中断返回
}
一般的OSIntExit(),大体如下,不论是哪个版本
OSIntExit()
{
OS_ENTER_CRITICAL();
if( OSIntNesting==0 && OSLockNesting == 0 ) {
找到目前系统中就绪表中优先级最的任务
如果不是当前任务,则调度它执行
OSIntCtxSw();
}
OS_EXIT_CRITICAL();
}
综上所述,任务调用链如下:
MyTask --> MyISR -->
① OSIntExit -->
② OS_ENTER_CRITICAL(); ③
OSIntCtxSw(); ④
由用户的堆栈内容如下
┃ 低端存储器 ┃
┣━━━━━━━━━━━━━┫ <━━━━━ 最后调度器调度高优先级任务时
┃调用OSIntCtxSw的返回地址 ┃<- ④ 低优先级任务的堆栈指针指向这里
┣━━━━━━━━━━━━━┫ |
┃ 由OS_ENTER_CRITICAL而保存┃(这两个返回地址都没有用,它们不会返回, |
┃ CPU内容,具体是由中断方式┃ 所以必须将堆栈指针调整回下面 |
┃ 决定 ┃<- ③ |
┣━━━━━━━━━━━━━┫ |
┃ 调用OSIntExit的返回地址 ┃<- ② |
┣━━━━━━━━━━━━━┫ <━━━━━ 而真正应该指向这里, <<<-- +
┃ ┃
┃ 被保存的CPU寄存器 ┃/
┃ ┃ /
┣━━━━━━━━━━━━━┫ /①
┃ 中断返回地址 ┃ /
┣━━━━━━━━━━━━━┫ /
┃ CPU状态字 ┃/
┣━━━━━━━━━━━━━┫
┃ 高端存储器 ┃
然而在实际的移植过程中,需要调整的指针偏移量是与编译器相关的,如果想要避免调整,显然一个简单的方法就是在调用OSIntExit之前先把
堆栈指针保存下来,以后调度该用户任务时,直接从此恢复堆栈指针,而不再管实际的堆栈内容了(因为下面的内容相对于调度程序来说已经
没有用处了)
综上所述:所有的中断服务程序(ISR),不论是uCOS-II系统的还是用户的中断服务程序,都必须在保存好现场以后必须加上如下两句
if ( OSIntNesting == 0 ) {
OSTCBCur->OSTCBStkPtr = 此时的堆栈指针;
}
当时系统的ISR,自v2.04以后都已经加上了这两句,而用户的ISR则完全由用户来保证加上两句,否则会出现问题,因为此时的OSIntCtwSW已经去掉了调整堆栈指针的指令。
以上就是v2.04以后不需要调整堆栈指针的原因。