开启辅助访问 切换到窄版

打印 上一主题 下一主题

Cortex-M PendSV 应用

[复制链接]
楼主
跳转到指定楼层
| 只看该作者 回帖奖励 |倒序浏览 |阅读模式


内容目录

一、任务切换
二、原理
三、实战
1、ucos 源码
2、freeRTOS 源码
参考文献


一、任务切换

如果使用了操作系统,任务、进程、线程的切换是必不可少的环节,不同的内核可能有不一样的处理方式,但是原理基本上都是没什么区别—保存上下文。目前使用比较多的嵌入式实时操作系统一般都是 ucos 和 freeRTOS,常用的 MCU 芯片内核一般也都是 cortex-m3 或者 cortex-m4 内核,这个内核针对操作系统做了一个 pendsv 异常类型,至于为什么要设立这个异常,肯定也是有原因的。
二、原理

操作系统都需要有一个心跳,Cortex-m 内核都有一个系统滴答定时器,一般的操作系统都会采用这个滴答定时器作为心跳源。如果需要进行任务切换的,一般都是有触发条件,延时开始、等待信号量或者等待消息队列等,这时候任务进入等待状态,就需要在任务就绪列表里面找到一个优先级最高的任务来运行。当要进行上下问切换的时候,我们有两个选择:
(1)执行一个系统调用
(2)系统滴答定时器(SYSTICK)中断,(轮转调度中需要)
让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过SysTick异常启动上下文切换。


图 2.1 两个任务间通过SysTick进行轮转调度的简单模式

上图是两个任务轮转调度的示意图。但若在产生SysTick异常时正在响应一个中断,则SysTick异常会抢占其ISR。在这种情况下,OS是不能执行上下文切换的,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知任何有一丁点实时要求的系统都决不能容忍这种事。因此,在CM3中也是严禁没商量,如果OS在某中断活跃时尝试切入线程模式,将触犯用法fault异常。
图 2.2 发生IRQ时上下文切换的问题


为解决此问题,早期的OS大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了IRQ,则本次SysTick在执行后不得作上下文切换,只能等待下一次SysTick异常),尤其是当某中断源的频率和SysTick异常的频率比较接近时,会发生“共振”,使上下文切换迟迟不能进行。
现在好了,PendSV来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到其它的ISR都完成了处理后才放行。为实现这个机制,需要把PendSV编程为最低优先级的异常。
如果OS检测到某IRQ正在活动并且被SysTick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换。如下图所示:




图 2.3 使用PendSV控制上下文切换

三、实战

1、ucos 源码

PendSV_Handler
CPSIDI;Preventinterruptionduringcontextswitch
MRSR0,PSP;PSPisprocessstackpointer
CBZR0,OS_CPU_PendSVHandler_nosave;Skipregistersavethefirsttime
SUBSR0,R0,#0x20;Saveremainingregsr4-11onprocessstack
STMR0,{R4-R11}
LDRR1,=OSTCBCur;OSTCBCur->OSTCBStkPtr=SP;
LDRR1,[R1]
STRR0,[R1];R0isSPofprocessbeingswitchedout;Atthispoint,entirecontextofprocesshasbeensaved
OS_CPU_PendSVHandler_nosave
PUSH{R14};SaveLRexc_returnvalue
LDRR0,=OSTaskSwHook;OSTaskSwHook();
BLXR0
POP{R14}
LDRR0,=OSPrioCur;OSPrioCur=OSPrioHighRdy;
LDRR1,=OSPrioHighRdy
LDRBR2,[R1]
STRBR2,[R0]

LDRR0,=OSTCBCur;OSTCBCur=OSTCBHighRdy;
LDRR1,=OSTCBHighRdy
LDRR2,[R1]
STRR2,[R0]

LDRR0,[R2];R0isnewprocessSP;SP=OSTCBHighRdy->OSTCBStkPtr;
LDMR0,{R4-R11};Restorer4-11fromnewprocessstack
ADDSR0,R0,#0x20
MSRPSP,R0;LoadPSPwithnewprocessSP
ORRLR,LR,#0x04;Ensureexceptionreturnusesprocessstack
CPSIEI
BXLR
2、freeRTOS 源码

SVC_Handler:.typefunc
;GetthelocationofthecurrentTCB.
ldr.wr3,=pxCurrentTCB
ldrr1,[r3]
ldrr0,[r1]
;Popthecoreregisters.
ldmiar0!,{r4-r11,r14}
msrpsp,r0
isb
movr0,#0
msrbasepri,r0
bxr14
.sizeSVC_Handler,$-SVC_Handler
.endsec

;-----------------------------------------------------------

.section.text
.thumb
.align4
vPortStartFirstTask.typefunc
;UsetheNVICoffsetregistertolocatethestack.
ldr.wr0,=0xE000ED08
ldrr0,[r0]
ldrr0,[r0]
;Setthemspbacktothestartofthestack.
msrmsp,r0
;CallSVCtostartthefirsttask.
cpsiei
cpsief
dsb
isb
svc0
.sizevPortStartFirstTask,$-vPortStartFirstTask
.endsec
因为 cortex-m 内核在中断或者异常的时候,会自动压栈,依次把xPSR, PC, LR, R12以及R3-R0由硬件自动压入适当的堆栈中:如果当响应异常时,当前的代码正在使用PSP,则压入PSP,也就是使用进程堆栈;否则就压入MSP,使用主堆栈。一旦进入了服务例程,就将一直使用主堆栈。所以上面的代码没有看到具体的进栈操作,只有出栈操作。出栈操作基本也是差不多,都是利用堆栈指针指向当前的任务申请的堆栈,发生任务切换的时候,cortex-m 会自动压栈操作,这时候就会把当前的寄存器值保存下来了,所以最重要的还是 PSP 和 MSP 指向位置,还有一个就是 CORTEX-M 堆栈结构:

图 2.4 寄存器组

所以 RTOS 在任务申请的时候,会进行堆栈初始化,这些初始化就是按照这个结构进行的,不过 RTOS 填了一些无用的值而已。FreeRTOS 源码就比 ucos 简洁很多,
ldr.wr3,=pxCurrentTCB
ldrr1,[r3]
ldrr0,[r1]
;Popthecoreregisters.
ldmiar0!,{r4-r11,r14}
msrpsp,r0
取当前活跃的任务堆栈,然后就出栈操作,PSP 指向当前任务的堆栈,因为压栈已经是硬件自动完成了,所以没有必要进行软件上的压栈。
参考文献

《Cortex-M3 权威指南》




微信公众号:depthkernel
关注可了解更多嵌入式的教程。问题或建议,请公众号留言;
如果你觉得对你有帮助,欢迎关注

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表