freeRTOS源码解析3--port.c 2

接着上一篇,了解了头文件里一些宏和内联函数后,就可以开始看点c里的代码了。在源文件中所有关于FPU和MPU的内容将不会涉及,笔者暂时也没有去了解过相关的原理。

源文件中的一些代码还涉及了汇编、以及函数调用的一些规则的知识,在碰到的时候再做相关介绍。下面正式开始。

2、port.c源码解析

2.1 pxPortInitialiseStack

看这个函数前,需要先了解一下:

2.1.1 ATPCS

ATPCS(The ARM-THUMB Procedure Call Standard),这个标准规定了函数调用,R0~R3放形参,第5个及后面的形参放在栈里,返回时R0存放返回值。

LR里存放返回地址,但M3/M4的异常返回时,会放一个特殊值,用于触发中断返回序列。

2.1.2 异常进入与退出

在《Cortex M3与M4权威指南》里详细描述了进入异常以及返回时,CPU会执行哪些动作。

 

进入异常时,CPU会按顺序将PSR、PC、LR、R12、R3~R0入栈,此时入栈时的LR并非是EXC_RETURN,而是触发中断时的LR值,

里面放的是正常的返回地址(函数调用保存的返回地址),PC是触发中断时下一条指令的地址,退出异常时会自动将这些值从栈中恢复到寄存器中。

2.1.3 异常返回时LR的值

 

 

 

 

 

 从异常返回时,会将LR变为“EXC_RETURN”的值,当LR是这个值的时候,执行“BX LR”时,CPU会触发异常返回序列,当是一个正常的返回地址时,CPU就会跳转到相应地址去取指。

 2.1.4 源码解析

了解了以上的知识后,就可以比较好理解源码了

 1 // XPSR置位的bit位指示的是thumb指令,因为cortex-m3/4不支持arm指令集。
 2 #define portINITIAL_XPSR                      ( 0x01000000 )
 3 #define portINITIAL_EXC_RETURN                ( 0xfffffffd )
 4 /* For strict compliance with the Cortex-M spec the task start address should
 5  * have bit-0 clear, as it is loaded into the PC on exit from an ISR. */
 6 /* 为了严格遵守Cortex-M规范,任务开始地址应该有位0清除,因为它是从ISR退出时加载到PC中。 */
 7 #define portSTART_ADDRESS_MASK                ( ( StackType_t ) 0xfffffffeUL )
 8 
 9 StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
10                                      TaskFunction_t pxCode,
11                                      void * pvParameters )
12 {
13     /* Simulate the stack frame as it would be created by a context switch
14      * interrupt. */
15     /* 模拟由上下文切换中断创建的堆栈帧。 */
16     // 初始化栈,执行这个函数的时候,该任务还从未开始执行过,所以需要先伪造一个现场。
17 
18     /* Offset added to account for the way the MCU uses the stack on entry/exit
19      * of interrupts, and to ensure alignment. */
20     /* 偏移量加至MCU进出中断时的出入栈规则,并确保对齐。 */
21     // 栈是满减的栈,所以当前栈指针pxTopOfStack指向栈的头部(地址最大处)。
22     pxTopOfStack--;
23 
24     // 跟前面说的一样,先伪造进入异常时CPU自动入栈的动作的结果。PSR、PC、LR、R12、R3~R0
25     // 任务切换或者说是上下文切换是发生在pendSV中断中的,这个在后面的源码中会看到。
26     *pxTopOfStack = portINITIAL_XPSR;                                    /* xPSR */
27     pxTopOfStack--;
28     *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */
29     pxTopOfStack--;
30     *pxTopOfStack = ( StackType_t ) prvTaskExitError;                    /* LR */
31 
32     /* Save code space by skipping register initialisation. */
33     /* 跳过寄存器初始化,节省代码空间。 */
34     // 因为是伪造的现场,不关心这些寄存器的值。但是根据ATPCS,R0的值就是任务的参数。
35     pxTopOfStack -= 5;                            /* R12, R3, R2 and R1. */
36     *pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */
37 
38     /* A save method is being used that requires each task to maintain its
39      * own exec return value. */
40     /* 每个任务维护自己的exec返回值。 */
41     // 这里在保存R11~R4之前,先保存了EXC_RETURN的值。
42     pxTopOfStack--;
43     *pxTopOfStack = portINITIAL_EXC_RETURN;
44 
45     // 这里保存的现场是程序做的,将当前任务的其他寄存器保存下来。
46     pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
47 
48     return pxTopOfStack;
49 }
pxPortInitialiseStack

2.2 prvTaskExitError

 1 static void prvTaskExitError( void )
 2 {
 3     /* A function that implements a task must not exit or attempt to return to
 4      * its caller as there is nothing to return to.  If a task wants to exit it
 5      * should instead call vTaskDelete( NULL ).
 6      *
 7      * Artificially force an assert() to be triggered if configASSERT() is
 8      * defined, then stop here so application writers can catch the error. */
 9     configASSERT( uxCriticalNesting == ~0UL );
10     portDISABLE_INTERRUPTS();
11 
12     for( ; ; )
13     {
14     }
15 }
prvTaskExitError

这个函数是任务异常返回/退出时,会进入的函数。正常情况下,一个任务应该永远不会return。

2.3 vPortSVCHandler

 

在讲这个函数前,需要先简单看一下TCB结构体的样子:

 1 typedef struct tskTaskControlBlock
 2 {
 3     /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
 4     /* 指向放在任务栈上的最后一项的位置。这必须是TCB结构的第一个成员。 */
 5     // 栈的当前位置。
 6     volatile StackType_t * pxTopOfStack;
 7 
 8     ListItem_t xStateListItem;
 9     ListItem_t xEventListItem;
10     UBaseType_t uxPriority;
11     /*< Points to the start of the stack. */
12     /* 指向栈的起始位置。 */
13     StackType_t * pxStack;
14     char pcTaskName[ configMAX_TASK_NAME_LEN ];
15 
16     #if ( configUSE_MUTEXES == 1 )
17         UBaseType_t uxBasePriority;
18         UBaseType_t uxMutexesHeld;
19     #endif
20 
21     #if ( configUSE_TASK_NOTIFICATIONS == 1 )
22         volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
23         volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
24     #endif
25 } tskTCB;

顺便再讲一下cortex-m3/4中psp和msp的区别:

《Cortex M3与M4权威指南》里的一句原文”for OS kernel and interrupts, the Main Stack Pointer (MSP) is used; for application tasks, the Process Stack Pointer (PSP) is used.

PSP(Process Stack Pointer),进程栈指针,用于应用程序。

MSP(Main Stack Pointer),主栈指针,用于操作系统内核和中断。

 

 1 __asm void vPortSVCHandler( void )
 2 {
 3 /* *INDENT-OFF* */
 4     PRESERVE8
 5     //此为SVC中断处理函数,是启动调度器后,启动的第一个任务,所以当前任务的栈还是伪造的现场。
 6     /* Get the location of the current TCB. */
 7     ldr r3, =pxCurrentTCB                  // 将pxCurrentTCB的地址存入R3。
 8     ldr r1, [ r3 ]                         // 相当于把pxCurrentTCB的值存入R1。
 9     ldr r0, [ r1 ]                         // 相当于把pxCurrentTCB->pxTopOfStack的值存入R0。
10     /* Pop the core registers. */
11     // 还记得伪造的现场长什么样子吗?
12     // 出栈,R14就是LR,此时LR = portINITIAL_EXC_RETURN = 0xfffffffd
13     ldmia r0!, {r4-r11,r14}
14     msr psp, r0                            // PSP是任务用的栈,把R0的值赋给PSP,退出SVC中断时就会触发异常返回序列。
15     isb
16     mov r0, #0
17     msr basepri, r0                        // 不屏蔽任何中断。
18     bx r14                                 // CPU会自动将伪造现场中的PSR、PC、LR、R12、R3~R0出栈。
19 /* *INDENT-ON* */
20 }
vPortSVCHandler

2.4 prvStartFirstTask

 

这个函数涉及到一个寄存器,需要先看一下:

 

这个寄存器里存放了向量表的偏移地址。

向量表里的第一个值就是sp的初始值。

 1 __asm void prvStartFirstTask( void )
 2 {
 3 /* *INDENT-OFF* */
 4     PRESERVE8
 5 
 6     ldr r0, =0xE000ED08    // r0 = VTOR寄存器的地址。
 7     ldr r0, [ r0 ]         // 获取向量表的偏移地址。
 8     ldr r0, [ r0 ]         // 将向量表里的第一个值赋给R0
 9     /* Set the msp back to the start of the stack. */
10     msr msp, r0            // 将msp设置为初始化的值,至此msp被重置了,再也无法返回了
11 
12     /* Globally enable interrupts. */
13     cpsie i                // 开中断
14     cpsie f                // 开异常
15     dsb
16     isb
17     /* Call SVC to start the first task. */
18     svc 0                  // 触发SVC中断
19     nop
20     nop
21 /* *INDENT-ON* */
22 }
prvStartFirstTask

2.5 xPortStartScheduler

这个函数涉及到一个寄存器,需要先看一下:

 

 用于配置中断/异常优先级的。

 1 #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY            0xf
 2 #define configKERNEL_INTERRUPT_PRIORITY       ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
 3 #define portNVIC_PENDSV_PRI                   ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
 4 #define portNVIC_SYSTICK_PRI                  ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
 5 #define portNVIC_SHPR3_REG                    ( *( ( volatile uint32_t * ) 0xe000ed20 ) )
 6 
 7 BaseType_t xPortStartScheduler( void )
 8 {
 9     /* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
10      * See https://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
11     configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
12 
13     /* This port can be used on all revisions of the Cortex-M7 core other than
14      * the r0p1 parts.  r0p1 parts should use the port from the
15      * /source/portable/GCC/ARM_CM7/r0p1 directory. */
16     configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
17     configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
18 
19     /* Make PendSV and SysTick the lowest priority interrupts. */
20     // portNVIC_PENDSV_PRI = (0xF << (8 - 4)) << 16 = 0xF00000。
21     // portNVIC_SYSTICK_PRI = (0xF << (8 - 4)) << 24 = 0xF0000000。
22     // portNVIC_SHPR3_REG为uint32_t指针,修改的分别为SCB->SHP[10]和SCB->SHP[11]
23     portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
24     portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
25 
26     /* Start the timer that generates the tick ISR.  Interrupts are disabled
27      * here already. */
28     // 配置系统tick中断,这个函数后面会解析到的。
29     vPortSetupTimerInterrupt();
30 
31     /* Initialise the critical nesting count ready for the first task. */
32     /* 为第一个任务初始化关键嵌套计数 */
33     uxCriticalNesting = 0;
34 
35     /* 启动第一个任务,这个函数之前已解析过了 */
36     prvStartFirstTask();
37 
38     /* Should not get here! */
39     return 0;
40 }
xPortStartScheduler

这个函数已经过简化了,详细的就不介绍了,函数比较简单。

 

剩下的,下篇再继续解析。

posted @ 2022-09-30 17:37  freeManX1807  阅读(860)  评论(0编辑  收藏  举报