一、什么是PendSV PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3 权威指南》里有介绍,下面我摘抄了一段。
OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动 作。悬起 PendSV 的方法是:手工往 NVIC的 PendSV悬起寄存器中写 1。悬起后,如果优先级不够 高,则将缓期等待执行。 PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:* V! j! { f! _' {
1、执行一个系统调用5 \0 N4 Z$ J" y1 S! f: N X
2、系统滴答定时器(SYSTICK)中断,(轮转调度中需要) 让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过SysTick异常启动上下文切换。但若在产生 SysTick 异常时正在响应一个中断,则 SysTick异常会抢占其 ISR。在这种情况下,OS是不能执行上下文切换的,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这 种事。因此,在 CM3 中也是严禁没商量——如果 OS 在某中断活跃时尝试切入线程模式,将触犯用法fault异常。 为解决此问题,早期的 OS 大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应 时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切 换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick在执行后不得作上下文切换,只能等待下 一次SysTick异常),尤其是当某中断源的频率和SysTick异常的频率比较接近时,会发生“共振”, 使上下文切换迟迟不能进行。现在好了,PendSV来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到 其它的 ISR都完成了处理后才放行。为实现这个机制,需要把 PendSV编程为最低优先级的异常。如果 OS检测到某 IRQ正在活动并且被 SysTick抢占,它将悬起一个 PendSV异常,以便缓期执行 上下文切换。 使用 PendSV 控制上下文切换个中事件的流水账记录如下: 1. 任务 A呼叫 SVC来请求任务切换(例如,等待某些工作完成) 2. OS接收到请求,做好上下文切换的准备,并且悬起一个 PendSV异常。 3. 当 CPU退出 SVC后,它立即进入 PendSV,从而执行上下文切换。 4. 当 PendSV执行完毕后,将返回到任务 B,同时进入线程模式。 5. 发生了一个中断,并且中断服务程序开始执行 6. 在 ISR执行过程中,发生 SysTick异常,并且抢占了该 ISR。 7. OS执行必要的操作,然后悬起 PendSV异常以作好上下文切换的准备。 8. 当 SysTick退出后,回到先前被抢占的 ISR中,ISR继续执行 9. ISR执行完毕并退出后,PendSV服务例程开始执行,并且在里面执行上下文切换 10. 当 PendSV执行完毕后,回到任务 A,同时系统再次进入线程模式。
我们在uCOS的PendSV的处理代码中可以看到: - OS_CPU_PendSVHandler
* C4 b/ \" ?! _* }" n3 S - CPSID I ; 关中断6 |* h3 Q1 L/ u& C* ~% i
- ;保存上文 1 _4 q6 b3 K8 m/ E
- ;.......................
( ?7 l' U8 H0 F - ;切换下文
) |. ^5 h9 l4 V L* X+ @ - CPSIE I ;开中断
' q" J; m; h% r% l) _ - BX LR ;异常返回
复制代码
( g; W+ k9 x2 h# V它在异常一开始就关闭了中端,结束时开启中断,中间的代码为临界区代码,即不可被中断的操作。PendSV异常是任务切换的堆栈部分的核心,由他来完成上下文切换。PendSV的操作也很简单,主要有设置优先级和触发异常两部分: - NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器
- w& k) l+ Q0 G7 t2 `! l - NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14). 9 \8 E# s, U2 u4 u. R/ V7 H
- NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低). NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值
) V% h$ q& Q4 k8 ]
' E3 {7 P! C9 z! W( |/ Y& B3 a- ; 设置PendSV的异常中断优先级
. A0 ?& } H) p: W- j - ; u& E; w( Y; b. g0 }- R+ s
- LDR R0, =NVIC_SYSPRI14 4 y5 a7 l( _3 {8 a6 h+ [/ H
- LDR R1, =NVIC_PENDSV_PRI
- I, {. ^' k9 k4 ] - STRB R1, [R0] ; 触发PendSV异常
. L. {" o2 v4 o; {1 f - LDR R0, =NVIC_INT_CTRL
8 \ i5 U" p3 h4 @. Z3 T, D - LDR R1, =NVIC_PENDSVSET
& B- q: Y' m; S. I( r - STR R1, [R0]
复制代码 ' f. @5 j+ A1 w
二、堆栈操作 Cortex M4有两个堆栈寄存器,主堆栈指针(MSP)与进程堆栈指针(PSP),而且任一时刻只能使用其中的一个。MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也使用MSP。怎么开启PSP? - MSR PSP, R0 ; Load PSP with new process SP
! M1 l Q. @' ?1 }; m! ? - ORR LR, LR, #0x04 ; Ensure exception return uses process stack
复制代码 8 v4 r. V2 \8 G& C: w6 I/ h
很容易就看出来了,置LR的位2为1,那么异常返回后,线程使用PSP。 写OS首先要将内存分配搞明白,单片机内存本来就很小,所以我们当然要斤斤计较一下。在OS运行之前,我们首先要初始化MSP和PSP, - EXTERN OS_CPU_ExceptStkBase
! V& Q4 |* n: [& `; A2 i5 A - ;PSP清零,作为首次上下文切换的标志
8 {: G1 V: A6 T5 f# ^; w4 f# |8 z - MOVS R0, #0 ; {5 Z) Q Z- s" W
- MSR PSP, R0
. _: W: h& l' D! P3 Q' ~2 L - ;将MSP设为我们为其分配的内存地址* ?) L' G/ c& k" W; h$ j" p
- LDR R0, =OS_CPU_ExceptStkBase
1 S) }. F5 `" e' R) o- I# c. z - LDR R1, [R0]2 H5 D. x( J+ e# v: e7 M
- MSR MSP, R1
复制代码 4 {% Q4 `9 f' l1 i5 y6 i
然后就是PendSV上下文切换中的堆栈操作了,如果不使用FPU,则进入异常自动压栈xPSR,PC,LR,R12,R0-R3,我们还要把R4-R11入栈。如果开启了FPU,自动压栈的寄存器还有S0-S15,还需吧S16-S31压栈。 - MRS R0, PSP* Q }7 X" x/ z& x' x
- SUBS R0, R0, #0x20 ;压入R4-R11
6 y# T' z. G* s- _, S - STM R0, {R4-R11} O* Q! q2 R: o8 H) a& k
3 n M, n- G0 c6 Z' R# ~; m8 ?- LDR R1, =Cur_TCB_Point ;当前任务的指针
/ Q8 O" p! k5 q - LDR R1, [R1]
5 @2 ?7 k& O& {) d1 o6 H2 A - STR R0, [R1] ; 更新任务堆栈指针
复制代码
6 U7 j4 w* l+ ^' @2 F5 `* M' z出栈类似,但要注意顺序 - LDR R1, =TCB_Point ;要切换的任务指针" O7 L% P. g4 E9 {& E# y1 M: o
- LDR R2, [R1]" {; q0 V) _3 j- x
- LDR R0, [R2] ; R0为要切换的任务堆栈地址/ c7 g. c& B# M& t1 ?* S
- 3 r" t; V% T o/ y
- LDM R0, {R4-R11} ; 弹出R4-R11
E5 x, p7 o' X6 s3 y. d4 X& I - ADDS R0, R0, #0x207 H0 B, L! ^" Y. I* a: M6 g% ^
- 3 I& l! l1 o8 ?! z4 g1 z
- MSR PSP, R0 ;更新PSP
复制代码
# S0 i; k: S; c: a三、OS实战 新建os_port.asm文件,内容如下: - NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register.; ^0 r" b; }; F; P, f% F' `
- NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).8 C9 J9 Q j3 b; M% h
- NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest).6 Z: y3 M' A6 M! O% l. Z
- NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception.
! f$ f: H2 g& P% x+ d1 F
* T; o2 U* t& V, n- RSEG CODE:CODE:NOROOT(2)5 m4 P b. C$ I7 ?* @. c: N; l8 ?: R
- THUMB
* R& q! @! H" ^2 P. G
% u+ ^6 K' r5 s/ M" ^6 v- 0 M" v! q5 \: a4 G9 p5 ^1 T
- EXTERN g_OS_CPU_ExceptStkBase
: p' ?1 x% T" X- i- H3 E1 | - 7 W: ?( D' B( Z
- EXTERN g_OS_Tcb_CurP3 s2 e. K- S# x8 i+ c. B
- EXTERN g_OS_Tcb_HighRdyP
* w0 n3 s* J1 {' x9 ~
; K' ] @5 O& B! G% f- PUBLIC OSStart_Asm
: l P% Z; M) \- u* z - PUBLIC PendSV_Handler
. k) t) T/ o. p. c8 S' ]. r" b - PUBLIC OSCtxSw
! ?( u* g# S0 S - & z9 z: Z5 s. i! S% l
- OSCtxSw) r" \6 S, C" V& w( l z8 Q/ v: i
- LDR R0, =NVIC_INT_CTRL1 L4 D! y8 |% Z- I8 H; y
- LDR R1, =NVIC_PENDSVSET
; ], E) V2 a d) v" O - STR R1, [R0]2 ^/ y) D; t% n
- BX LR ; Enable interrupts at processor level2 P* F& E; f' @6 j, x0 F
- , m+ U1 o W! h/ d$ P5 R& I
- OSStart_Asm' m6 |# G4 H$ F2 a3 G4 P/ k8 a
- LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
, {$ E: n2 }0 o$ b, K - LDR R1, =NVIC_PENDSV_PRI- h' Q! Q( x3 P( { g
- STRB R1, [R0]
: r( |- N% A- }2 O
7 o2 v5 s( g8 A( h) [7 B- MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
% k, H2 f0 H; S - MSR PSP, R0
; M* D- i: f% J8 n8 S - ! u( l, ~3 B9 t1 }# ^. l+ G8 Y0 `
- LDR R0, =g_OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase: `, o# E/ ?- f! {9 r6 M; K" Z
- LDR R1, [R0]7 `' `* F. _$ J/ J
- MSR MSP, R1
; n2 W' Y* V3 {- r - - M# R5 A! X; o1 i; ~
- LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
, G1 ?; T1 A9 K+ l9 @ - LDR R1, =NVIC_PENDSVSET1 o+ U# C; m* t& c- k5 `
- STR R1, [R0]
6 c) n g% b5 c" `' X7 D' i
* U+ S V: }, w! C A. t" h3 _$ [- CPSIE I ; Enable interrupts at processor level
4 e7 f9 V0 r B& R# r
2 p4 P- ]9 ? I" k+ h" S* h5 W- OSStartHang
5 e; x8 L. |2 D. R - B OSStartHang ; Should never get here8 a1 T& B: n8 ~0 a' R# I+ ?6 [
-
+ G8 e$ y9 T; p! q) }* Z - . z6 U0 d6 _ w: c; z$ Y
" B2 r6 e6 N5 W. |- PendSV_Handler! z" Y' r/ c- x5 n5 h% s+ q0 k* _
- CPSID I ; Prevent interruption during context switch L2 D1 \# ]7 G: Q
- MRS R0, PSP ; PSP is process stack pointer
$ S1 |7 ~9 H. O; q( H ]6 m - CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
1 G: e% F0 `/ v/ s+ H- [ - _( F4 f% N* I" {6 y
- SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
/ `4 B% i" w' z" v) l' H/ }+ y - STM R0, {R4-R11}
. R1 Z7 P) H- `% {4 B4 c
. w3 N" O9 Z0 X3 z+ j- LDR R1, =g_OS_Tcb_CurP ; OSTCBCur->OSTCBStkPtr = SP;
. d" y( e8 w( {! B/ k - LDR R1, [R1]
5 _+ t0 ^$ f' @/ e' @ - STR R0, [R1] ; R0 is SP of process being switched out
& S) X$ q+ n C* Z
, n8 }+ R) |! b" C- ; At this point, entire context of process has been saved
1 z! x* K4 j$ G ~1 N: V - OS_CPU_PendSVHandler_nosave
, R6 w+ ?+ M9 ?6 W - LDR R0, =g_OS_Tcb_CurP ; OSTCBCur = OSTCBHighRdy;
* F1 A5 Y; f$ ? - LDR R1, =g_OS_Tcb_HighRdyP
) i+ R# A) Q+ z% j" D - LDR R2, [R1]
" C5 D/ O& q1 P& C- P9 w: f - STR R2, [R0]
8 t$ m) [# w* {
. K1 ]0 Q0 M) A1 ]% S) B& }) A; r3 T; l/ s- LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
% g6 Y2 A9 h% z; _' Z3 X% z -
7 f. s! t( r; i - LDM R0, {R4-R11} ; Restore r4-11 from new process stack( b# T ]; U6 Y
- ADDS R0, R0, #0x20 @2 o; D& K6 `3 T
-
9 k* U U7 B4 |- C/ S2 d - MSR PSP, R0 ; Load PSP with new process SP( F! i Y0 _* E M+ {# g! }
- ORR LR, LR, #0x04 ; Ensure exception return uses process stack P$ r9 s" T. ?6 O I, I! T _8 A
-
. k8 v. i4 H3 p4 s2 ?+ Z' Y - CPSIE I" j- |, B( ?% y( U' i: S5 j! T9 N& d
- BX LR ; Exception return will restore remaining context
7 q7 o8 v- E9 K( v - ' o- E! H$ F( d9 I
- END
复制代码 X7 W; o! @ W0 n, q
main.c内容如下: - #include "stdio.h"5 j0 t; ^! Z' `+ H: W( \8 {: X
- #define OS_EXCEPT_STK_SIZE 1024$ F ?: J) Y+ u5 F
- #define TASK_1_STK_SIZE 1024
" x% W- N& [% k. j; {' v! e - #define TASK_2_STK_SIZE 1024$ Q' d! a* n# C/ |" ~
- * H; H1 r% V! P7 q6 r* @5 N. m2 V/ d
- typedef unsigned int OS_STK;& x. g% N- ^/ u. N- E
- typedef void (*OS_TASK)(void);; }. X8 g0 I% I0 p2 S( J
8 |. G- P& M7 [( n& \: }- F- typedef struct OS_TCB M8 E! _5 i. O/ B5 n8 a
- {
* P. U) P" |' p( R1 S6 M5 a - OS_STK *StkAddr;
5 T3 ]6 e# {9 L3 f" U( [9 [ - }OS_TCB,*OS_TCBP;6 k0 H+ ~5 b* N1 N+ C
. z( L. @+ v7 e
1 S3 }9 N8 C# B0 r& E6 k( b" S+ w- OS_TCBP g_OS_Tcb_CurP; ( i+ E' b2 M$ i$ e8 p
- OS_TCBP g_OS_Tcb_HighRdyP; a$ ]( [, `9 k1 j0 J" T
0 A& d, q/ Z+ t. K- A) U/ d* ~9 U- static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];$ {3 w/ a; [- z
- OS_STK *g_OS_CPU_ExceptStkBase;
/ P- t; ]; j: B; V
, E, y. f6 `" S- G* r- static OS_TCB TCB_1;
[3 O0 p; z. M8 H z - static OS_TCB TCB_2;
; j& E9 Z( g6 c4 s+ h6 X! Y - static OS_STK TASK_1_STK[TASK_1_STK_SIZE];0 I9 P$ z* C9 H8 M5 `* {6 n
- static OS_STK TASK_2_STK[TASK_2_STK_SIZE];
* h8 e1 U4 u8 ?* ~+ L1 n1 }$ r) b - ( j" ^, p6 C8 d' E9 C6 `. X$ c4 ~( v
- extern void OSStart_Asm(void);+ L& t8 R' v, U' y1 M+ I! I' i
- extern void OSCtxSw(void);
- k: g+ D8 Q1 B' { - " v: N+ x7 j' O( }; Q8 T
- void Task_Switch()
2 d( Z3 o! ^) X; W( Z - {1 H% f# ]: `/ `" G; Q4 r. ]0 }
- if(g_OS_Tcb_CurP == &TCB_1)
: ]9 X2 H4 v. A8 J0 E - g_OS_Tcb_HighRdyP=&TCB_2;
7 b% c1 v7 h1 C8 B) L$ ^1 d - else! H8 G% r4 n7 {+ c5 ~
- g_OS_Tcb_HighRdyP=&TCB_1; ^6 ?1 L' H* X; }' S, Y0 d* Z/ n
- - k+ c8 [, m- v$ y/ P! |
- OSCtxSw();5 M* @1 C! J" }
- }- ^) N5 k8 C! h' s h) n# u$ J# j- U
- - H- v. @! I/ p; c4 R% c" r
- / G$ d/ u9 m5 a4 f" s5 d
- void task_1()
4 [0 ?. U* i0 C. ` - {
' c V2 M! g! a1 E" f - printf("Task 1 Running!!!\n");3 v& f& l6 H* a6 l
- Task_Switch();
( X' w! n5 b. g- N - printf("Task 1 Running!!!\n");$ P- O" V: n% V
- Task_Switch();
& r! n# E, y4 c) U5 Z - }
) O% D. c( V0 Z7 @. d! w; | - 3 r$ k4 Q+ F$ \- Y& i
- void task_2()
9 g" z c; e0 [5 e9 Z9 ` - {
q0 J7 f o l0 g+ { - 5 Y3 ?9 G% J% P
- printf("Task 2 Running!!!\n");
' X) M! c5 n8 t* P - Task_Switch();6 w5 A/ X6 T: \/ N
- printf("Task 2 Running!!!\n");
1 \. y" g0 t( h' P$ K - Task_Switch();" |' g3 ?7 N5 ^5 v
- }. S. A( y1 | m2 s
+ ~$ F" r7 i5 n \- void Task_End(void)
[6 }1 c$ G3 ~5 N6 i, C - {: A2 c+ I; ~; k
- printf("Task End\n");
' @6 R# {2 K8 |6 x1 a - while(1)
0 p; U) l* g" A - {}+ y" q5 w1 o9 Z
- }, h7 W* `/ Q# y' u
- ' [1 ?/ D7 l" b+ O+ y
- void Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk)
* b0 d3 g4 @+ ~1 x - {
! M: w6 h* @ ?1 {5 E+ ^2 N - OS_STK *p_stk;1 X3 {5 @% c1 m5 p1 ]1 r
- p_stk = stk;
8 s/ K7 f& j" W, K# d - p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);9 u+ B2 f' I2 \. z) Y4 H0 g
- / g. m- g m6 i* g5 H) s
- *(--p_stk) = (OS_STK)0x01000000uL; //xPSR
" ~: p3 g( L! j- h - *(--p_stk) = (OS_STK)task; // Entry Point
/ w& n! J% z8 \- U) ], B6 f - *(--p_stk) = (OS_STK)Task_End; // R14 (LR)
$ a4 y& R# w" [ - *(--p_stk) = (OS_STK)0x12121212uL; // R12" v9 C% M5 ~$ ]( `3 Z6 E
- *(--p_stk) = (OS_STK)0x03030303uL; // R3
9 h, W6 q4 q" l- B) W - *(--p_stk) = (OS_STK)0x02020202uL; // R2' K' r, r, t7 }) A
- *(--p_stk) = (OS_STK)0x01010101uL; // R1
+ u5 j$ g! Y" O! y! h6 p4 N - *(--p_stk) = (OS_STK)0x00000000u; // R0( S, J5 H% ?8 _) m" W& D& {$ ^
- / Z Y7 u4 }) X3 F% J
- *(--p_stk) = (OS_STK)0x11111111uL; // R11' \: g3 s4 q) D' l2 R$ N
- *(--p_stk) = (OS_STK)0x10101010uL; // R10
X% ]' M+ K& Z% \ - *(--p_stk) = (OS_STK)0x09090909uL; // R9( U+ r; O- s# c/ b& i5 n, f
- *(--p_stk) = (OS_STK)0x08080808uL; // R8) w4 @; ?/ n9 I( a2 X6 G7 W
- *(--p_stk) = (OS_STK)0x07070707uL; // R7" y" A" ]" j7 [% ?
- *(--p_stk) = (OS_STK)0x06060606uL; // R6
0 s+ h3 K/ J( ?- V& v( y/ h - *(--p_stk) = (OS_STK)0x05050505uL; // R5
% j! }3 q; Z7 \/ b - *(--p_stk) = (OS_STK)0x04040404uL; // R44 z7 a! Y( x) O: H6 ]1 `
-
$ P# R8 R* ?8 T - tcb->StkAddr=p_stk;
7 N6 m/ T* s# W- O8 l - }
. q R2 u" ]( J5 c. q0 J
7 \- ?8 }. B+ N/ X& F( L
! v7 m5 T; i" S6 A3 R; D1 l- int main()
, N) @7 P3 `% q* b8 M" Q* M - {8 P- B3 y+ w/ x
-
; _5 I- F( x; F" s b3 i$ x Y - g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;
$ k' H; `9 [2 h* _1 a - 1 x9 v) G2 c; K$ Z
- Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1]);0 a8 p: B" \; Y
- Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_1_STK_SIZE-1]);
) R2 [ L) E0 ~ - % [. {$ e. {! I( [
- g_OS_Tcb_HighRdyP=&TCB_1;
! l) R% i5 E: _1 w. p2 y -
: N/ V# ~- c" U* `. N* T @ - OSStart_Asm();8 P) U2 [4 E' x
- 6 h0 j' h5 Q/ n) D
- return 0;
- w9 M, d9 n% p/ D6 ^' J5 A - }
复制代码 * D. c! U, I2 a# |" |. X0 P
编译下载并调试:
. j8 k" ~+ ~7 j" r6 c5 z9 u X
# `, T+ V4 L' s7 D# T
在此处设置断点 此时寄存器的值,可以看到R4-R11正是我们给的值,单步运行几次,可以看到进入了我们的任务task_1或task_2,任务里打印信息,然后调用Task_Switch进行切换,OSCtxSw触发PendSV异常。 / K! N; i" d/ ?6 y$ M. U& E7 d7 k* l
* M2 g( w( s. Z2 ^4 V, g/ |9 ~
IO输出如下:
5 C# |9 J! o2 c3 {0 [+ U! Y
7 j l" T) D% |- i+ K
至此我们成功实现了使用PenSV进行两个任务的互相切换。之后,我们使用使用SysTick实现比较完整的多任务切换。
' z7 \) s! X/ P4 X. m |