1. 任务调度器启动相关API函数
函数 | 描述 |
---|---|
vTaskStartScheduler() | 开启任务调度器 |
任务调度启动中其它重要的API函数(介绍过的函数不列出,请参考前面的文章):
函数 | 描述 |
---|---|
xPortStartScheduler() | 由函数vTaskStartScheduler()调用,配置系统节拍时钟,并启动第一个任务 |
prvStartFirstTask() | 复位MSP,调用触发SVC中断,启动第一个任务 |
vPortSVCHandler() | SVC中断服务函数 |
2. 任务调度的基本知识
任务调度器是使用相关的调度算法来找到最高优先级的任务,执行任务切换,从而总是使最高优先级的任务占用CPU资源。在嵌入式实时操作系统中,最核心的部分也就是任务调度和任务切换,任务调度最核心的是调度算法,为了性能最优,部分代码会使用汇编指令。
2.1 FreeRTOS支持的三种调度方式
FreeRTOS支持的三种调度方式有抢占式调度,时间片调度和合作式调度。 实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。
2.1.1 抢占式调度器基本概念
每个任务都有一个优先级,高优先级的任务能打断低优先级的任务,抢占CPU的的资源。如果任务在执行过程中,有更高优先级的任务就绪,调度器会执行调度,让更高优先级的任务占用CPU资源。
2.1.1 时间片调度器基本概念
当多个任务拥有同一个优先级时,FreeRTOS允许每个任务运行一个系统节拍后让出CPU使用权给其它任务。使用时间片调度器功能,需要设置以下两个宏定义。
#define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程
#define configUSE_TIME_SLICING 1 //1使能时间片调度(默认是使能的)
时间片的长度为一个系统节拍,设置如下:
#define configTICK_RATE_HZ (1000) //每秒中断多少次;时钟节拍频率,这里设置为1000,周期就是1ms
若某优先级下有3个就绪态任务,执行过程如下:
如果任务在一个系统节拍后仍没有执行完,则会让出CPU使用权,等到下一个节拍接着运行。如果一个系统节拍内提前执行完成,则会提前执行任务切换,让出CPU使用权。
3. 开启任务调度源代码分析
3.1 函数vTaskStartScheduler()分析
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 );
/* 使用静态方式创建一个空闲任务,优先级为0 */
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
"IDLE",
ulIdleTaskStackSize,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,
pxIdleTaskTCBBuffer );
/* 如果申请内存成功 */
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else /* 如果不使用静态方式创建任务 */
{
/* 使用动态方式创建一个空闲任务 */
xReturn = xTaskCreate( prvIdleTask,
"IDLE", configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle );
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
/* 如果使用软件定时器功能 */
#if ( configUSE_TIMERS == 1 )
{
/* 如果创建空闲任务成功 */
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask(); /* 创建一个定时器任务 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
portDISABLE_INTERRUPTS();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY; /* 设置下一个唤醒任务系统节拍为portMAX_DELAY */
xSchedulerRunning = pdTRUE; /* 使能任务调度器 */
xTickCount = ( TickType_t ) 0U; /* 设置系统时钟片为0 */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); /* 空代码 */
if( xPortStartScheduler() != pdFALSE )
{
/* 如果调度器启动成功,不会允许到这里 */
}
else
{
/* 当调用 xTaskEndScheduler()才会运行到这里 */
}
}
else
{
/* 如果允许到这里,说明创建空闲任务或定时任务失败,内存分配出错 */
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
/* 防止编译器警告 */
( void ) xIdleTaskHandle;
}
3.2 函数xPortStartScheduler()
BaseType_t xPortStartScheduler( void )
{
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
/*
* 获取第一个中断优先级寄存器的地址:0xE000 E400
* 每个中断有8位表示优先级,其中高4位有效,连续4个中断组成一个32位的NVIC_IPRx
*/
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
ulOriginalPriority = *pucFirstUserPriorityRegister; /* 保存原有的寄存器的值 */
/*
* 首先向所有位写1,然后再读出来
* 由于无效的优先级位读出为0,确定1的个数就能知道平台有多少位表示优先级
*/
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );
/* 算出由FreeRTOS管理的优先级最高中断优先级 */
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
/* 其中的portMAX_PRIGROUP_BITS为( ( uint8_t ) 7 ),即为0000 0111 */
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
/*
* 判断最高优先级数ucMaxPriorityValue有多少个1
* 在Cortex-M3/M4中有抢占优先级(分组优先级)和亚优先级(子优先级),FreeRTOS没有处理亚优先级情况
* ulMaxPRIGROUPValue表示优先级分组
* 当ulMaxPRIGROUPValue等于7时,表示0位抢占优先级;为3表示4位抢占优先级
*/
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
/* 右移8位,配置优先级组寄存器的[10:8]有效 */
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
/* 恢复优先级配置寄存器值 */
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif /* conifgASSERT_DEFINED */
/*
* 每个中断占据一个8位的优先级寄存器设置,数值越小,优先级越高
* 4个相临的寄存器组成32位的寄存器
* 从0xE000 ED20地址,PendSV和SysTick分别占据次高8位和最高8位,其中每8位中高4位才有效
* 设置PendSV和SysTick中断的优先级为0xF0,即最低优先级
*/
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 初始化系统节拍定时器,即系统的SysTick定时器 */
vPortSetupTimerInterrupt();
/* 初始化边界嵌套计数器 */
uxCriticalNesting = 0;
/* 使能FPU功能 */
prvEnableVFP();
/*
* CM4内核有个叫lazy stacking的特性,默认该特性已经开启,软件通过FPCCR寄存器来配置lazy stacking特性
* 它的目的是减少没必要的浮点寄存器入栈和出栈时间
*/
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
/* 触发SVC异常,从而启动第一个任务 */
prvStartFirstTask();
return 0; /* 一般不会返回 */
}
3.3 函数prvStartFirstTask()
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* Cortext-M3硬件中,0xE000ED08地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址*/
ldr r0, =0xE000ED08
/* 取出向量表中的第一项,向量表第一项存储主堆栈指针MSP的初始值 */
ldr r0, [r0] /* 将R0所保存的地址处的值赋值给R0 */
ldr r0, [r0] /* 获取MSP初始值 */
msr msp, r0 /* 复位MSP */
cpsie i /* 使能中断(清除PRIMASK) */
cpsie f /* 使能中断(清除FAULTMASK) */
/* 流水线相关 */
dsb /* 数据同步隔离 */
isb /* 指令同步隔离 */
svc 0 /* 调用SVC指令触发SVC异常,启动第一个任务 */
nop
nop
}
3.4 启动第一个任务
启动第一个任务使用SVC中断,其中断服务函数为SVC_Handler()。操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,这里触发SVC中断,在异常服务中启动第一个任务。
在FreeRTOS中使用了宏定义,如下:
#define vPortSVCHandler SVC_Handler
函数源代码如下:
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB /* R3为pxCurrentTCB的储存地址,pxCurrentTCB永远指向当前需要运行的任务的TCB首地址 */
ldr r1, [r3] /* 将R3所处地址的数据赋值给R1,即获取了当前任务控制块的地址 */
ldr r0, [r1] /* 将R1所处地址的数据赋值给R0,即获取了栈顶指针指向的地址 */
ldmia r0!, {r4-r11, r14} /* 出栈,恢复R4 ~ R11,R14的值 */
msr psp, r0 /* 进程栈指针PSP设置为堆栈 */
isb /* 指令同步屏障 */
mov r0, #0 /* 使R0为0 */
msr basepri, r0 /* 使寄存器basepri为0,即开启中断 */
bx r14
}
参考资料:
【1】: 正点原子:《STM32F407 FreeRTOS开发手册V1.1》
【2】: 野火:《FreeRTOS 内核实现与应用开发实战指南》
【3】: 《Cortex M3权威指南(中文)》