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 }
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 }
这个函数是任务异常返回/退出时,会进入的函数。正常情况下,一个任务应该永远不会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 }
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 }
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 }
这个函数已经过简化了,详细的就不介绍了,函数比较简单。
剩下的,下篇再继续解析。