一步步移植uCOS-II and LwIP (四)

来源:互联网 发布:win10图片查看软件 编辑:程序博客网 时间:2024/05/21 06:43

二、uCOS-II移植

嵌入式实时操作系统uCOS-II移植的核心在于任务切换时上下文环境的保存及恢复,针对Cortex-M3内核的单片机,其采用了PendSVHandler中断处理的方式解决这一核心问题。我们要做的就是在任务切换及中断任务切换过程中开启触发PendSV中断,并在PendSVHandler中实现上下文环境的切换,即保存CPU内部寄存器值和恢复切换任务的相关信息。

为什么要使用PendSV中断实现上下文切换呢?参阅Cortex-M3的一段话。

   另一个相关的异常是PendSV(可悬起的系统调用),它和SVC协同使用。一方面,SVC异常是必须在执行SVC指令后立即得到响应的(对于SVC异常来说,若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬fault——译者注),应用程序执行SVC时都是希望所需的请求立即得到响应。另一方面,PendSV则不同,它是可以像普通的中断一样被悬起的(不像SVC那样会上访)。OS可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。悬起PendSV 的方法是:手工往NVIC的PendSV悬起寄存器中写1。悬起后,如果优先级不够高,则将缓期等待执行。   PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:  - 执行一个系统调用  -系统滴答定时器(SYSTICK)中断,(轮转调度中需要)  让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过SysTick异常启动上下文切换。如图所示。

systick

   上图是两个任务轮转调度的示意图。但若在产生SysTick 异常时正在响应一个中断,则SysTick异常会抢占其ISR。在这种情况下,OS是不能执行上下文切换的,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。因此,在CM3中也是严禁没商量——如果OS在某中断活跃时尝试切入线程模式,将触犯用法fault异常。

systick

   为解决此问题,早期的OS大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了IRQ,则本次SysTick在执行后不得作上下文切换,只能等待下一次SysTick异常),尤其是当某中断源的频率和SysTick异常的频率比较接近时,会发生“共振”,使上下文切换迟迟不能进行。     现在好了,PendSV来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行。为实现这个机制,需要把PendSV编程为最低优先级的异常。如果OS检测到某IRQ正在活动并且被SysTick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换。如图所示

systick

个中事件的流水账记录如下:
1. 任务A呼叫SVC来请求任务切换(例如,等待某些工作完成)
2. OS接收到请求,做好上下文切换的准备,并且悬起一个PendSV异常。
3. 当CPU退出SVC后,它立即进入PendSV,从而执行上下文切换。
4. 当PendSV执行完毕后,将返回到任务B,同时进入线程模式。
5. 发生了一个中断,并且中断服务程序开始执行
6. 在ISR执行过程中,发生SysTick异常,并且抢占了该ISR。
7. OS执行必要的操作,然后悬起PendSV异常以作好上下文切换的准备。
8. 当SysTick退出后,回到先前被抢占的ISR中,ISR继续执行
9. ISR执行完毕并退出后,PendSV服务例程开始执行,并且在里面执行上下文切换
10. 当PendSV执行完毕后,回到任务A,同时系统再次进入线程模式。

1、任务堆栈初始化

创建一个新任务时需要对该任务的堆栈进行初始化,其程序如下

/***********************************************************************************************************              INITIALIZE A TASK'S STACK** Description: This function is called by either OSTaskCreate() or OSTaskCreateExt() to initialize the*  stack frame of the task being created.  This function is highly processor specific.** Arguments  : task is a pointer to the task code**  p_arg is a pointer to a user supplied data area that will be passed to the task when the task first executes.**   ptos is a pointer to the top of stack.  It is assumed that 'ptos' points to a 'free' entry on the task stack.  If OS_STK_GROWTH is set to 1 then* 'ptos' will contain the HIGHEST valid address of the stack.  Similarly, if OS_STK_GROWTH is set to 0, the 'ptos' will contains the LOWEST valid address of the stack.**  opt specifies options that can be used to alter the behavior of OSTaskStkInit(). (see uCOS_II.H for OS_TASK_OPT_xxx).** Returns    : Always returns the location of the new top-of-stack once the processor registers have been placed on the stack in the proper order.** Note(s)    : 1) Interrupts are enabled when your task starts executing.*              2) All tasks run in Thread mode, using process stack.**********************************************************************************************************/OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void   *p_arg, OS_STK *ptos, INT16U opt){    OS_STK *stk;    (void)opt;          /* 'opt' is not used, prevent warning */    stk       = ptos;    /* Load stack pointer */  /* Registers stacked as if auto-saved on exception    */    *(stk)    = (INT32U)0x01000000L;   /* xPSR */    *(--stk)  = (INT32U)task;          /* Entry Point */    *(--stk)  = (INT32U)0xFFFFFFFEL;            /* R14 (LR) (init value will cause fault if ever used)*/    *(--stk)  = (INT32U)0x12121212L;       /* R12 */    *(--stk)  = (INT32U)0x03030303L;        /* R3 */    *(--stk)  = (INT32U)0x02020202L;       /* R2 */    *(--stk)  = (INT32U)0x01010101L;     /* R1  */    *(--stk)  = (INT32U)p_arg;     /* R0 : argument */  /* Remaining registers saved on process stack  */    *(--stk)  = (INT32U)0x11111111L;        /* R11 */    *(--stk)  = (INT32U)0x10101010L;        /* R10 */    *(--stk)  = (INT32U)0x09090909L;        /* R9 */    *(--stk)  = (INT32U)0x08080808L;       /* R8 */    *(--stk)  = (INT32U)0x07070707L;       /* R7 */    *(--stk)  = (INT32U)0x06060606L;      /* R6 */    *(--stk)  = (INT32U)0x05050505L;     /* R5 */    *(--stk)  = (INT32U)0x04040404L;    /* R4 */    return (stk);}

任务堆栈的初始化主要是对CPU内部寄存器进行入栈操作,由于M3内核堆栈方向是高地址向低地址生长的,故堆栈地址是递减的,且须按照程序中的顺序操作。具体原因,请研读Cortex-M3权威指南。

2、上下文环境切换

上文中已经介绍过,一般发生系统调用时,例如使用系统函数OSTimeDlyOSTimeDlyHMSM 需要调用OS_Sched的OSCtxSw进行任务级别的上下文切换。systick轮询中断产生时,需调用OSIntExit 中的OSIntCtxSw() 进行中断级的任务切换,在这两个函数中我们只需开启PendSV中断,在PendSV函数中实现上下文环境的的保存及恢复。

OSCtxSw    LDR     R0, =NVIC_INT_CTRL                                      LDR     R1, =NVIC_PENDSVSET    STR     R1, [R0]    BX      LROSIntCtxSw    LDR     R0, =NVIC_INT_CTRL                                      LDR     R1, =NVIC_PENDSVSET    STR     R1, [R0]    BX      LRPendSV_Handler    CPSID   I                                                       MRS     R0, PSP                                                 CBZ     R0, OS_CPU_PendSVHandler_nosave          SUBS    R0, R0, #0x20                                           STM     R0, {R4-R11}    LDR     R1, =OSTCBCur                                           LDR     R1, [R1]    STR     R0, [R1]                                            OS_CPU_PendSVHandler_nosave    PUSH    {R14}                                                   LDR     R0, =OSTaskSwHook                                       BLX     R0    POP     {R14}    LDR     R0, =OSPrioCur                                          LDR     R1, =OSPrioHighRdy    LDRB    R2, [R1]    STRB    R2, [R0]    LDR     R0, =OSTCBCur                                           LDR     R1, =OSTCBHighRdy    LDR     R2, [R1]    STR     R2, [R0]    LDR     R0, [R2]                                                LDM     R0, {R4-R11}                                            ADDS    R0, R0, #0x20    MSR     PSP, R0                                                 ORR     LR, LR, #0x04                                           CPSIE   I    BX      LR              

在这之前我们需要设置PendSV中断的优先级,以及在系统初始化的时候,启动最高优先级别的任务,这主要是由OSStartHighRdy 函数来实现的。

OSStartHighRdy    LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority    LDR     R1, =NVIC_PENDSV_PRI    STRB    R1, [R0]    MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call    MSR     PSP, R0    LDR     R0, =OSRunning                                      ; OSRunning = TRUE    MOVS    R1, #1    STRB    R1, [R0]    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)    LDR     R1, =NVIC_PENDSVSET    STR     R1, [R0]    CPSIE   I                                                   ; Enable interrupts at processor levelOSStartHang    B       OSStartHang                     

SYSTICK中断服务函数:

void  SysTick_Handler(void){    OSIntEnter();    OSTimeTick();            OSIntExit();                            }

3、其它设置

临界代码宏定义

#define  OS_CRITICAL_METHOD   3#if OS_CRITICAL_METHOD == 3#define  OS_ENTER_CRITICAL()  {cpu_sr = OS_CPU_SR_Save();}#define  OS_EXIT_CRITICAL()   {OS_CPU_SR_Restore(cpu_sr);}#endifOS_CPU_SR_Save    MRS     R0, PRIMASK                                                CPSID   I    BX      LROS_CPU_SR_Restore    MSR     PRIMASK, R0    BX      LR

在移植过程中,一定要注意修改uncomment掉stmf10x_it.c文件中的pendsv handler和systick handler,并修改os_cpu_asm.asm文件中的中断处理函数名,并使用 EXPORT PendSV_Handler,否则移植不能通过。

4、测试

测试代码如下:

OS_STK Task1Stack[TASK1STACKSIZE];OS_STK Task2Stack[TASK2STACKSIZE];void TaskCreate(void){    OSInit();    OSTaskCreate(Task1,(void *)0,(OS_STK*)&Task1Stack[TASK1STACKSIZE-1],TASK1PRIO);    OSTaskCreate(Task2,(void *)0,(OS_STK*)&Task2Stack[TASK2STACKSIZE-1],TASK2PRIO);    OSStart();}void Task1(void *pdata){    pdata = pdata;    for(;;)    {        printf("Task1 is running!\n");        OSTimeDly(10);    }}void Task2(void *pdata){    pdata = pdata;    for(;;)    {        printf("Task2 is running!\n");        OSTimeDly(10);    }}

测试输出:
ucosii printf

0 0
原创粉丝点击