cortex-M3 的SVC、PendSV异常,与操作系统(ucos实时系统)

29 篇文章 0 订阅

SVC异常是?
PendSV异常是?
ucos 任务切换时机?
ucos 如何满足实时性(实现)?
ucos中,systick的优先级?
SVC和PendSV

SVC(系统服务调用,亦简称系统调用)和PendSV(可悬起系统调用),它们多用于在操作系统之上的软件开发中。
SVC:

SVC 用于产生系统函数的调用请求。
例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。
因此,
当用户程序想要控制特定的硬件时,它就会产生一个SVC 异常,
然后操作系统提供的SVC 异常服务例程得到执行,
它再调用相关的操作系统函数,
后者完成用户程序请求的服务。
这种“提出要求——得到满足”的方式,很好、很强大、很方便、很灵活、很能可持续发展。
首先,它使用户程序从控制硬件的繁文缛节中解脱出来,而是由操作系统 负责控制具体的硬件。
第二,操作系统的代码可以经过充分的测试,从而能使系统更加健壮和可靠。
第三,它使用户程序无需在特权级下执行,用户程序无需承担因误操作而瘫痪整个系统的风险。
第四,通过SVC 的机制,还让用户程序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能。开发应用程序唯一需要知道的就是操作系统提供的应用编程接口(API),并且了解各个请求代号和参数表,然后就可以使用SVC 来提出要求了(事实上,为使用方便,操作系统往往会提供一层封皮,以使系统调用的形式看起来和普通的函数调用一致。各封皮函数会正确使用SVC指令来执行系统调用——译者注)。
其实,严格地讲,操作硬件的工作是由设备驱动程序完成的,只是对应用程序来说,它们也是操作系统的一部分。如图7.14 所示
这里写图片描述
SVC 异常通过执行”SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。SVC异常服务例程稍后会提取出此代号,从而解释本次调用的具体要求,再调用相应的服务函数。例如,

SVC 0x3 ; 调用3 号系统服务

    1

在SVC 服务例程执行后,上次执行的SVC 指令地址可以根据自动入栈的返回地址计算出。找到了SVC 指令后,就可以读取该SVC 指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。如果用户程序使用的是PSP,服务例程还需要先执行

MRS Rn,PSP

    1

指令来获取应用程序的堆栈指针。通过分析LR 的值,可以获知在SVC 指令执行时,正在使用哪个堆栈。
由CM3 的中断优先级模型可知,你不能在SVC 服务例程中嵌套使用SVC 指令(事实上这样做也没意义),因为同优先级的异常不能抢占自身。这种作法会产生一个用法fault。同理,在NMI 服务例程中也不得使用SVC,否则将触发硬fault。
PendSV:

另一个相关的异常是PendSV(可悬起的系统调用),它和SVC 协同使用。
一方面,SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬fault——译者注),应用程序执行SVC 时都是希望所需的请求立即得到响应。
另一方面,PendSV 则不同,它是可以像普通的中断一样被抢占挂起的(不像SVC 那样会上访)。
操作系统 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。

PendSV是什么?
根据 权威指南。PendSV是为系统设备而设的“可悬挂请求”(pendable request)。

    上下文切换 不能在中断中进行,会导致中断延期。为了解决这个问题,使用 PendSV。PendSV可以挂起,也就是等到别的 ISR结束后缓期执行。
    为了实现缓期执行PendSV,PendSV一定要被设置为最低优先级的异常。

挂起PendSV 的方法是:软件实现OSIntCtxSw()函数,向NVIC 的PendSV 悬起寄存器中写1。

NVIC_INT_CTRL   EQU     0xE000ED04   ; Interrupt control state register.
NVIC_PENDSVSET  EQU     0x10000000   ; Value to trigger PendSV exception.
OSIntCtxSw
    LDR     R0, =NVIC_INT_CTRL       ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

    1
    2
    3
    4
    5
    6
    7

挂起后,如果优先级不够高,则将缓期等待执行。
PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。
操作系统,上下文切换 实例:

场景假设:一个系统(按时间片轮转调度的系统)中有两个就绪的任务(A任务、B任务),
上下文切换被触发的场合可以是:

    执行一个系统调用
    系统滴答定时器(SYSTICK)中断,(轮转调度中需要)

A、B两个就绪任务,通过SysTick 异常启动上下文切换。如图7.15 所示。
这里写图片描述
上图是两个任务轮转调度的示意图。
但若在产生SysTick 异常时正在响应一个中断,则SysTick 异常会抢占其ISR。
在这种情况下,操作系统 不可以执行上下文切换,否则将使中断请求被延迟,
而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。
因此,在CM3 中也是严禁没商量——如果操作系统 在某中断活跃时尝试切入线程模式,将触犯用法fault 异常。
这里写图片描述
为解决此问题,早期的操作系统 大多会在SysTick 异常中 检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。
然而,这种方法的弊端在于,
它可能把任务切换动作拖延很久(因为如果抢占了IRQ,则本次SysTick 在执行后不得作上下文切换,只能等待下一次SysTick 异常),尤其是当某中断源的频率和SysTick 异常的频率比较接近时,会发生“共振”。
现在好了,PendSV 来完美解决这个问题了(产生SysTick 异常时正在响应一个中断,SysTick 异常会抢占其ISR。此时,操作系统 不可以执行上下文切换,否则将使中断请求被延迟):
把PendSV 编程为最低优先级的异常,PendSV 异常会自动延迟上下文切换的请求,直到其它的ISR 都完成了处理后才放行。
如果操作系统 检测到某IRQ 正在活动并且被SysTick 抢占,它将悬起一个PendSV 异常,以便缓期执行上下文切换。如图7.17 所示
这里写图片描述
流水账记录如下:
1. 任务 A 呼叫SVC 来请求任务切换(例如,等待某些工作完成)
2. OS 接收到请求,做好上下文切换的准备,并且pend 一个PendSV 异常。
3. 当 CPU 退出SVC 后,它立即进入PendSV,从而执行上下文切换。
4. 当 PendSV 执行完毕后,将返回到任务B,同时进入线程模式。
5. 发生了一个中断,并且中断服务程序开始执行
6. 在 ISR 执行过程中,发生SysTick 异常,并且抢占了该ISR。
7. OS 执行必要的操作,然后pend 起PendSV 异常以作好上下文切换的准备。
8. 当 SysTick 退出后,回到先前被抢占的ISR 中,ISR 继续执行
9. ISR 执行完毕并退出后,PendSV 服务例程开始执行,并且在里面执行上下文切换
10. 当 PendSV 执行完毕后,回到任务A,同时系统再次进入线程模式。

其实,ucos中的实现,于此有些差异,但从结果上看,是一致的(如果systick抢占了其他ISRs,不会在其中执行上下文切换。会等到全部的ISRs执行完毕后(期间一定是无任务调度的),才执行pendsv异常,完成上下文的切换。==差别在于生成pendsv异常的时机。)
ucos 关于 PendSV 异常的应用(上下文切换时机、怎样满足实时性):

在systick异常中,执行必要的任务维护更新工作,在退出时,考虑生成pensv异常。
当且仅当无中断被抢占时,生成pensv异常,并于pendsv异常中,完成上下文切换工作;
当每一个中断/异常处理函数中,均在退出时考虑生成pensv异常,则最终无中断可执行时,pensv异常一定会生成,并且期间无任务调度。

这可能与上边描述的不一致,但结果上来看,是一致的。
下边以ucos系统的源码,进行大概的讲解:
中断/异常处理通用模板:

    OS_CPU_SR  cpu_sr;
    OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR          */
    OSIntNesting++;
    OS_EXIT_CRITICAL();
    用户处理代码;
    void  OSIntExit (void);//OSIntNesting--;以及可能的调度

    1
    2
    3
    4
    5
    6

systick异常实现(ucos心脏):

void  OS_CPU_SysTickHandler (void)
{
    OS_CPU_SR  cpu_sr;
    OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR */
    OSIntNesting++;
    OS_EXIT_CRITICAL();
    OSTimeTick();  /* Call uC/OS-II's OSTimeTick()       */

    OSIntExit();  /* Tell uC/OS-II that we are leaving the ISR */
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

void OSTimeTick (void)

void  OSTimeTick (void)
{
更新系统时间,OSTime++;
遍历OSTCBList 任务控制块链表(已经建立的任务),
    如果任务控制块OSTCBDly非零,则减一;
    如果等于零,更新OSTCBStat(任务状态)、OSTCBStatPend(任务挂起状态)成员;
    如果OSTCBStat等于OS_STAT_RDY(就绪状态),则将任务放入就绪表中。
}

    1
    2
    3
    4
    5
    6
    7
    8

OSIntExit

/*$PAGE*/
/*
*********************************************************************************************************
*                                               EXIT ISR
*
* Description: This function is used to notify uC/OS-II that you have completed serviving an ISR.  When
*              the last nested ISR has completed, uC/OS-II will call the scheduler to determine whether
*              a new, high-priority task, is ready to run.
*
* Arguments  : none
*
* Returns    : none
*
* Notes      : 1) You MUST invoke OSIntEnter() and OSIntExit() in pair.  In other words, for every call
*                 to OSIntEnter() at the beginning of the ISR you MUST have a call to OSIntExit() at the
*                 end of the ISR.
*              2) Rescheduling is prevented when the scheduler is locked (see OS_SchedLock())
*********************************************************************************************************
*/

void  OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register */
    OS_CPU_SR  cpu_sr = 0;
#endif

 

    if (OSRunning == OS_TRUE) {
        OS_ENTER_CRITICAL();
        if (OSIntNesting > 0) {                            /* Prevent OSIntNesting from wrapping       */
            OSIntNesting--;
        }
        if (OSIntNesting == 0) {                           /* Reschedule only if all ISRs complete ... */
            if (OSLockNesting == 0) {                      /* ... and not locked.                      */
                OS_SchedNew();
                OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];
                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy */

#if OS_TASK_PROFILE_EN > 0
                    OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task  */
#endif
                    OSCtxSwCtr++;                          /* Keep track of the number of ctx switches */
                    OSIntCtxSw();                          /* Perform interrupt level ctx switch       */
                }
            }
        }
        OS_EXIT_CRITICAL();
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51

我们在进入systick异常时,有执行OSIntNesting++;准备出来时,自然需要OSIntNesting–;
如果OSIntNesting不等于零,退出systick异常。
只有OSIntNesting等于零(无其他异常/中断发生)并且OSLockNesting等于零(无任务调度锁),才执行OS_SchedNew()查就任务绪表中最高优先级并返回,比较返回的优先级是否为当前运行任务的优先级,
仅不相等时,执行OSIntCtxSw()函数,生成pendsv异常。OSIntCtxSw()函数的实现见上边篇幅。
pendsv异常OS_CPU_PendSVHandler,实现上下文的切换。这里不做解释。
ucos中,systick的优先级?

PENDSV和SYSTICK属于系统异常;
定时器中断,串口中断这些属于外部中断。
PENDSV和SYSTICK的中断优先级可以编程,
一般要把PENDSV的优先级设置成最低(没什么好说的)。
但SYSTICK异常的优先级:

    一般无需设置(高于外部中断的优先级),毕竟这是系统的时钟源(ucos心脏);
    当然,也可根据项目需要(有些外部中断,项目上要求务必实时),将SYSTICK优先级设置与合适的位置。
    确实存在的普遍现象是,很多项目对于实时没有很高的要求,干脆将PENDSV和SYSTICK的优先级都设置成OxFF。

都是最低优先级,此时因为PENDSV在中断向量表中排在SYSTICK前面,所以如果PENDSV,SYSTICK同时产生中断,PENDSV优先中断。

本文有些地方表述的确实有些怪异,见谅。
欢迎各位指正。
---------------------
作者:点滴写录
来源:CSDN
原文:https://blog.csdn.net/guozhongwei1/article/details/49544671
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值