xTaskCreateStatic( )
rvInitialiseNewTask( )
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
pxTopOfStack--;
*pxTopOfStack = portINITIAL_XPSR; /* xPSR的bit24必须置1 */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC,即任务入口函数 */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR,函数返回地址 */
pxTopOfStack -= 5; /* R12, R3, R2 and R1 默认初始化为0 */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0,任务形参 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4默认初始化为0 */
return pxTopOfStack;
}
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 )使pxTopOfStack指向高地址处,
函数pxPortInitialiseStack()就是给这些寄存器赋值,该函数返回后,pxTopOfStack指向低地址处
因为进行任务切换时会调用到vPortSVCHandler中断处理函数,在该函数中,pxCurrentTCB指向rvInitialiseNewTask()函数中的pxNewTCB,
ldmia r0!, {r4-r11}就是将pxPortInitialiseStack()函数里面pxTopOfStack指向的内存中的值赋给r4-r11
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1] 将pxCurrentTCB 的第一个成员即pxTopOfStack赋给r0,由于pxTopOfStack是指向低地址处,ldmia r0!, {r4-r11}是先将内存中的值加载到寄存器,再使r0递增,
这一指令执行完后,r0其实指向了“自己”
msr psp, r0使栈指针指向r0
bx r14跳转指令,调到PC所指向的地方,在跳转之前,硬件会自动进行出栈操作,将栈中剩余的值依次弹出给r0 r1 r2 r3 r12 r14 r15,r15即PC,在函数pxPortInitialiseStack()中,
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;这是任务的入口,在退栈时,这个值会赋给PC,当指向bx r14跳转指令时,就会跳转到任务入口处,
__asm void vPortSVCHandler( void )
{
extern pxCurrentTCB;
PRESERVE8
ldr r3, =pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */
ldr r1, [r3] /* 加载pxCurrentTCB到r1 */
ldr r0, [r1] /* 加载pxCurrentTCB指向的值到r0,目前r0的值等于第一个任务堆栈的栈顶 */
ldmia r0!, {r4-r11} /* 以r0为基地址,将栈里面的内容加载到r4~r11寄存器,同时r0会递增 */
msr psp, r0 /* 将r0的值,即任务的栈指针更新到psp */
isb
mov r0, #0 /* 设置r0的值为0 */
msr basepri, r0 /* 设置basepri寄存器的值为0,即所有的中断都没有被屏蔽 */
orr r14, #0xd /* 当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,
使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回Thumb状态 */
bx r14 /* 异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:
xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
同时PSP的值也将更新,即指向任务栈的栈顶 */
}
程序如何进入vPortSVCHandler中断处理函数:svc 0 就是触发SVC中断,从而进入vPortSVCHandler()中断处理函数,从而跳转到任务中去执行
vTaskStartScheduler()
xPortStartScheduler()
prvStartFirstTask()
prvStartFirstTask()
__asm void prvStartFirstTask( void )
{
PRESERVE8
/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,
里面存放的是向量表的起始地址,即MSP的地址 */
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/* 设置主堆栈指针msp的值 */
msr msp, r0
/* 使能全局中断 */
cpsie i
cpsie f
dsb
isb
/* 调用SVC去启动第一个任务 */
svc 0
nop
nop
}
在任务中如何切换到另一个任务:
这里以手动切换为例,假设自此时在执行task1(),portYIELD()会产生一个PendSV中断,从而进入xPortPendSVHandler()中断处理函数,在该函数中bl vTaskSwitchContext指令是跳转到vTaskSwitchContext()函数中去更改pxCurrentTCB所指向的任务控制块,从而改变r3的指向,任务控制块的pxTopOfStack里设置了任务的入口地址,xPortPendSVHandler()中断函数退栈时,会将另一个任务的入口地址赋值给PC,从而切换到另一个任务去执行
void Task1_Entry( )
{
for( ;; )
{
portYIELD();
}
}
#define portYIELD() \
{ \
/* 触发PendSV,产生上下文切换 */ \
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \
__dsb( portSY_FULL_READ_WRITE ); \
__isb( portSY_FULL_READ_WRITE ); \
}
__asm void xPortPendSVHandler( void )
{
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
ldr r3, =pxCurrentTCB /* 加载pxCurrentTCB的地址到r3 */
ldr r2, [r3] /* 加载pxCurrentTCB到r2 */
stmdb r0!, {r4-r11} /* 将CPU寄存器r4~r11的值存储到r0指向的地址 */
str r0, [r2] /* 将任务栈的新的栈顶指针存储到当前任务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 /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */
mov r0, #0 /* 退出临界段 */
msr basepri, r0
ldmia sp!, {r3, r14} /* 恢复r3和r14 */
ldr r1, [r3]
ldr r0, [r1] /* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/
ldmia r0!, {r4-r11} /* 出栈 */
msr psp, r0
isb
bx r14 /* 异常发生时,R14中保存异常返回标志,包括返回后进入线程模式还是处理器模式、
使用PSP堆栈指针还是MSP堆栈指针,当调用 bx r14指令后,硬件会知道要从异常返回,
然后出栈,这个时候堆栈指针PSP已经指向了新任务堆栈的正确位置,
当新任务的运行地址被出栈到PC寄存器后,新的任务也会被执行。*/
nop
}
void vTaskSwitchContext( void )
{
/* 两个任务轮流切换 */
if( pxCurrentTCB == &Task1TCB )
{
pxCurrentTCB = &Task2TCB;
}
else
{
pxCurrentTCB = &Task1TCB;
}
}