您当前的位置:五五电子网电子知识单片机-工控设备嵌入式系统-技术利用MC68K的C编译器的μC/OS-II向MC68K移植的实现 正文
利用MC68K的C编译器的μC/OS-II向MC68K移植的实现

利用MC68K的C编译器的μC/OS-II向MC68K移植的实现

点击数:7166 次   录入时间:03-04 11:57:07   整理:http://www.55dianzi.com   嵌入式系统-技术

    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);

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



www.55dianzi.com

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

上一页  [1] [2] [3]  下一页


本文关键字:编译器  嵌入式系统-技术单片机-工控设备 - 嵌入式系统-技术