学习FreeRTOS(2):初始化阶段的中断状态

分析FreeRTOS的Cortex-M4F移植版(portable/GCC/ARM_CM4F)在初始化阶段CPU中断状态的变化。复位后中断默认处于开启状态,当创建第一个任务时中断被关闭,开启调度器时重新开启中断。总体上也适用于其它Cortex-M,部分细节可能略有差别。

mingdu.zheng at gmail dot com

Cortex-M在复位后,中断处于开启状态,即PRIMASK=0,FAULTMASK=0,BASEPRI=0。第一次调用xTaskCreate创建任务后,中断将会被关闭,BASEPRI=0x80。

// 中断被关闭时的调用栈
// xTaskCreate
// prvAllocateTCBAndStack
// pvPortMallocAligned
// xTaskResumeAll
// taskENTER_CRITICAL
// vPortEnterCritical

默认开启的中断在vPortEnterCritical函数中被关闭。来看一下vPortEnterCritical和vPortExitCritical函数。

void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;

/* This is not the interrupt safe version of the enter critical function so
assert() if it is being called from an interrupt context. Only API
functions that end in "FromISR" can be used in an interrupt. Only assert if
the critical nesting count is 1 to protect against recursive calls if the
assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}

vPortEnterCritical函数首先调用portDISABLE_INTERRUPTS关闭中断,然后将uxCriticalNesting加1。

/*-----------------------------------------------------------*/
// xTaskResumeAll函数结束时调用vPortExitCritical
// xTaskCreate
// prvAllocateTCBAndStack
// pvPortMallocAligned
// xTaskResumeAll
// taskEXIT_CRITICAL
// vPortExitCritical
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}

vPortExitCritical函数首先将uxCriticalNesting减1,如果uxCriticalNesting减到0,那么调用portENABLE_INTERRUPTS重新打开中断。对vPortEnterCritical和vPortExitCritical函数的分析,可以得出一个结论:当xTaskResumeAll调用vPortExitCritical后中断将会重新被打开。而实际上并非如此,因为uxCriticalNesting不是被初始化成0,而是被初始化成0xaaaaaaaa,因此中断仍然处于关闭状态,直到调用vTaskStartScheduler开启调度器并执行第一个线程。

/* Each task maintains its own interrupt status in the critical nesting
variable. */
static UBaseType_t uxCriticalNesting = 0xaaaaaaaa;
// 将uxCriticalNesting设置为0的调用栈
// vTaskStartScheduler
// xPortStartScheduler
BaseType_t xPortStartScheduler( void )
{
......
/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;
......
/* Start the first task. */
prvPortStartFirstTask();
......
return 0;
}
static void prvPortStartFirstTask( void )
{
__asm volatile(
" ldr r0, =0xE000ED08 \n" /* Use the NVIC offset register to locate the stack. */
" ldr r0, [r0] \n"
" ldr r0, [r0] \n"
" msr msp, r0 \n" /* Set the msp back to the start of the stack. */
" cpsie i \n" /* Globally enable interrupts. */
" cpsie f \n"
" dsb \n"
" isb \n"
" svc 0 \n" /* System call to start first task. */
" nop \n"
);
}
void vPortSVCHandler( void )
{
__asm volatile (
" ldr r3, pxCurrentTCBConst2 \n" /* Restore the context. */
" ldr r1, [r3] \n" /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" ldmia r0!, {r4-r11, r14} \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
" msr psp, r0 \n" /* Restore the task stack pointer. */
" isb \n"
" mov r0, #0 \n"
" msr basepri, r0 \n"
" bx r14 \n"
" \n"
" .align 2 \n"
"pxCurrentTCBConst2: .word pxCurrentTCB \n"
);
}

vTaskStartScheduler将uxCriticalNesting设置成0,随后prvPortStartFirstTask触发了一个SVC,SVC处理函数是vPortSVCHandler,vPortSVCHandler开启了中断(mov r0, #0; msr basepri, r0,这两条指令将BASEPRI设置为0,中断处于开启状态)并进入了第一个线程,在这之后vPortEnterCritical和vPortExitCritical才会按照预期的方式工作,在这之前vPortEnterCritical和vPortExitCritical是不起作用的。