RTX4 源码分析——Cortex-M3

cortex-m3

模式及特权

cortex-m3执行任务有两种模式,Thread mode & Handler mode

正常任务在thread mode 下执行
异常和中断在handler mode 下执行

handler mode 始终在 privilege 下执行,使用 MSP (即主栈指针)
thread mode 使用 privilege 还是 UNprivilege、使用 MSP 还是 PSP 由内核寄存器 control 决定。

对于有操作系统的程序,操作系统相关的任务始终在 handler mode 下执行,调用 SVC 进入。
用户任务始终在 thread mode 下执行(除 privilege 操作使用 SVC 进入 handler mode 处理)。

栈空间

MSPPSP双栈的优点:
用户任务栈使用 PSP 访问,每个任务有自己的堆栈空间,创建任务时分配的空间
系统任务使用 MSP 访问,系统初始化时建立的堆栈空间,启动文件分配的空间
用户任务栈不会影响到系统任务栈。

编译器编译程序时,
先将需要使用的寄存器入栈,再操作寄存器执行任务,
待程序执行完毕再出栈,所以不用担心中断会破坏程序。

系统异常和中断嵌套时,硬件自动入栈 8个内核寄存器:
xPSRPCLRR12R3R2R1R0
入栈使用的栈指针为被中断的任务所使用的栈指针,
一般有操作系统时,用户任务使用 PSP,系统任务使用 MSP。
R4-R11需要手动入栈

RTX4 HAL_CM3.c 源码分析

SVC_Handler

SVC中断处理程序

/*-------------------------- SVC_Handler ------------------------------------*/

__asm void SVC_Handler (void) {
        PRESERVE8                                                       //stack 8-byte alignment

#ifdef  IFX_XMC4XXX
        EXPORT  SVC_Handler_Veneer
SVC_Handler_Veneer        
#endif

        IMPORT  SVC_Count                                               //引用SVC_Count
        IMPORT  SVC_Table                                               //引用SVC_Table
        IMPORT  rt_stk_check                                            //引用rt_stk_check
                                                                        //======= 读取SVC 异常编号,系统任务使用 0 号
        MRS     R0,PSP                  ; Read PSP                      //PSP --> R0
        LDR     R1,[R0,#24]             ; Read Saved PC from Stack      //[R0+24] --> R1,即自动入栈的 PC --> R1
        LDRB    R1,[R1,#-2]             ; Load SVC Number               //[R1-2] --> R1,即PC回退2字节找到 SVC 号 --> R1
        CBNZ    R1,SVC_User                                             //R1 != 0 ==> SVC_User, 0 为系统任务,其他为用户任务
                                                                        //======= 硬件自动入栈的参数加载到 R0-R3,R12, 其中 R12 是异常任务入口
        LDM     R0,{R0-R3,R12}          ; Read R0-R3,R12 from stack     //[R0...] --> R0-R3,R12
        BLX     R12                     ; Call SVC Function             //调用系统任务(如果是初始化任务会修改 PSP 到 idle task 栈地址+32,模拟自动入栈)
                                                                        //======= 保存 PSP 到 R12,比较 run & new,不需要任务切换就跳转到 SVC_Exit
        MRS     R12,PSP                 ; Read PSP                      //PSP --> R12
        LDR     R3,=__cpp(&os_tsk)                                      //&os_tsk --> R3
        LDM     R3,{R1,R2}              ; os_tsk.run, os_tsk.new        //[R3...] --> R1,R2 ,R1 = run,R2 = new
        CMP     R1,R2                                                   //R1-R2, update APSR
        BEQ     SVC_Exit                ; no task switch                //R1 == R2, ==> SVC_Exit (即没有切换任务)
                                                                        //======= 如果 run 为 0,跳转到 SVC_Restore
        CBZ     R1,SVC_Restore          ; Runtask deleted?              //R1 == 0, ==> SVC_Restore,删除了正在执行前任务,就不需要保存环境了
                                                                        //======= 保存旧任务环境,同时检查栈溢出
        PUSH    {R2,R3}                                                 //R2,R3 --> [MSP]
        MOV     R3,#1                                                   //1 --> R3
        STRB    R3,[R1,#TCB_RETUPD]     ; os_tsk.run->ret_upd = 1       //R3 --> [R1+TCB_RETUPD],更新返回值标记,主动进入 SVC 切换任务,等返回时需要提供返回值,如延时、信号量
        STMDB   R12!,{R4-R11}           ; Save Old context              //R4-R11 --> [R12...], 上文中有 PSP --> R12
        STR     R12,[R1,#TCB_TSTACK]    ; Update os_tsk.run->tsk_stack  //R12 --> [R1+TCB_TSTACK],更新任务 PSP 值供以后使用
        BL      rt_stk_check            ; Check for Stack overflow      //调用 rt_stk_check 检查栈溢出
        POP     {R2,R3}                                                 //[MSP] --> R2,R3

SVC_Restore                                                             //======= 恢复新任务环境
        STR     R2,[R3]                 ; os_tsk.run = os_tsk.new       //R2 --> [R3],更新结构体 os_tsk,新任务 new 放到 run 位置

        LDR     R12,[R2,#TCB_TSTACK]    ; os_tsk.new->tsk_stack         //[R2+TCB_TSTACK] --> R12
        LDMIA   R12!,{R4-R11}           ; Restore New Context           //[R12...] --> R4-R11
        LDRB    R3,[R2,#TCB_RETUPD]     ; Update ret_val?               //[R2+TCB_RETUPD] --> R3
        MSR     PSP,R12                 ; Write PSP                     //R12(PSP) --> PSP
                                                                        //======= 更新任务返回值
        CBZ     R3,SVC_Return                                           //R3 == 0, ==> SVC_Return
        LDRB    R0,[R2,#TCB_RETVAL]     ; Write os_tsk.new->ret_val     //任务返回值(如延时完成、有信号量) --> R0
                                                                        //======= 更新 R0 到栈
SVC_Exit
        STR     R0,[R12]                ; Function return value         //R0 --> [R12],此时 R12 == PSP,指向栈R0位置
                                                                        //======= 从 handler 返回 thread,并使用 PSP 栈
SVC_Return
        MVN     LR,#:NOT:0xFFFFFFFD     ; set EXC_RETURN value          //0xFFFFFFFD --> LR,表示返回到 thread mode,之后使用 PSP
#ifdef  IFX_XMC4XXX
        PUSH    {LR}
        POP     {PC}
#else
        BX      LR                                                      //return to LR指向的位置
#endif

        /*------------------- User SVC ------------------------------*/

SVC_User
        PUSH    {R4,LR}                 ; Save Registers                //LR,R4 --> 栈
        LDR     R2,=SVC_Count                                           //SVC_Count --> R2 ,另一个文件里面,SVC_Count是一个地址标记
        LDR     R2,[R2]                                                 //[R2] --> R2
        CMP     R1,R2                                                   //R1-R2, --> APSR
        BHI     SVC_Done                ; Overflow                      //C=1&Z=0 ==> SVC_Done

        LDR     R4,=SVC_Table-4                                         //SVC_Table-4 --> R4
        LDR     R4,[R4,R1,LSL #2]       ; Load SVC Function Address     //[R4 + R1<<2] --> R4

        LDM     R0,{R0-R3,R12}          ; Read R0-R3,R12 from stack     //[R0...] --> R0-R3,R12
        BLX     R4                      ; Call SVC Function             //调用 R4所指向的位置

        MRS     R12,PSP                                                 //PSP --> R12
        STM     R12,{R0-R3}             ; Function return values        //R0-R3 --> [R12...]
SVC_Done
        POP     {R4,PC}                 ; RETI                          //栈 --> PC,R4

        ALIGN                                                           //填充对齐
}

PendSV_Handler

PendSV中断处理程序


/*-------------------------- PendSV_Handler ---------------------------------*/

__asm void PendSV_Handler (void) {
        PRESERVE8

#ifdef  IFX_XMC4XXX
        EXPORT  PendSV_Handler_Veneer
PendSV_Handler_Veneer        
#endif

        BL      __cpp(rt_pop_req)                                       //调用 rt_pop_req

Sys_Switch
        LDR     R3,=__cpp(&os_tsk)                                      //os_tsk 地址 --> R3
        LDM     R3,{R1,R2}              ; os_tsk.run, os_tsk.new        //run,new --> R1,R2
        CMP     R1,R2                                                   //R1-R2, update APSR
        BEQ     Sys_Exit                                                //run == new, ==> Sys_Exit
                                                                        //======= 保存 run 的环境,所谓环境即运行时内核寄存器的值
        PUSH    {R2,R3}                                                 //R2,R3 --> [MSP]
        MOV     R3,#0                                                   //0 --> R3
        STRB    R3,[R1,#TCB_RETUPD]     ; os_tsk.run->ret_upd = 0       //R3 --> [R1+TCB_RETUPD],切换任务不需要更新返回值
        MRS     R12,PSP                 ; Read PSP                      //PSP --> R12
        STMDB   R12!,{R4-R11}           ; Save Old context              //R4-R11 --> [R12],手动 PSP 压栈
        STR     R12,[R1,#TCB_TSTACK]    ; Update os_tsk.run->tsk_stack  //R12 --> [R1+TCB_TSTACK]
        BL      rt_stk_check            ; Check for Stack overflow      //栈溢出检查

        POP     {R2,R3}                                                 //[MSP] --> R2,R3
        STR     R2,[R3]                 ; os_tsk.run = os_tsk.new       //new --> run,更新结构体 os_tsk
                                                                        //======= 加载 new 的环境
        LDR     R12,[R2,#TCB_TSTACK]    ; os_tsk.new->tsk_stack         //[R2+TCB_TSTACK] --> R12
        LDMIA   R12!,{R4-R11}           ; Restore New Context           //[R12...] --> R4-R11,手动 POP 出栈
        MSR     PSP,R12                 ; Write PSP                     //R12 --> PSP
                                                                        //======= 更新任务返回值
        LDRB    R3,[R2,#TCB_RETUPD]     ; Update ret_val?               //[R2+TCB_RETUPD] --> R3
        CBZ     R3,Sys_Exit                                             //R3 == 0, ==> Sys_Exit
        LDRB    R3,[R2,#TCB_RETVAL]     ; Write os_tsk.new->ret_val     //[R2+TCB_RETVAL] --> R3
        STR     R3,[R12]                                                //R3 --> [R12],此时的R12 为 PSP 的位置,指向栈 R0
Sys_Exit
        MVN     LR,#:NOT:0xFFFFFFFD     ; set EXC_RETURN value          //从 handler 返回 thread 并使用 PSP 的指令 0xFFFFFFFD --> LR
#ifdef  IFX_XMC4XXX
        PUSH    {LR}
        POP     {PC}
#else
        BX      LR                      ; Return to Thread Mode         //跳转到 LR
#endif

        ALIGN
}

SysTick_Handler

SysTick中断处理程序

/*-------------------------- SysTick_Handler --------------------------------*/

__asm void SysTick_Handler (void) {
        PRESERVE8                               //stack 8-byte alignment

#ifdef  IFX_XMC4XXX
        EXPORT  SysTick_Handler_Veneer
SysTick_Handler_Veneer        
#endif

        BL      __cpp(rt_systick)               //call systick
        B       Sys_Switch                      //jump to Sys_Switch

        ALIGN                                   //padding & align to boundary
}

/*-- rt_System.c ------------ rt_systick ------------------------------------*/

void rt_systick (void) {
  /* Check for system clock update, suspend running task. */
  P_TCB next;

  os_tsk.run->state = READY;                    //将正在执行的任务状态从run改为ready
  rt_put_rdy_first (os_tsk.run);                //将上述程序放到ready链表第一位

  /* Check Round Robin timeout. */
  rt_chk_robin ();                              //时间片模式下的任务的切换

  /* Update delays. */
  os_time++;                                    //系统时间累加
  rt_dec_dly ();                                //延时等待任务延时减一,如果延时变为零,改变状态到ready并加入ready链表,并给出返回值

  /* Check the user timers. */
  rt_tmr_tick ();                               //定时任务延时减一,并处理定时任务

  /* Switch back to highest ready task */
  next = rt_get_first (&os_rdy);                //获取ready链表中的第一个任务
  rt_switch_req (next);                         //将上述任务状态改为run
}

os_set_env

系统环境设置,此处为设置特权级,同时设置 thread mode 使用 PSP

/*--------------------------- os_set_env ------------------------------------*/

__asm void os_set_env (void) {
   /* Switch to Unprivileged/Privileged Thread mode, use PSP. */
        MOV     R0,SP                   ; PSP = MSP                         // MSP = SP --> R0,内核复位时使用的是 MSP
        MSR     PSP,R0                                                      // R0 -->  PSP  (MSR 特殊寄存器指令)
        LDR     R0,=__cpp(&os_flags)                                        // &os_flags --> R0
        LDRB    R0,[R0]                                                     // [R0] --> R0
        LSLS    R0,#31                                                      // 逻辑左移31位并更新PSR,即 ((R0<<31) == 0)?
        MOVNE   R0,#0x02                ; Privileged Thread mode, use PSP   // APSR.Z != 0, 2-->R0
        MOVEQ   R0,#0x03                ; Unprivileged Thread mode, use PSP // APSR.Z == 0, 3-->R0
        MSR     CONTROL,R0                                                  // R0 --> CONTROL
        BX      LR                                                          // 返回 LR 所指的位置并切换指令集

        ALIGN                                                               // 填充对齐
}

rt_init_stack

初始化应用堆栈

/*--------------------------- rt_init_stack ---------------------------------*/

void rt_init_stack (P_TCB p_TCB, FUNCP task_body) {
  /* Prepare TCB and saved context for a first time start of a task. */
  U32 *stk,i,size;

  /* Prepare a complete interrupt frame for first task start */
  size = p_TCB->priv_stack >> 2;                        //判断任务栈是系统分配还是用户指定
  if (size == 0) {
    size = (U16)os_stackinfo >> 2;                      //系统分配任务栈4-byte对齐,方便32位指针操作
  }

  /* Write to the top of stack. */
  stk = &p_TCB->stack[size];                            //stk 指向栈尾边界

  /* Auto correct to 8-byte ARM stack alignment. */
  if ((U32)stk & 0x04) {                                //stk 8-byte对齐
    stk--;
  }

  stk -= 16;                                            //stk向前跳跃16个双字,用于任务切换时保存内核寄存器信息

  /* Default xPSR and initial PC */
  stk[15] = INITIAL_xPSR;                               //PSR 状态寄存器,这里是状态寄存器的地址
  stk[14] = (U32)task_body;                             //PC 程序计数器寄存器,存储任务地址

  /* Clear R1-R12,LR registers. */
  for (i = 0; i < 14; i++) {                            //应该是R0-R12,LR, 共14个寄存器,赋初值0
    stk[i] = 0;
  }

  /* Assign a void pointer to R0. */
  stk[8] = (U32)p_TCB->msg;                             //stk[8] 存储 R0 寄存器,函数用 R0 寄存器存放形参

  /* Initial Task stack pointer. */
  p_TCB->tsk_stack = (U32)stk;                          //将stk位置作为任务栈地址,前半部分用于子程序调用,后半部分任务切换

  /* Task entry point. */
  p_TCB->ptask = task_body;                             //uVision debugger 用

  /* Set a magic word for checking of stack overflow. */
  p_TCB->stack[0] = MAGIC_WORD;                         //在栈首地址赋值一个数,用于检查栈溢出
}

头尾8-byte boundary
-----------------------------------------------------------------------------
|                                        |                                   |
stack           任务栈              tsk_stack   R4-R11,R0-R3,R12,LR,PC,xPSR  |
|                                        |                                   |
-----------------------------------------------------------------------------
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值