freeRTOS源码解析3--port.c 3
接着上一篇继续解析。
2、port.c源码解析
2.6 vPortEndScheduler
这个函数的作用我还不清楚,暂时先按函数名字面意思来理解,就是用于结束调度器的。
1 void vPortEndScheduler( void ) 2 { 3 /* Not implemented in ports where there is nothing to return to. 4 * Artificially force an assert. */ 5 /* 在没有可返回的ports中未实现。人工强制断言。 */ 6 configASSERT( uxCriticalNesting == 1000UL ); 7 }
2.7 vPortEnterCritical
1 #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() 2 #define portNVIC_INT_CTRL_REG ( *( ( volatile uint32_t * ) 0xe000ed04 ) ) 3 /* Masks off all bits but the VECTACTIVE bits in the ICSR register. */ 4 /* 屏蔽所有bit位,除了ICSR寄存器里的VECTACTIVE的位 */ 5 #define portVECTACTIVE_MASK ( 0xFFUL ) 6 7 void vPortEnterCritical( void ) 8 { 9 // 这个宏就是vPortRaiseBASEPRI,在前面的文章中已解析过了。 10 portDISABLE_INTERRUPTS(); 11 // 在启动调度器的时候,该值初始化为0。 12 uxCriticalNesting++; 13 14 /* This is not the interrupt safe version of the enter critical function so 15 * assert() if it is being called from an interrupt context. Only API 16 * functions that end in "FromISR" can be used in an interrupt. Only assert if 17 * the critical nesting count is 1 to protect against recursive calls if the 18 * assert function also uses a critical section. */ 19 if( uxCriticalNesting == 1 ) 20 { 21 // portNVIC_INT_CTRL_REG这个寄存器前面的文章也已描述过了。 22 // VECTACTIVE(bit0~9) & 0xFF非0的话,表明当前有中断正在处理。 23 // 这个函数不应该在中断中被调用,所以非0则assert。 24 configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); 25 } 26 }
该函数用于进入临界区,freeRTOS进入临界区的方法很简单,就是屏蔽所有会调用系统调用的中断,并用一个变量uxCriticalNesting记录递归调用。
2.8 vPortExitCritical
1 #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) 2 3 void vPortExitCritical( void ) 4 { 5 // 只有进入临界区(uxCriticalNesting必定大于0)了,才需要退出。 6 configASSERT( uxCriticalNesting ); 7 uxCriticalNesting--; 8 9 // 如果此时uxCriticalNesting非0,说明vPortEnterCritical存在递归调用, 10 // 还有其他任务或者内核在临界区中,此时不能开中断。 11 if( uxCriticalNesting == 0 ) 12 { 13 portENABLE_INTERRUPTS(); 14 } 15 }
该函数用于退出临界区。
2.9 xPortPendSVHandler
下面的解析,去除了FPU相关的代码,下图为任务栈的图,当前任务保存现场之后和下一个就绪任务的现场部分应该是一样的。
1 __asm void xPortPendSVHandler( void ) 2 { 3 extern uxCriticalNesting; 4 extern pxCurrentTCB; 5 extern vTaskSwitchContext; 6 7 /* *INDENT-OFF* */ 8 PRESERVE8 9 10 mrs r0, psp // r0 = psp,psp是任务用的栈指针 11 isb 12 /* Get the location of the current TCB. */ 13 ldr r3, =pxCurrentTCB 14 ldr r2, [ r3 ] // r2 = pxCurrentTCB->pxTopOfStack 15 16 /* Save the core registers. */ 17 stmdb r0!, {r4-r11, r14} // 将当前任务的r14,r4~r11入栈 18 19 /* Save the new top of stack into the first member of the TCB. */ 20 str r0, [ r2 ] // 将r0的值写入pxCurrentTCB->pxTopOfStack 21 22 stmdb sp!, {r0, r3} // 因为在中断中,所以这里的sp值的是msp,保存r0和r3的值 23 mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY 24 msr basepri, r0 // 屏蔽中断 25 dsb 26 isb 27 bl vTaskSwitchContext // 找到下一个要运行的任务,并将pxCurrentTCB指向该任务的TCB 28 mov r0, #0 29 msr basepri, r0 // 启动中断 30 ldmia sp!, {r0, r3} // 恢复r0和r3的值 31 32 /* The first item in pxCurrentTCB is the task top of stack. */ 33 ldr r1, [ r3 ] 34 ldr r0, [ r1 ] 35 36 /* Pop the core registers. */ 37 ldmia r0!, {r4-r11, r14} // 将pxCurrentTCB的任务的现场恢复 38 39 msr psp, r0 // 将pxCurrentTCB的任务的栈赋给psp 40 isb 41 42 bx r14 // 触发中断返回序列 43 /* *INDENT-ON* */ 44 }
该函数是PendSV中断的处理函数,是freeRTOS的核心函数之一,主要用于执行上下文切换,并将CPU让给下一个就绪任务。
2.10 xPortSysTickHandler
1 void xPortSysTickHandler( void ) 2 { 3 /* The SysTick runs at the lowest interrupt priority, so when this interrupt 4 * executes all interrupts must be unmasked. There is therefore no need to 5 * save and then restore the interrupt mask value as its value is already 6 * known - therefore the slightly faster vPortRaiseBASEPRI() function is used 7 * in place of portSET_INTERRUPT_MASK_FROM_ISR(). */ 8 // 屏蔽中断。 9 vPortRaiseBASEPRI(); 10 { 11 /* Increment the RTOS tick. */ 12 // 滴答时钟的处理函数,如果返回pdTRUE的话,则表示要执行一次调度。 13 if( xTaskIncrementTick() != pdFALSE ) 14 { 15 /* A context switch is required. Context switching is performed in 16 * the PendSV interrupt. Pend the PendSV interrupt. */ 17 // 触发PendSV中断,该中断处理函数,在上面已经解析过了。 18 portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 19 } 20 } 21 22 // 开启所有中断 23 vPortClearBASEPRIFromISR(); 24 }
这是系统滴答时钟的中断处理函数,它每隔一定时间就会执行一次。
2.11 vPortSetupTimerInterrupt
1 __weak void vPortSetupTimerInterrupt( void ) 2 { 3 /* Calculate the constants required to configure the tick interrupt. */ 4 #if ( configUSE_TICKLESS_IDLE == 1 ) 5 { 6 ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ); 7 xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick; 8 ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ ); 9 } 10 #endif /* configUSE_TICKLESS_IDLE */ 11 12 /* Stop and clear the SysTick. */ 13 portNVIC_SYSTICK_CTRL_REG = 0UL; 14 portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; 15 16 /* Configure SysTick to interrupt at the requested rate. */ 17 portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL; 18 portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT ); 19 }
这是配置滴答时钟的函数,根据手册配置即可,这里是配置成1ms中断。
2.12 vPortValidateInterruptPriority
1 void vPortValidateInterruptPriority( void ) 2 { 3 uint32_t ulCurrentInterrupt; 4 uint8_t ucCurrentPriority; 5 6 /* Obtain the number of the currently executing interrupt. */ 7 ulCurrentInterrupt = vPortGetIPSR(); 8 9 /* Is the interrupt number a user defined interrupt? */ 10 if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER ) 11 { 12 /* Look up the interrupt's priority. */ 13 ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ]; 14 configASSERT( ucCurrentPriority >= ucMaxSysCallPriority ); 15 } 16 17 configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue ); 18 }
这是用于检查在中断中调用带有FromISR后缀的系统调用时,该中断优先级是否属于configMAX_SYSCALL_INTERRUPT_PRIORITY下的,这是因为freeRTOS屏蔽中断时只会屏蔽中断优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,而更高优先级的中断不会被屏蔽,所以如果更高优先级的中断中调用带有FromISR后缀的系统调用时,freeRTOS就可能无法正常运行。
具体解析这里就不做了,去看里面用到的寄存器的描述就懂了。
至此port.c的函数基本都过了一遍,现在已经了解了freeRTOS是如何进行任务切换的以及进入退出临界区的方式等等,到现在freeRTOS的基础函数都解析过了,它的调度、事件、队列等的处理都会用到里面的一个或几个函数或宏,后面开始freeRTOS的第二个核心:任务处理/调度。