μCOS-II是一个经典的RTOS。
任务切换对于RTOS来说是最基本也是最核心的部分,除此之外还有任务调度算法。
先来看看基于stm32f107的任务切换代码:
;********************************************************************************************************
; START MULTITASKING
; void OSStartHighRdy(void)
;
; Note(s) : 1) This function triggers a PendSV exception (essentially, causes a context switch) to cause
; the first task to start.
;
; 2) OSStartHighRdy() MUST:
; a) Setup PendSV exception priority to lowest;
; b) Set initial PSP to 0, to tell context switcher this is first run;
; c) Set the main stack to OS_CPU_ExceptStkBase;
; d) Set OSRunning to TRUE;
; e) Trigger PendSV exception;
; f) Enable interrupts (tasks will run with interrupts enabled).
;******************************************************************************************************** OSStartHighRdy
LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0] MOVS R0, # ; Set the PSP to 0 for initial context switch call
MSR PSP, R0 LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1 LDR R0, =OSRunning ; OSRunning = TRUE
MOVS R1, #
STRB R1, [R0] LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0] CPSIE I ; Enable interrupts at processor level OSStartHang
B OSStartHang ; Should never get here ;********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From task level)
; void OSCtxSw(void)
;
; Note(s) : 1) OSCtxSw() is called when OS wants to perform a task context switch. This function
; triggers the PendSV exception which is where the real work is done.
;******************************************************************************************************** OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR ;********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From interrupt level)
; void OSIntCtxSw(void)
;
; Notes: 1) OSIntCtxSw() is called by OSIntExit() when it determines a context switch is needed as
; the result of an interrupt. This function simply triggers a PendSV exception which will
; be handled when there are no more interrupts active and interrupts are enabled.
;******************************************************************************************************** OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR ;********************************************************************************************************
; HANDLE PendSV EXCEPTION
; void OS_CPU_PendSVHandler(void)
;
; Note(s) : 1) PendSV is used to cause a context switch. This is a recommended method for performing
; context switches with Cortex-M3. This is because the Cortex-M3 auto-saves half of the
; processor context on any exception, and restores same on return from exception. So only
; saving of R4-R11 is required and fixing up the stack pointers. Using the PendSV exception
; this way means that context saving and restoring is identical whether it is initiated from
; a thread or occurs due to an interrupt or exception.
;
; 2) Pseudo-code is:
; a) Get the process SP, if 0 then skip (goto d) the saving part (first context switch);
; b) Save remaining regs r4-r11 on process stack;
; c) Save the process SP in its TCB, OSTCBCur->OSTCBStkPtr = SP;
; d) Call OSTaskSwHook();
; e) Get current high priority, OSPrioCur = OSPrioHighRdy;
; f) Get current ready thread TCB, OSTCBCur = OSTCBHighRdy;
; g) Get new process SP from TCB, SP = OSTCBHighRdy->OSTCBStkPtr;
; h) Restore R4-R11 from new process stack;
; i) Perform exception return which will restore remaining context.
;
; 3) On entry into PendSV handler:
; a) The following have been saved on the process stack (by processor):
; xPSR, PC, LR, R12, R0-R3
; b) Processor mode is switched to Handler mode (from Thread mode)
; c) Stack is Main stack (switched from Process stack)
; d) OSTCBCur points to the OS_TCB of the task to suspend
; OSTCBHighRdy points to the OS_TCB of the task to resume
;
; 4) Since PendSV is set to lowest priority in the system (by OSStartHighRdy() above), we
; know that it will only be run when no other exception or interrupt is active, and
; therefore safe to assume that context being switched out was using the process stack (PSP).
;******************************************************************************************************** OS_CPU_PendSVHandler
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer
CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11} LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out ; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14} LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0] LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0] LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
OSPendSV()是 PendSV Handler 的中断处理函数(的名称),它实现了上下文切换。这种实现
方式对于 ARM Cortex-M3 来说是强烈推荐的。这是因为对于任何异常,ARM Cortex-M3 可以
自动的保存(进入异常)和恢复上下文(退出异常)的 一部分内容。因此 PendSV handler 只
需要保存和恢复 R4-R11 和堆栈指针这些剩余的上下文。使用了 PendSV 的异常机制,意味
着,无论是由任务触发还是由中断或异常触发的上下文切换都可以用同一种方法实现。
置 注意你必须在异常向量表的位置 14 处设置一个指针指向 OSPendSV()。
PendSV handler 的伪代码如下:
OSPendSV:
if (PSP != NULL) { ()
Save R4-R11 onto task stack; ()
OSTCBCur->OSTCBStkPtr = SP; ()
}
OSTaskSwHook(); ()
OSPrioCur = OSPrioHighRdy; ()
OSTCBCur = OSTCBHighRdy; ()
PSP = OSTCBHighRdy->OSTCBStkPtr; ()
Restore R4-R11 from new task stack; ()
Return from exception; ()
(0) 注意当 OSPendSV 被 CPU 运行,CPU 会自动地保存 xPSR、PC、LR、R12 和 R0-R13 到
任务的堆栈。当将部分上下文保存到相应的任务的堆栈后,CPU 要切换堆栈指针了,
转而使用 SP_main 来运行剩下的中断程序。
(1) 这里我们检查 SP_process 堆栈指针是否为 NULL。重申:OSStartHighRdy()函数将
SP_process 置 NULL 来达到避免当运行第一个任务时保存任务的上下文的目的。
(2) 如果 PendSV()确实被触发来实现一个完整的任务切换,则我们简单的保存上下的寄
存器的值(R4-R11)
(3) 一旦正在被切换的任务的上下文被保存,我们简单的保存任务的堆栈指针
(SP_process)到正在被切换的任务的 OS_TCB。
(4) 接下来我们调用 UCOS 上下文切换的钩子函数(参见 OS_CPU_C.C)。
(5) 像其他硬件平台上移植 UCOS 的做法一样,我们需要赋值新的高优先级的任务的指
针到当前的任务的指针。
(6) 同上,我们需要复制 OSTCBHighRdy 进 OSTCBCur。
(7) 接下来,我们恢复我们希望切换的那个任务的当前栈顶指针。重申,栈顶指针保存
在变量 OSTCBHighRdy->OSTCBStkPtr 中。方便起见,UCOS 总是将.OSTCBStkPtr 放在结
构体 OS_TCB 的开头,这样避免了去查找 SP 的 offset,offset 总是 0。
(8) 我们从任务的堆栈结构中恢复任务的上下文,为任务的执行做准备。
(9) 实现一个异常的返回,这将促使 ARM Cortex-M3 自动从相应的任务堆栈结构中恢复
R3-R12、LR、PC 和 xPSR 寄存器的值。至此便正在运行新任务了。
注意:PendSV Handler 是不可抢占的是不可被中断的,以此保证上下文自动切换。如果上下
文切换时发生中断,则新任务恢复后(上下文切换完毕后)立即执行相应的中断服务程序。
OSStartHighRdy将PSP置零,设置并触发了PendSV,在OS_CPU_PendSVHandler中,会跳过PSP和r4-r11的压栈,这是因为是第一次执行任务切换,之前没有有意义的任务需要保存。
同时可以发现任务级别的切换和中断级别的切换是一样的。
对于移植μCOS-II,基本需要专为操作系统而生的SYSTICK提供的OS心跳、任务切换和禁止使能中断就可以了。