任务创建
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, /* 任务函数的指针 */
const char * const pcName, /* 任务函数名字 */
const configSTACK_DEPTH_TYPE usStackDepth, /* 任务栈的大小,单位为字 */
void * const pvParameters, /* 任务函数的参数 */
UBaseType_t uxPriority, /* 任务的优先级 */
TaskHandle_t * const pxCreatedTask ) /* 任务句柄,以后使用其操作任务 */
- 调度模式
- 由configUSE_PREEMPTION决定是抢占式(1)还是合作式(0)
- 由configUSE_PREEMPTION决定是抢占式(1)还是合作式(0)
切换任务—依赖于systick定时器的中断
- 两种情况下会触发pendSV: 1.执行某些系统调用函数(如taskYIELD); 2.SysTick中断(xPortSysTickHandler)
//整个切换的函数调用vTaskStartScheduler --> xPortStartScheduler
//--> prvPortStartFirstTask --> vPortSVCHandler
static void prvPortStartFirstTask( void )
{
__asm volatile(
" ldr r0, =0xE000ED08 \n" /* 这三步将msp的值保存在r0中 */
" ldr r0, [r0] \n" /* 此时r0等于0x0*/
" ldr r0, [r0] \n" /* ro取到的是msp的值 */
" msr msp, r0 \n" /* */
" cpsie i \n" /* 开启全局中断 */
" cpsie f \n"
" dsb \n"
" isb \n"
" svc 0 \n" /* 调用SVC中断,0对应的系统服务是vPortSVCHandler?*/
" nop \n"
);
}
//将之前存在栈中的值,加载到寄存器,即恢复现场
void vPortSVCHandler( void )
{
__asm volatile (
" ldr r3, pxCurrentTCBConst2 \n" /* 加载pxCurrentTCBConst2地址到r3 */
" ldr r1, [r3] \n" /* 用pxCurrentTCBConst获取pxCurrentTCB地址*/
" ldr r0, [r1] \n" /* 任务控制块pxCurrentTCB的第一个成员是栈顶指针 */
" ldmia r0!, {r4-r11} \n" /* 将r0为基地址,依次将栈中的值存到r4-r11 */
" msr psp, r0 \n" /* 将现在的栈顶指针存到psp */
" isb \n"
" mov r0, #0 \n"
" msr basepri, r0 \n" /* 打开所有中断 */
" orr r14, #0xd \n" /* 为下一步做预先处理 */
" bx r14 \n" /* 异常返回,自动将栈中剩余内容存到剩下的寄存器中 */
" \n"
" .align 4 \n" /* 这个是4字对齐 */
"pxCurrentTCBConst2: .word pxCurrentTCB \n"
/* 链接地址的别名,相当于一个指针;冒号相当于goto的直接跳转,而.word表示变量*/
);
}
//完成上文的保存,根据优先级,切换当前任务控制块
//xPortPendSVHandler中断服务函数,定义于portable\RVDS\ARM_CM3\port.c
__asm void xPortPendSVHandler( void )//pend代表可挂起的中断
{
extern pxCurrentTCB; /* 定义于tasks.c的全局指针,指向正在运行的任务控制块 */
extern vTaskSwitchContext; /* 也定义于tasks.c */
PRESERVE8 /* 按字节对齐 */
mrs r0, psp /* 将进程堆栈指针的值存到r0寄存器 */
isb /* 清洗流水线,保证前面的指令已全部执行*/
ldr r3, =pxCurrentTCB /* 将当前任务控制块的地址存到r3 */
ldr r2, [ r3 ] /* 加载r3指向的内容到r2,即r2=pxCurrentTCB */
stmdb r0 !, { r4 - r11 } /* Save the remaining registers. */
str r0, [ r2 ] /* Save the new top of stack into the first member of the TCB. */
stmdb sp !, { r3, r14 }
/* 将R3和R14临时压入堆栈,因为即将调用函数vTaskSwitchContext,
调用函数时,返回地址自动保存到R14中,所以一旦调用发生,R14的值会被覆盖,因此需要入栈保护;
R3保存的当前激活的任务TCB指针(pxCurrentTCB)地址,函数调用后会用到,因此也要入栈保护*/
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0 /* 这一句和上一句实现关中断,通过写寄存器实现,中断屏蔽寄存器之一 */
dsb
isb
bl vTaskSwitchContext /* 完成全局指针pxCurrentTCB的更新,使变量pxCurrentTCB指向新的任务来实现任务切换 */
mov r0, #0
msr basepri, r0 /* 这一句和上一句实现开中断 */
ldmia sp !, { r3, r14 }
ldr r1, [ r3 ]
ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */
ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */
msr psp, r0
isb
bx r14
nop
}
空闲任务
系统在启动调度器时创建的优先级最低的任务
//启动调度器创建:
void vTaskStartScheduler( void )
{
...
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT,
&xIdleTaskHandle );
}
//实际的空闲任务void prvIdleTask( void *pvParameters );也即等于下面的函数
static portTASK_FUNCTION( prvIdleTask, pvParameters )
{
( void ) pvParameters;
portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE );
for( ;; )
{
prvCheckTasksWaitingTermination();
/*如果有任务删除了自身,idle线程要负责回收和删除TCB和栈*/
#if ( configUSE_PREEMPTION == 0 )
{
/* 如果没有开启任务抢占,则强制进入任务调度 */
taskYIELD();
}
#endif
#if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) )
{
/* 开启了任务抢占,且和IDLE线程具有相同优先级的线程不止一个,则切换任务,按时间片轮转 */
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )
> ( UBaseType_t ) 1 )
{
taskYIELD();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */
#if ( configUSE_IDLE_HOOK == 1 )
{
extern void vApplicationIdleHook( void );
/* 空闲任务钩子函数,这个函数里的操作可以由用户自己定义,
但不能调用任何可以阻塞空闲任务的 API 函数(要保证至少有一个任务运行)*/
vApplicationIdleHook();
}
#endif
/* tickless相关的操作,由IDLE线程调用 */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;
xExpectedIdleTime = prvGetExpectedIdleTime();/* 获取等待时间 */
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{ /* 大于2,即要等待超过两个tick中断时,才会去关调度,再判断进入tickless操作 */
vTaskSuspendAll();
{
configASSERT( xNextTaskUnblockTime >= xTickCount );
xExpectedIdleTime = prvGetExpectedIdleTime();
/* 如果不想去调用portSUPPRESS_TICKS_AND_SLEEP,则下一句话会将xExpectedIdleTime置0 */
configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime );
if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
{ /* 进入tickless函数处理,Tickless的关键操作:关中断,补偿时间 */
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
( void ) xTaskResumeAll();
}/* end configUSE_TICKLESS_IDLE*/
}/* end for*/
}