OS_CPU_A.ASM

本文介绍µC/OS-Ⅱ操作系统移植所需的四个关键汇编语言函数:OSStartHighRdy()用于使最高优先级任务开始运行;OSCtxSw()用于在任务间进行上下文切换;OSIntCtxSw()用于中断上下文中的任务切换;OSTickISR()为时钟中断服务程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

µC/OS-Ⅱ的移植实例要求用户编写四个简单的汇编语言函数:
		OSStartHighRdy()
		OSCtxSw()
		OSIntCtxSw()
		OSTickISR()
	如果用户的编译器支持插入汇编语言代码的话,用户就可以将所有与处理器相关的代码放到OS_CPU_C.C文件中,而不必再拥有一些分散的汇编语言文件。
.01	OSStartHighRdy()
	使就绪状态的任务开始运行的函数叫做OSStart(),如下所示。在用户调用OSStart()之前,用户必须至少已经建立了自己的一个任务(参看OSTaskCreate()和OSTaskCteateExt())。OSStartHighRdy()假设OSTCBHighRdy指向的是优先级最高的任务的任务控制块。前面曾提到过,在µC/OS-Ⅱ中处于就绪状态的任务的堆栈结构看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。要想运行最高优先级任务,用户所要做的是将所有处理器寄存器按顺序从任务堆栈中恢复出来,并且执行中断的返回。为了简单一点,堆栈指针总是储存在任务控制块(即它的OS_TCB)的开头。换句话说,也就是要想恢复的任务堆栈指针总是储存在OS_TCB的0偏址内存单元中。

void OSStartHighRdy (void)
{
    Call user definable OSTaskSwHook();                                    
    Get the stack pointer of the task to resume:                           
        Stack pointer = OSTCBHighRdy->OSTCBStkPtr;
    OSRunning = TRUE;
    Restore all processor registers from the new task's stack;             
    Execute a return from interrupt instruction;                           
}

	注意,OSStartHighRdy()必须调用OSTaskSwHook(),因为用户正在进行任务切换的部分工作——用户在恢复最高优先级任务的寄存器。而OSTaskSwHook()可以通过检查OSRunning来知道是OSStartHighRdy()在调用它(OSRunning为FALSE)还是正常的任务切换在调用它(OSRunning为TRUE).
	OSStartHighRdy()还必须在最高优先级任务恢复之前和调用OSTaskSwHook()之后设置OSRunning为TRUE。
.02	OSCtxSw()
	如前面所述,任务级的切换问题是通过发软中断命令或依靠处理器执行陷阱指令来完成的。中断服务例程,陷阱或异常处理例程的向量地址必须指向OSCtxSw()。
	如果当前任务调用µC/OS-Ⅱ提供的系统服务,并使得更高优先级任务处于就绪状态,µC/OS-Ⅱ就会借助上面提到的向量地址找到OSCtxSw()。在系统服务调用的最后,µC/OS-Ⅱ会调用OSSched(),并由此来推断当前任务不再是要运行的最重要的任务了。OSSched()先将最高优先级任务的地址装载到OSTCBHighRdy中,再通过调用OS_TASK_SW()来执行软中断或陷阱指令。注意,变量OSTCBCur早就包含了指向当前任务的任务控制块(OS_TCB)的指针。软中断 (或陷阱) 指令会强制一些处理器寄存器(比如返回地址和处理器状态字)到当前任务的堆栈中,并使处理器执行OSCtxSw()。OSCtxSw()的原型如程序清单 L8.2所示。这些代码必须写在汇编语言中,因为用户不能直接从C中访问CPU寄存器。注意在OSCtxSw()和用户定义的函数OSTaskSwHook()的执行过程中,中断是禁止的。

程序清单 L 8.2	OSCtxSw()的原型
void OSCtxSw(void)
{
    保存处理器寄存器;
    将当前任务的堆栈指针保存到当前任务的OS_TCB中:
        OSTCBCur->OSTCBStkPtr = Stack pointer;
    调用用户定义的OSTaskSwHook();
    OSTCBCur  = OSTCBHighRdy;
    OSPrioCur = OSPrioHighRdy;
    得到需要恢复的任务的堆栈指针:
        Stack pointer = OSTCBHighRdy->OSTCBStkPtr;
    将所有处理器寄存器从新任务的堆栈中恢复出来;
    执行中断返回指令;
}

4.03	OSIntCtxSw()
	OSIntExit()通过调用OSIntCtxSw()来从ISR中执行切换功能。因为OSIntCtxSw()是在ISR中被调用的,所以可以断定所有的处理器寄存器都被正确地保存到了被中断的任务的堆栈之中。实际上除了我们需要的东西外,堆栈结构中还有其它的一些东西。OSIntCtxSw()必须要清理堆栈,这样被中断的任务的堆栈结构内容才能满足我们的需要。
	要想了解OSIntCtxSw(),用户可以看看µC/OS-Ⅱ调用该函数的过程。用户可以参看图8.2来帮助理解下面的描述。假定中断不能嵌套(即ISR不会被中断),中断是允许的,并且处理器正在执行任务级的代码。当中断来临的时候,处理器会结束当前的指令,识别中断并且初始化中断处理过程,包括将处理器的状态寄存器和返回被中断的任务的地址保存到堆栈中[F8.2(1)]。至于究竟哪些寄存器保存到了堆栈上,以及保存的顺序是怎样的,并不重要。
接着,CPU会调用正确的ISR。µC/OS-Ⅱ要求用户的ISR在开始时要保存剩下的处理器寄存器[F8.2(2)]。一旦寄存器保存好了,µC/OS-Ⅱ就要求用户或者调用OSIntEnter(),或者将变量OSIntNesting加1。在这个时候,被中断任务的堆栈中只包含了被中断任务的寄存器内容。现在,ISR可以执行中断服务了。并且如果ISR发消息给任务(通过调用OSMboxPost()或OSQPost()),恢复任务(通过调用OSTaskResume()),或者调用OSTimeTick()或OSTimeDlyResume()的话,有可能使更高优先级的任务处于就绪状态。
	假设有一个更高优先级的任务处于就绪状态。µC/OS-Ⅱ要求用户的ISR在完成中断服务的时候调用OSIntExit()。OSIntExit()会告诉µC/OS-Ⅱ到了返回任务级代码的时间了。调用OSIntExit()会导致调用者的返回地址被保存到被中断的任务的堆栈中[F8.2(3)]。
	OSIntExit()刚开始时会禁止中断,因为它需要执行临界段的代码。根据OS_ENTER_CRITICAL()的不同执行过程(参看8.03.02),处理器的状态寄存器会被保存到被中断的任务的堆栈中[F8.2(4)]。OSIntExit()注意到由于有更高优先级的任务处于就绪状态,被中断的任务已经不再是要继续执行的任务了。在这种情况下,指针OSTCBHighRdy会被指向新任务的OS_TCB,并且OSIntExit()会调用OSIntCtxSw()来执行任务切换。调用OSIntCtxSw()也同样使返回地址被保存到被中断的任务的堆栈中[F8.2(5)]。
	在用户切换任务的时候,用户只想将某些项([F8.2(1)]和[F8.2(2)])保留在堆栈中,并忽略其它项(F8.2(3),(4)和(5))。这是通过调整堆栈指针(加一个数在堆栈指针上)来完成的[F8.2(6)]。加在堆栈指针上的数必须是明确的,而这个数主要依赖于移植的目标处理器(地址空间可能是16,32或64位),所用的编译器,编译器选项,内存模式等等。另外,处理器状态字可能是8,16,32甚至64位宽,并且OSIntExit()可能会分配局部变量。有些处理器允许用户直接增加常量到堆栈指针中,而有些则不允许。在后一种情况下,可以通过简单的执行一定数量的pop(出栈)指令来实现相同的功能。一旦堆栈指针完成调整,新的堆栈指针会被保存到被切换出去的任务的OS_TCB中[F8.2(7)]。
	OSIntCtxSw()的原型如程序清单 L8.3所示。这些代码必须写在汇编语言中,因为用户不能直接从C语言中访问CPU寄存器。如果用户的编译器支持插入汇编语言代码的话,用户就可以将OSIntCtxSw()代码放到OS_CPU_C.C文件中,而不放到OS_CPU_A.ASM文件中。正如用户所看到的那样,除了第一行以外,OSIntCtxSw()的代码与OSCtxSw()是一样的。这样在移植实例中,用户可以通过“跳转”到OSCtxSw()中来减少OSIntCtxSw()代码量。

程序清单 L 8.3	OSIntCtxSw()的原型
void OSIntCtxSw(void)
{
    调整堆栈指针来去掉在调用:
        OSIntExit(),
        OSIntCtxSw()过程中压入堆栈的多余内容;
    将当前任务堆栈指针保存到当前任务的OS_TCB中:
        OSTCBCur->OSTCBStkPtr = 堆栈指针;
    调用用户定义的OSTaskSwHook();
    OSTCBCur  = OSTCBHighRdy;
    OSPrioCur = OSPrioHighRdy;
    得到需要恢复的任务的堆栈指针:
        堆栈指针 = OSTCBHighRdy->OSTCBStkPtr;
    将所有处理器寄存器从新任务的堆栈中恢复出来;
    执行中断返回指令;
}

8.04.04	OSTickISR()
	µC/OS-Ⅱ要求用户提供一个时钟资源来实现时间的延时和期满功能。时钟节拍应该每秒钟发生10-100次。为了完成该任务,可以使用硬件时钟,也可以从交流电中获得50/60Hz的时钟频率。
	用户必须在开始多任务调度后(即调用OSStart()后)允许时钟节拍中断。换句话说,就是用户应该在OSStart()运行后,µC/OS-Ⅱ启动运行的第一个任务中初始化节拍中断。通常所犯的错误是在调用OSInit()和OSStart()之间允许时钟节拍中断(如程序清单 L8.4所示)。
	
程序清单 L 8.4	在不正确的位置启动时钟节拍中断
void main(void)
{
    .
    .
    OSInit();               /* 初始化 オµC/OS-II                 */
    .
    .
    /* 应用程序初始化代码 ...                         */
    /* ... 调用OSTaskCreate()建立至少一个任务       */
    .
    .
    允许时钟节拍中断; /* 千万不要在这里允许!!!            */
    .
    .
    OSStart();              /* 开始多任务调度                  */
}

	有可能在µC/OS-Ⅱ开始执行第一个任务前时钟节拍中断就发生了。在这种情况下,µC/OS-Ⅱ的运行状态不确定,用户的应用程序也可能会崩溃。
	时钟节拍ISR的原型如程序清单 L8.5所示。这些代码必须写在汇编语言中,因为用户不能直接从C语言中访问CPU寄存器。如果用户的处理器可以通过单条指令来增加OSIntNesting,那么用户就没必要调用OSIntEnter()了。增加OSIntNesting要比通过函数调用和返回快得多。OSIntEnter()只增加OSIntNesting,并且作为临界段代码中受到保护。

程序清单 L 8.5	时钟节拍ISR的原型
void OSTickISR(void)
{
   保存处理器寄存器;
   调用OSIntEnter()或者直接将 OSIntNesting加1;

   调用OSTimeTick();

   调用OSIntExit();
   恢复处理器寄存器;
   执行中断返回指令;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值