OSCtxSw( )是一个任务级的任务切换函数(在任务中调用,区别于在中断程序中调用的OSIntCtxSw(),在MC68K系统上,通过执行一条软中断指令来实现任务切换。软中断向量指向函数,而该函数的执行结构可能造成系统任务重新调度(例如,试图唤醒一个优先级更高的任务),则在函数的末尾会调用OSSched (),OSSched()将查找当前就绪的优先级最高的任务。如果不是当前任务,则判断是否需要进行任务调度,再找到该任务控制块OS_TCB的地址,并将该地址拷贝到变量OSTCBHighRdy中,然后通过宠OS_TASK_SW()执行软中断,进行任务切换。在此过程中,变量OSTCBCur始终包含一个指向当前运行任务OS_TCB的指针。OSCtxSw()的汇编代码如下:
_OSCtxSw
MOVEM.L A0-A6/D0-D7,-(A7) ;存储当前任务环境
MOVE.L (_OSTCBCur),A1 ;保存当前任务TCB指针
MOVE.L A7,(A1)
MOVE.L (_OSTCBHighRdy),A1 ;获取最高优先级就绪任务的TCB地址
MOVE.L A1,(_OSTCBCur) ;将就绪任务设置为当前运行任务
MOVE.L (A1),A7 ;取得新任务的堆栈指针
MOVEM.L (A7)+,A0-A6/D0-D7 ;
RTE ;中断返回,切换任务
(3)OSIntCtxSw()函数
在μC/OS -II中,由于中断的产生可能会引起任务切换,在中断服务程序的最后会调用OSICntExit()函数检查任务就绪状态。如果需要进行任务切换,将调用 OSIntCtxSw(),所以,OSIntCtxSw()又称为中断级的任务切换函数。由于在调用OSIntCtxSw()之前已经发生了中断, OSIntCtxSw()默认CPU寄存器已经保存在被中断任务的堆栈了。OSIntCtxSw()的代码与OSCtxSw()的大部分相同,不同之处是:第一,由于中断已经发生,此处不需要再保存CPU寄存器;第二,OSIntCtxSw()需要调整堆栈指针,去掉堆栈中一些不需要的内容,以使堆栈中包含任务的运行环境。
_OSIntCtxSw
ADDA #10,A7 ;忽略掉由于函数嵌套调
;用而压入堆栈的内容
MOVE.L (_CSTCBCur),A1 ;在TCB中保存当前
;任务的堆栈指针
MOVE.L A7,(A1)
MOVE.L (_OSTCBHighRdy),A1
;获取最高优先级就绪任务的TCB地址
MOVE.L A1,(_OSTCBCur) ;将就绪任务设备为当前
;运行任务
MOVE.L (A1),A7 ;取得堆栈指针
MOVEM.L (A7)+,A0-A6/D0-D7 ;
RTE ;中断返回,切换任务
(4)OSTickISR()函数
在μC/OS-II中,当调用OSStart()启动多任务环境后,时钟中断非常重要。在时钟中断中处理所有与定时相关的工作,如任务的延时、等待操作等等。在时钟中断中将查询处于等待状态的任务,判断是否延时结束,以重新进行任务调度。
和μC/OS -II中的其他中断服务程序一样,OSTICkISR()首先在被不断任务堆栈中保存CPU寄存器的值,然后调用OSIntEnter()。ΜC/OS- II要求在中断服务程序开头调用OSIntEnter(),其作用是将记录中断嵌套层数的全局变量OSIntNesting加1。如果不调用 OSIntEnter(),直接将OSIntNesting加1也是允许的。随垢,OSTickISR()调用OSTimeTick(),检查所有处于延时等待状态的任务,判断是否有延时结束并就绪的任务。在OSTickISR()的最后调用OSIntExit(),如果在中断中(或其他嵌套的中断)有更高优先级的任务就绪,并且当前中断为中断嵌套的最后一层,OSIntExit()将进行任务调度。注意,如果进行了任务调度,OSIntExit()将不再返回调用者,而是用新任务堆栈中的寄存器数值恢复CPU现场,然后用RTE实现任务切换。如果当前中断不是中断嵌套的最后一层,或中断中没有改变任务的就绪状态,OSIntExit()将返回调用者OSTickISR(),最后OSTickISR()返回被中断的任务。
4.OS_CPU32.C文件
μC/OS-II的移值需要用户在OS_CPU32.C中定义6个函数,而实际上需要定义的只有OSTaskStkInit()一个函数,其他5个函数需要声明,但不一定有实际内容。这5个函数都是用户定义的,所以OS_CPU32.C中没有给出代码。如果用户需要使用这些函数,请将文件OS_CDG.H中的#define constant OS_CPU_HOOKS_EN设为1,设为0表示不使用这些函数。
OSTaskStkInit ()函数由任务创建函数OSTaskCreate()或OSTaskCreateExt()调用,用来初始化任务的堆栈。初始状态的堆栈模拟发生一次中断后的堆栈结构。按照中断后的进栈次序预留各个寄存器的存储空间,而中断返回地址指向任务代码的起始地址。当调用OSTaskCreate()或 OSTaskCreateExt()创建一个新任务时,需要传递的参数是:任务代码的起始地址、参数指针、任务堆栈顶端的地址、任务的优先级。 OSTaskCreateExt()还需要一些其他参数,但与OSTaskStkInit()没有关系。OSTaskStkInit()只需要以上提到的 3个参数:task、pdata、ptos。由于MC68K堆栈是16位宽的(以字为单位),OSTaskStkInit()将创立一个指向以字为单位的内存区域的指针,同时要求堆栈指针指向空堆栈的顶端。堆栈初始化工作结束后,OSTaskStkInit()返回新的堆栈顶指针, OSTaskCreate()或OSTaskCreateExt()将指针保存在任务的OS_TCB中。
三、移植中的几点注意事项
由于μC/OS-II运行的实时性,调试内核几乎不可能。一旦移植过程中内核运行不稳定,很难确定是什么地方的问题,更困难的是有些现象几乎是不可重复的。这就需要详细了解内核运行机理,认真分析,找出可能存在的问题。下面就来分析这些移植过程中的问题。
1.编译器的优化选项
在移植过程中,除了要熟悉μC/OS-II和目标芯片之外,熟悉使用的C编码器也非常重要。通常C编译器都会提供一些优化代码的选项,在移植μc/OS-II的过程中,这些选项往往会带来麻烦。下面是移植中与HIWARE的C编译器有关的例子。
通常在调用子程序或进入中断时,C编译器会自动保存CPU内部寄存器到堆栈中。例如,在进入中断时编译器会加入下面2条指令:
LINK #$0000,A6;
MOVEM.L D0/D1/D3/D4/D5/D6/D7/A0/A1/A2/A3/A4/A5,-(A7);
这2 条汇编指令的作用是将CPU的数据寄存器D0~D7、地址寄存器A0~A5保存到堆栈中,再将此时的堆栈指针A7也保存到堆栈中,并使用A6作为临时的堆栈指针。这本是一个非常好的优化选项,可以防止在中断中偶然地更改了数据寄存器或地址寄存器;但在μC/OS-II中,这个机制将对OS_CPU_C.C 和OS_CPU_ASM.ASM中的几个子程序和中断服务例程产生致命的影响。
OS_CPU_C.C和OS_CPU_ASM.ASM中的子程序中断引发任务调度,当前的任务被挂起。挂起任务是通过下面的语句来完成的:
MOVEM.L A0-A6/D0-D7,-(A7);
MOVE.L @OSTCBCur,A2;
MOVE.L (A2),A1;
MOVE.L A7,(A1);
保存任务的指针和所有数据地址寄存器的值,那么理想情况下,此时的任务堆栈应该是如图1所示的情况(以OSCtxSw()函数为例,可以对应到OS_CPU_C.C和OS_CPU_ASM.ASM中的其他函数和中断处理例程)。
那么恢复挂起的任务时,只要通过如下语句:
MOVE.L OSTCBHighRdy,A1;
MOVE.L @OSTCBCur,A2;
MOVE.L A1,(A2);
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);
较好的方法当然是调整编译器,取消这个优化选项。如果无法调整编译器,就只有用以上办法来适应编译器了。
评论
查看更多