前言
- 最近在研究RTOS内核的实现,于是就翻阅一本较 uCOS-II 的书,【嵌入式实时操作系统 uC/OS-II 第二版】,这本书买的比较早了,之前没有细细的看,最近对照着uCOS-II的代码,看了起来,学到了很多
- uCOS-II 的代码实现比较的经典,对深入学习内核实现原理与掌握内核设计的开发技术有很大的参考价值
任务切换流程
- 任务切换本质上就是CPU 寄存器的切换,CPU工作需要一套寄存器,改变这套寄存器,就改变了CPU的处理环境(场景),也就实现了任务切换。
- 为了保证任务被切走,还能切回来继续(接着)执行,需要保存老的,恢复新的。
- uCOS-II 中称之为 上下文(Context),uCOS-II 在 ARM Cortex-M3 系列MCU上,上下文切换在 PendSV 中断中执行的
OS_CPU_PendSVHandler
- 这个是 uCOS-II 的 PendSV 中断处理函数,实现了任务上下文的切换,通过长长的注释,很经典的说明了任务切换的流程,这里做个随笔记录一下
- 原文注释如下:
;********************************************************************************************************
; 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-M. This is because the Cortex-M auto-saves half of the
; processor context on any exception, and restores same on return from exception. So only
; saving of R4-R11 & R14 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
; b) Save remaining regs r4-r11 & r14 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 and R14 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).
;
; 5) Increasing priority using a write to BASEPRI does not take effect immediately.
; (a) IMPLICATION This erratum means that the instruction after an MSR to boost BASEPRI
; might incorrectly be preempted by an insufficient high priority exception.
;
; (b) WORKAROUND The MSR to boost BASEPRI can be replaced by the following code sequence:
;
; CPSID i
; MSR to BASEPRI
; DSB
; ISB
; CPSIE i
;********************************************************************************************************
PendSV
- PendSV 被用于触发(引起)一个任务切换(上下文切换)
- 在Cortex-M 系列中,这是一种【推荐】的方式
- Cortex-M 在异常(中断)情况下,会【自动保存】一半处理器的上下文(寄存器),退出异常(中断)后会自动恢复。这就解释了为什么我们不手动保存r0~r3、r12、r15、xPSR等寄存器了,注意r14 (LR)这个特殊寄存器,以及 r13 (SP)栈指针寄存器的处理
- 所以只需要手动保存 R4~R11 以及 R14 寄存器即可(压栈方式)
- 使用PendSV 异常 来做任务切换,保存与恢复(上下文),不管是在中断还是异常中触发的任务切换,都是同样处理
切换流程
- 获取 PSP(Process SP,可以称之为:进程堆栈指针)
- 保存【剩余的】r4~r11 r14 寄存器 到 进程栈 (process stack),因为其他的寄存器在进入中断异常时,由MCU 自动保存了
- 保存线程栈 SP 到 自己的 TCB任务控制块中,
OSTCBCur->OSTCBStkPtr = SP; - 调用 钩子函数
OSTaskSwHook ,注意这个钩子函数必须定义,函数执行可以为空,否则会跳转到【空指针】 - 获取当前最高的优先级,这个已经获取到,保存在了 OSPrioHighRdy,
OSPrioCur = OSPrioHighRdy; - 获取【即将切换任务】的TCB 任务控制块,
OSTCBCur = OSTCBHighRdy; - 从【即将切换任务】的TCB 中,获取新的进程堆栈指针(Process SP),
SP = OSTCBHighRdy->OSTCBStkPtr; - 新的PSP中恢复 r4-r11 和 r14 (到CPU),这样CPU 的这几个寄存器被更新,
- 处理异常返回,这样完成【整个任务上下文】的切换,注意其他自动保存(压栈)的寄存器,如r0~r3,r12、 r13、 r15等,会自动的恢复,不需要手动处理
说明
- ARM Cortex-M 系列的CPU,进入异常(中断)时寄存器
xPSR, PC, LR, R12, R0-R3 会自动的保存,其中 PC 为 r15, LR 为 r14 - SP r13 寄存器,保存在任务控制块中
OSTCBStkPtr - 处理器进入中断,处理器本身的模式由
Thread mode 切换为 Handler mode - 栈 由
Process stack 切换为 Main stack - OSTCBCur 指向即将被切换的线程(suspend),被保存挂起
- OSTCBHighRdy 指向即将切换线程(resume),被恢复
- PendSV 的优先级最低,注意切换PSP时,需要保证不被其他的异常或中断打断,需要屏蔽全局中断等处理保证安全的切换
小结
- 操作系统内核的设计博大精深,值得细细研读
- 了解基于ARM Cortex-M 系列CPU的PendSV,了解任务切换的流程,会对RTOS内核有更进一步的认识
|