Coos与上下文切换有关的文件主要是arch.c和port.c。
Coos使用PendSV_Handler中断进行上下文切换。
Coos使用SysTick_Handler中断作为trick计时,并引发调度,导致上下文切换。
Coos在等待sem, mutex,event,创建/删除/挂起/Delay task,修改task优先级会引发调度,导致上下文切换。
Schedule函数判断是否要切换上下问,如果要找出要切换的下一个task的pcb放到TCBNext中。
执行SwitchContext引发上下文切换
U32 NVIC_INT_CTRL = 0xE000ED04; // Interrupt control state register
U32 NVIC_PENDSVSET = 0x10000000; // Value to trigger PendSV exception
void SwitchContext(void)
{
__asm volatile
(
" LDR R3,=NVIC_INT_CTRL \n"
" LDR R3,[R3] \n"
" LDR R2,=NVIC_PENDSVSET \n"
" LDR R1,[R2] \n"
" STR R1, [R3] \n"
" BX LR \n"
);
}
SwitchContext通过向0xE000ED04写0x10000000触发PendSV中断,在PendSV中断中进行上下文切换。
void PendSV_Handler(void)
{
////////debug block /////////////////////////
__asm volatile
(
" LDR R3,=TCBRunning \n"
" LDR R1,[R3] \n" // R1 == running tcb
" LDR R2,=TCBNext \n"
" LDR R2,[R2] \n" // R2 == next tcb
" CMP R1,R2 \n"
" BEQ exitPendSV \n" //如果上下文是同一个task,不做切换直接退出
" MRS R0, PSP \n" //将SP保存在R0中,Cortex-M0在中断处理中,使用的是MSP,而各个Task的SP是保存在PSP中,需要获得当前执行任务的SP,需要从PSP中读出
//Cortex-M0在进中断时硬件会自动将R0~R3,R12,RL(R14),PC(R15),xPSR入栈,因此不用软件保护
//R4~R11软件入栈,完成当前任务的上下文切换
" SUB R0,R0,#32 \n" //SP指针移动32字节,准备保存R4~R11
" STR R0,[R1] \n" //将SP指针保存到R1中,因为之后的SIMIA会修改R0
" STMIA R0!,{R4-R7} \n" //将R4~R7入栈
" MOV R4,R8 \n"
" MOV R5,R9 \n"
" MOV R6,R10 \n"
" MOV R7,R11 \n"
" STMIA R0!,{R4-R7} \n" //将R8~R11入栈
//切换到下一个task
" popStk: \n"
" STR R2, [R3] \n" // TCBRunning = TCBNext;
" LDR R0, [R2] \n" // 把next task 的sp指针送到R0中.
" ADD R0,R0,#16 \n" //SP指针移动堆栈内的R8保存处,准备恢复R8~R11
" LDMIA R0!,{R4-R7} \n" // Restore new Context (R8-R11)
" MOV R8,R4 \n"
" MOV R9,R5 \n"
" MOV R10,R6 \n"
" MOV R11,R7 \n"
" SUB R0,R0,#32 \n" //SP移动到堆栈内的R4保存处,准备恢复R4~R7
" LDMIA R0!,{R4-R7} \n" // Restore new Context (R4-R7)
" ADD R0,R0,#16 \n" //将SP指向堆栈内的R0保存处
" MSR PSP, R0 \n" //将新的SP更新到PSP中
" exitPendSV: \n" //将调度锁设置为0
" LDR R3,=OSSchedLock \n"
" MOV R0, #0x0 \n"
" STRB R0, [R3] \n"
" LDR R3,=0xFFFFFFED \n" //设置EXC_RETURN为返回到thread mode使用PSP作为SP
" LDR R0, [R3] \n"
" BX R0 \n" // Exit interrupt,完成上下文切换
);
}
以上过程为中断处理过程,整个上下文切换包括硬件处理和软件处理可图示为:
Cortex-M0的R13为SP指针,根据CONTROL寄存器的bit1不同,分别操作MSP和PSP。
进行POP和PUSH时根据CONTROL[1]的不同,选择操作MSP或是PSP.
当Cortex-M0上电启动时,默认是MSP
Coos OS在第一次调度前都处于使用MSP的状态。
调用CoInitOS初始化OS,此时会创建一个idle task。
创建task_init。
调用CoStartOS开始调度。将runing tcb设置为idle task,将task init设置为next tcb。
由于在CoStartOS的时候会引发第一次调度,此后就不会调度回CoStartOS的调用者。因此要在CoStartOS前再创建初始化任务,否则无法调度起来。
第一次调度发生时,会将CoStartOS调用者的R0~R3,R12,R14,R15入栈到MSP,当第一次调度完成后,返回到Thread mode使用PSP,之后除了在中断处理下使用MSP,再无机会使用MSP,因此CoStartOS的调用者入栈的内容无法再使用。