FreeRTOS上下文切换问题--pendSV和Systick优先级为什么要设定为最低

前言

在学习FreeRTOS时,到了任务调度学习的时候,都会碰到将pendSV异常和Systick异常的优先级设定为最低的语句,如下:

	/* Make PendSV and SysTick the lowest priority interrupts. */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

这两句就是将控制pendSV和Systick异常的内核SCB外设,关于控制这两个异常的优先级寄存器的对应位设置为15的优先级,也就是最低的优先级。

问题在于,只是看代码的话不知道为什么将他们两个设置为最低优先级,笔者看了野火和正点原子的FreeRTOS的开发手册,只是照着源代码说这两个要设置为最低,并且解释了这两个优先级是通过哪个寄存器来设置的优先级。真正要理解为什么要这样设置,可能还是要对照着《CM3权威指南》中的第七章-异常-7.6 SVC和PendSV来理解。

SysTick异常

CM3处理器的异常向量表中的前16个内容包含了所有的内核级别的异常,笔者关于异常和中断的文章中解释过,不能简单的把异常和中断统称为中断,不利于对CM3的异常系统的理解。

SysTick处于异常中最末位,优先级是可编程的,这点也意味着它的优先级可以人为的设置,也正因为这点,在设计RTOS的时候利用它很好的解决了实时性的问题。

在一个没有外部中断的系统中,我们利用SysTick异常周期性出现的特点,对一个具有两个任务的系统进行任务的来回切换。通俗的说,就是有个两个任务,任务A和任务B。在SysTick发生的周期下,每发生一次SysTick异常,就从A切换到B,或者从B切换到A。此时SysTick处理函数(SysTick_Handler)中要做的事就是AB任务的切换。

注意:任务的切换也要理解一些概念。如,在任何中断和异常发生的时候,处理器处于Handler模式,在中断或者异常结束以后可切换回线程(Thread)模式。在中断活跃,或者说中断处理函数没有返回的时候,是不能从Handler模式切换回线程模式的,如果这样操作,会引发用法fault异常,下面有内容会引用到这点。)。

SysTick优先级缺省值下的任务切换

如上所述的利用SysTick进行任务切换的流程如下图:

这张图的意思就是:

当发生了SysTick异常,OS就进行任务切换,这个OS进行任务切换的动作是在SysTick处理函数中进行的。

(任务的切换,需要了解的东西比较多,处理器的工作级别;MSP和PSP;现场保护和恢复现场等等,本文暂不展开)

很显然,实际的系统不会这么简单。比如,任务A在执行而还没执行完的某一时刻来了一个中断并且这个中断执行还没返回的时候,SysTick异常产生了(用CM3权威指南的话说就是在产生 SysTick 异常时正在响应一个中断),这个时候会有什么问题?

首先,我们知道SysTick异常属于异常,异常的优先级在默认的情况下都是高于外部中断的,也就是说在刚才描述的那一时刻,中断将会被SysTick异常抢占,也就是说中断处理还没处理完,停了。停了去干什么了呢?去做SysTick异常的处理函数了,SysTick异常处理的是上下文的切换工作,就是从A任务的执行换成B任务的执行。注意,在SysTick异常处理的时候,处理器是出于Handler模式的,当上下文切换完成从处理函数退出的时候,就来到了任务B了,任务B的执行是线程模式状态的。Handler模式--》线程模式,记不记得上面说过一句话:

中断处理函数没有返回的时候,是不能从Handler模式切换回线程模式的,如果这样操作,会引发用法fault异常

正好,这个时候就是在中断处理还没处理完的时刻。SysTick异常处理函数试图在Handler模式下往任务B所工作的线程模式切换。这么搞,不就是引发用法fault的情景了吗。结合权威指南的图来看:

所以,这种情形下的任务切换系统是不行的,得改进。

SysTick优先级低于外部中断优先级下的任务切换

其实思路很简单,既然上面的问题是SysTick异常优先级高于IRQ的优先级,导致了用法fault,那我就解决根源问题--用法fault的消除。

逻辑也很简单,就是把SysTick异常优先级设置为最低,低于任意的IRQ优先级。

1.任务A执行,来了一个中断,中断执行过程中还没返回,SysTick异常产生;

2.中断优先级比SysTick异常优先级高,中断继续执行,直到返回;

3.中断执行完毕,响应SysTick异常,执行上下文切换。

4.来到任务B。

过程如下:

这个形式解决了用法fault的问题,但是这只是中断在SysTick异常前的情况,那么SysTick异常在中断前来的情况呢,是不是会因为IRQ的优先级高而直接抢占SysTick异常呢?

实际上,在进行任务切换的时候,是需要关闭中断的,关闭了中断来进行任务的切换,那么在任务切换完之前的的这段时间来了IRQ怎么办?这段时间就相当于把中断请求给挂起了,导致了中断的响应延迟,也就还是IRQ无法抢占SysTick异常,就无法正常的处理中断。所以这种直接将SysTick异常优先级设置为低的方式解决了用法fault问题,却带来了无法及时响应中断的问题。

那我们从解决及时响应中断的角度出发呢?在任务切换这个过程中,花费时间的是任务切换,如果在响应SysTick异常的时候,把任务切换的事情拖到中断处理完以后去做,是不是用法fault和中断延迟响应的问题都解决了?那么有那样的办法吗?有,pendSV。

PendSV异常

pendSV异常在异常向量表中紧挨SysTick异常,在它楼上。它的特点就是能将异常挂起,延迟执行。什么叫延迟执行呢?这个功能有什么用?对比一下其他的异常和中断就知道了。其他所有的异常和中断在发生的时候,第一,立刻响应;第二,立即快速的执行。尤其是中断,不仅要立即执行,而且在中断处理函数中,还得不做耽误时间的处理,就是要快。毕竟它们是打断了别人正在干的事插进来干的事,当然不能耽误时间了。

但是处理器设计者,偏偏要设计一个异类,其他的异常都是快响应快执行,那要是有个万一呢,有的事情响应了以后就是需要晚点执行怎么办?这个pendSV就有用了,就是在响应了以后,拖一段时间再去执行。

设定SysTick与PendSV低于中断优先级

 通过设定SysTick优先级低于IRQ的优先级,解决了用法fault的错误,但是引入了当SysTick异常处理时,由于关闭中断,而无法响应中断,导致中断响应迟缓的问题。这种基础上,引入pendSV,将pendSV的优先级也设置为最低。在SysTick异常处理中只做时钟计数处理,处理完了马上发起一个pendSV的异常,然后退出SysTick异常处理。

此时pendSV是最低优先级的,首先它抢占不了SysTick异常,因此SysTick异常会正常退出,当然最主要的还是在SysTick异常处理中屏蔽了中断,进入了临界区;其次发生了pendSV异常,但是它不用马上执行,被自己的特性原因挂起了。也就是还是啥事也没干。

由于SysTick异常处理中会关闭优先级5以下的中断,因此中断在SysTick异常处理时仍然无法打断它的执行,但是由于设定了pendSV异常,使得SysTick异常处理时间已经很短很短了,在打开所有中断后,IRQ即可执行,并不会被打断。这样的处理似乎并没有很完美,让IRQ的请求有那么一丝的等待,但是这已经是很快的了,而且只要中断一执行,就没有能打断的,起码保证了中断执行的连续性。

如前所述,我们用pendSV异常就是来做上下文切换的,终于排除万难到了pendSV异常处理函数,那就把任务切换要做的事拿来在这个处理函数里面做就行了。

当然啦,如上说的,IRQ抢占了SysTick异常的处理,那就是挂起了SysTick异常啊,那SysTick异常的响应不就慢了,没办法及时处理了吗?问题是,没有说过SysTick异常必须得及时处理啊,或者说中断的及时处理比系统的异常处理对用户来说更为紧急,做RTOS的初衷就是为了实时的处理中断,而不是实时的处理异常,因为相对于异常的控制,中断的控制对用户是更开放的(笔者理解)。

另外,在一些OS设计中,也有在SysTick异常发生时,判断是否有中断在进行的操作,如果有中断就不执行上下文切换,直接退出SysTick异常处理,到下一次的SysTick异常发生再来任务切换。这种办法不好的点在于,如果说每次SysTick异常发生的时候总有个中断刚好在处理,那任务切换岂不是永远切不了了,这种情况就是权威手册里面说的中断源的频率与SysTick异常频率相同的情况。

当然在我们说的这个系统下,就是SysTick异常和pendSV异常都是最低优先级,可以被IRQ抢占的情况下,IRQ源的频率可能也是和SysTick异常时一样的,但是我们有个什么好处呢,就是你是频率一样,但是我的任务切换并没有说放到下次的SysTick异常中处理啊,我只是等你这次的中断执行完以后,再去那个挂起的pendSV异常中去处理了,只是因为中断执行的原因没有那么的速度。但是该做的事在一个SysTick异常发生的时候做了,没有留下后患。

结语

其实在写文章中,笔者发现,无论是设置SysTick异常优先级最低,还是让中断优先级比SysTick异常低,都会有一个问题,就是谁的优先级低,谁的执行就会被挂起从而导致延迟,这么搞的话,要得到完美的操作系统是不可能的,两弊相衡取其轻,我们需要的是中断的执行要迅速,从而做到实时性,没有说任务切换需要实时性吧。因此把SysTick异常的优先级定的最低,是满足设计RTOS的初衷的。另外在提一点,哪怕是我们把SysTick异常的优先级设定到IRQ以上,让在发生中断的时候SysTick异常去抢占IRQ,也能正常的执行上下文的切换,这时候IRQ的延迟也不高,但是会卡顿,因为SysTick异常是周期性的,它总在抢占IRQ的执行,中断的卡顿当然是无法被用户接受的,因此,用FreeRTOS抛弃了任务切换的速度换取了中断执行的连续,这是好的。

注:本文参考了RTOS系列文章(2):PendSV功能,为什么需要PendSV-CSDN博客并引用了文中的一些图片

另外也参考了《CM3权威指南》关于异常的讲解

  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值