在真正启动调度器之前,先创建空闲任务,初始化一些变量。真正启动调度器的函数是xPortStartScheduler
/* 启动调度器 */
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
#if (configSUPPORT_STATIC_ALLOCATION == 1)
{
StaticTask_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
vApplicationGetIdleTaskMemory(&pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize);
xIdleTaskHandle = xTaskCreateStatic(prvIdleTask, configIDLE_TASK_NAME, ulIdleTaskStackSize,
(void *)NULL, portPRIVILEGE_BIT, pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer);
if(xIdleTaskHandle != NULL)
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
/* 创建空闲任务 */
xReturn = xTaskCreate(prvIdleTask, configIDLE_TASK_NAME, configMINIMAL_STACK_SIZE,
(void *)NULL, portPRIVILEGE_BIT, &xIdleTaskHandle);
}
#endif
#if (configUSE_TIMERS == 1)
{
if(xReturn == pdPASS)
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
if(xReturn == pdPASS)
{
#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
{
freertos_tasks_c_additions_init();
}
#endif
/* 禁止中断 */
portDISABLE_INTERRUPTS();
#if (configUSE_NEWLIB_REENTRANT == 1)
{
_impure_ptr = &(pxCurrentTCB->xNewLib_reent);
}
#endif
/* 下一次解除阻塞的时间初始化为最大 */
xNextTaskUnblockTime = portMAX_DELAY;
/* 调度器运行状态初始化为正在运行 */
xSchedulerRunning = pdTRUE;
/* 初始化当前节拍数为0 */
xTickCount = (TickType_t)configINITIAL_TICK_COUNT;
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
traceTASK_SWITCHED_IN();
/* 启动调度器 */
if(xPortStartScheduler() != pdFALSE)
{
}
else
{
}
}
else
{
configASSERT(xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY);
}
(void)xIdleTaskHandle;
}
启动调度器的本质是启动系统节拍器并切换到第一个任务
/* 启动调度器 */
BaseType_t xPortStartScheduler(void)
{
#if (configASSERT_DEFINED == 1)
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t *const pucFirstUserPriorityRegister = (uint8_t *)(portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER);
volatile uint8_t ucMaxPriorityValue;
ulOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
configASSERT(ucMaxPriorityValue == (configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue));
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while((ucMaxPriorityValue & portTOP_BIT_OF_BYTE) == portTOP_BIT_OF_BYTE)
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= (uint8_t)0x01;
}
#ifdef __NVIC_PRIO_BITS
{
configASSERT((portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue) == __NVIC_PRIO_BITS);
}
#endif
#ifdef configPRIO_BITS
{
configASSERT((portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue) == configPRIO_BITS);
}
#endif
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif
/* 设置PendSV优先级为255(最低) */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
/* 设置SysTick优先级为255(最低) */
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 初始化定时器中断 */
vPortSetupTimerInterrupt();
/* 临界层数初始化为0 */
uxCriticalNesting = 0;
/* 启动第一个任务 */
prvStartFirstTask();
return 0;
}
启动第一个任务,主要工作是请求系统调用(系统调用属于异常,可以获取权限将堆栈指针从MSP切换到PSP)
/* 启动第一个任务 */
__asm void prvStartFirstTask(void)
{
PRESERVE8
/* 向量表偏移寄存器地址 */
ldr r0, =0xE000ED08
/* 向量表起始地址 */
ldr r0, [r0]
/* MSP初始值 */
ldr r0, [r0]
/* 初始化MSP */
msr msp, r0
/* 打开IRQ */
cpsie i
/* 打开FIQ */
cpsie f
dsb
isb
/* 产生系统调用请求 */
svc 0
nop
nop
}
在看系统调用异常处理源代码之前,先了解一下基础知识,参考《Cortex-M3 权威指南》的第九章(中断的具体行为)
发生异常之后会自动进行三个步骤:
1.入栈 2.取向量 3.选择堆栈指针MSP/PSP,更新堆栈指针SP,更新链接寄存器LR,更新程序计数器PC
异常返回之后会自动进行两个步骤:
1.出栈 2.更新NVIC寄存器
在进入异常服务程序后,LR的值被自动更新为特殊的EX_RETURN
系统调用异常处理入口步骤:
1.出栈和入栈寄存器是相对应的不会将r4-r11出栈,因此要手动出栈
2.将当前任务栈设置为PSP
3.通过改变LR的值,返回线程模式并使用线程堆栈
4.跳转到任务
/* 系统调用异常处理入口 */
__asm void vPortSVCHandler(void)
{
PRESERVE8
/* r3=&pxCurrentTCB */
ldr r3, =pxCurrentTCB
/* r1=pxCurrentTCB */
ldr r1, [r3]
/* r0=pxTopOfStack */
ldr r0, [r1]
/* 将任务栈中的r4-r11弹出 */
ldmia r0!, {r4-r11}
/* 将任务栈设置为psp(进程堆栈) */
msr psp, r0
isb
/* 打开中断 */
mov r0, #0
msr basepri, r0
/* 在异常中LR的值被重新解释 */
/* bit31-4:EXC_RETURN bit3:模式 bit2:栈 bit0:arm/thumb */
/* 0xd=1101b 线程模式+PSP+thumb */
/* 根据异常响应序列,异常返回执行自动出栈(PSP),并跳转到PC(原压入PSP中的) */
orr r14, #0xd
bx r14
}