新闻中心

EEPW首页>嵌入式系统>设计应用> 在MOTOROLAA68K系列MCU上移植μC/OS-II

在MOTOROLAA68K系列MCU上移植μC/OS-II

作者: 时间:2016-09-12 来源:网络 收藏

MOVE.L (A1),A7;

MOVEM.L (A7)+,A0-A6/D0-D7;

将保存在任务TCB中的任务堆栈指针恢复,再恢复数据地址寄存器,最后执行OSCtxSw()的中断返回,就可以顺利地恢复被挂起的任务。

如果C编译器在OSCtxSw()函数入口处插入了2条保存数据地址寄存器和堆栈指针的语句后,再执行挂起任务的语句,任务的堆栈会变成图2所示的情况。编译器引起了堆栈的变化,如果所有的任务都是用这种方式挂起和恢复的,并不会产生致命的问题,因为编码器退出OSCtxSw()函数时会插入如下语句恢复堆栈:

MOVEM.L (A7)+,D0-D7/A0-A5;

UNLK A6;

问题在于初始化任务的时候,每个任务实际上是按照图1所示的堆栈结构被初始化的,那么,按照图2的堆栈结构来恢复自然会导致堆栈崩溃。

解决这个问题的方法很多,可以改定任务初始化的代码以适应C编译器的这个“优化”,也可以在进入OSCtxSw()函数时首先调用如下语句恢复堆栈,抵消C编码器的影响:

MOVEM.L (A7)+,D0-D7/A0-A5;

UNLK A6;

而在退出OSCtxSw()函数前再调用如下语句模拟出更动的堆栈:

LINK #$0000,A6

MOVEM.L D0-D7/A0-A5,-(A7);

较好的方法当然是调整编译器,取消这个优化选项。如果无法调整编译器,就只有用以上办法来适应编译器了。

2.开关中断的方法

在μC/OS-II中,开关中断是非常重要的,它可以保证关键代码或访问全局变量时不受中断的意外影响。CPU32的中断控制比较复杂,提供了7级具有不同级别的中断;可以选择关闭或打开某几级中断。但多级中断会使得μC/OS- II的中断处理变得复杂。在简单的应用或初次尝试移植μC/OS-II时,可以使用全开全关的方法。

如果考虑多级中断,必须注意到中断开关级别的控制是一个重要的信息,在关闭中断之前需要将这个信息保存起来,在对应的开中断时恢复这个中断级别控制信息。最容易想到的方法是用一个全局变量存存这个信息。

使用这个方法的程序如下:

#define OS_EXIT_CRITICAL() asm move SR_TEMP,sr;

#define OS_ENTER_CRITICAL() asm move.w SR,SR_TEMP;

asm ori.w #0x0700,SR;

接着构造两个任务,每个任务分别向屏幕输出一句话,同时修改内核的代码,让空闲任务也输出一句话。运行内核,通常在几分钟内会发现内核停止调试,只有空闲任务不停地向屏幕输出。这种情况非常麻烦,因为根据无法通过调试手段判断何时何处导致内核停止调度。

分析一下,当只有空闲任务运行时,代码为:

move.w sr,sr_temp

ori.w #0700,sr

addi.1 #1,OSIdleCtr

move.w sr_temp,sr

jmp ****

这5句语句在循环运行,而中断(这时只有定时中断)可以在任意一句语句中间切入。那么,如果在MOVE.W SR,SR_TEMP的时候产生了中断,

就会执行中断(因为正要关中断,但还没有关上);而中断程序调用的OSIntENTER和OSIntEXIT都会调用OS_ENTER_CRITICAL()来关闭中断,递增中断嵌套层数全局变量。这时,再次执行MOVE.W SR,SR_TEMP变量就被改写成关中断的值,当从中断返回到IDLE任务执行MOVE.W SR_TEMP,SR时,就关闭了中断,而不是恢复原来的状态寄存器。这样就导致内核无法响应中断,无法调度任务,只有IDLE任务在运行。

如何解决?最容易想到的方法是再增加一个全局变量,用来保存进入中断时的中断开关信息,退出中断恢复这个信息;但如果考虑到中断嵌套,相同的情况又出现了,并且如果一个任务在执行MOVE.W SR,SR_TEMP时被中断打断并且发生了任务调度,那么当个任务恢复时,它使用的中断信息SR_TEMP可以已经是被其他任务更改后的值了。内核无法响应中断,无法调度的任务可能依然存在。

给每个任务和中断都定义一个这样的全局变量,在不考虑中断嵌套的情况下似乎可以解决问题,但想象一下为每一个任务和中断提供一个单独的OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()函数所带来的工作量。显然这不一个好办法。

将中断信息推入堆栈是一个好主意,但我们会看到由此带来的一些更加隐蔽而复杂的问题。实现这个方法的程序代码如下:

#define OS_ENTER_CRITICAL() asm move SR,-(A7);

asm ori.w #0x0700,SR;

#define OS_EXIT_CRITICAL() asm move (A7)+,sr;

这样,每次调用OS_ENTER_CRITICAL(),都将当前的中断开关信息保存到当前任务堆栈或系统堆栈中断OS_EXIT_CRITICAL()时,恢复这个信息。

使用了这个方法后,必须小心地计算堆栈的使用情况,修改 OS_CPU_A.ASM和OS_CPU_C.C文件里的函数。以OSIntCtxSw()函数为例,这个函数将导致中断级的任务调度,即被中断打断的程序不能继续运行,退出中断中另一个优先级更高的任务得以运行。在这个函数中必须对被中断的任务堆栈进行清理,使得这个任务的堆栈看起来和一次正常的任务切换后的情况相同,这样,才能保证这个任务被正确地恢复运行。OSIntCtxSw()函数仅仅在OSICntExit()函数中被调用。

须指出的是,在中断发生时,CPU32已经将全部的寄存器和状态寄存器,PC指针内容保存到了堆栈中,这样已经为被打断的任务的恢复作好了准备。如果按照正常的中断流程,在退出中断时,被打断的任务应该恢复运行。现在,由于执行了中断级的任务切换,被打断的任务不能立刻恢复,而是被挂起,这就要求在执行任务调度前调整堆栈,使得被中断打断的任务处于随时可以被恢复的状态。

在中断处理程序中,当执行到OSIntExit()时,堆栈的情况和刚刚进入中断还是相同的,是能够随时恢复被打断的任务的情况。那么,只需要忽略OSIntExit()函数造成的堆栈变化。首先,是OSIntExit()函数本身的返回地址,长度为2个字;调用OS_ENTER_CRITICAL()压入堆栈的状态寄存器,长度为1个字;最后,是OSIntCtxSw()函数的返回地址,长度为2个字。那么在OSIntCtxSw()进行任务切换时,首先要把这5个字的堆栈的内容清除,才能保证被中断任务的正确恢复。该语句如下:

ADDA #10,A7;

在完成了这些调整后,由于开关中断可能导致的内核调度死锁的可能已经不存在了。但是在这种情况下,另一个更加隐蔽的问题会出现,这个问题又是和使用的C编码器相关的。



关键词:

评论


相关推荐

技术专区

关闭