SylixOS cortex-m支持
对于cortex-m
平台,根据任务切换的手段不同,SylixOS有两个不同版本,一个版本是利用svc
进行任务切换等操作,另一个版本是利用pendsv
进行任务切换,下面会对两个版本的实现方式进行阐述。(注:下文会提高svc
模式或是pendsv
模式指的是利用svc
进行任务切换或pendsv
进行任务切换,并不是处理器的工作模式)
cortex-m简介
在介绍具体实现前,先简单介绍一下cortex-m
平台关于任务切换、中断响应、中断嵌套的工作原理。主动保存上下文这都好办,但是当中断来临时如何保存上下文,不同架构处理器实现方式就有很大差别了,这里会对cortex-m
系列和cortex-a
系列分别讲解。
在正式讲解前,先想一个问题,就是对于中断打断的任务上下文,当中断服务程序执行完,我们要想切换回被打断的任务,一定要保证中断前的所有寄存器得到恢复。那有一个问题,就是既然中断前所有寄存器都要保存,当然也包括pc
寄存器,但是我要执行服务程序啊,这样的话pc
必须指向对应服务程序入口才可以,如果给pc
改了,那不就破坏了被打断任务寄存器上下文吗?带着这个问题,来看看cortex-m和cortex-a
分别如何实现的。
cortex-m中断现场保存
cortex-m
在发生中断时会自动保存部分硬件寄存器,这里注意下,cortex-m
异常向量表包括中断和异常,在讨论cortex-m
时,这里面提到的中断包括异常。保存内容如下:
当有中断产生时,硬件会自动压入上述寄存器,对于栈是递减的,r0
位于最低地址。这里可以看到,不光保存了上面说的pc
,还有其它寄存器,这样在我硬件自动保存寄存器后,再将pc
指向异常向量表的服务函数,我也能从栈中给被中断任务上下文恢复出来。之所有保存这几个寄存器是有原因的,根据Arm
的c语言调用规则,在函数调用时,被调用者会自动保证r4-r11
的值不变,如果被调用者用到r4-r11
寄存器,会对相应的寄存器进行压栈出栈操作。这样的话,如果我们在中断服务程序中进行c语言函数调用,中断前的r4-r11
肯定不会改变,而可能发生改变的值已经存入栈中了,这种实现还是很机智的。
有了中断现场保护,接下来看看如果恢复现场。结合上面,既然r4-r11
都没变,那我就想办法告诉处理器,帮我给栈中的寄存器回复吧,一旦恢复了,pc
就会指向被中断的指令(当发生异常时)或是被中断的指令的下一条指令(常规中断),就完成了上下文的回复。cortex-m
本身有两个堆栈指针,处理器怎么知道你的上下文在哪个栈中呢?这里又是cortex-m
的特殊实现了,既然我保护上下文的时候自动保存了8个寄存器,这几个寄存器我就能随便用,所以这里又给lr
做了特殊定义,如下图:
其中的bit[2]
就说明了,中断发生前我用的是哪个栈,处理器会根据对应的栈恢复上下文。bit[3]
可以用来判断是否处于中断嵌套的。
cortex-a中断现场保存
既然cortex-m
通过硬件自动压栈的方式提前保存好了pc
,那cortex-a
系列怎么搞?这里涉及到cortex-a
系列的工作模式和对应寄存器了,这方面资料很多,我不详细阐述了,大家只需要知道,对于几种异常模式irq、fiq、und
等,会有各自的r13(sp)、r14(lr)及spsr
,也就是说这些寄存器是有多份的,一提到多份,那岂不是就可以既保存被打断任务的寄存器,又可以将pc
指向特定的服务程序了吗!这里以irq
为例,讲述cortex-a
系列详细流程,以下均属于硬件自动完成:
- 假设处理器工作在用户模式,当有外部中断产生时,会将当前
cpsr
寄存器的内容保存到irq
模式的cpsr(即spsr)
中,同时设置cpsr
,禁止irq
中断,并进入到irq
模式 - 将发生中断前
pc - 4
的值保存在lr_irq(irq模式下的lr)
中,因为后面我们要设置pc
,所以这里必须先保存下来。这里注意一下,保存下来的pc - 4
针对不同中断或异常需要不同处理,有的能直接用,比如中断,因为中断执行完本身就要下一条指令,由于流水线,pc
本身指向下两条,pc - 4
正好就是下一条指令。而对于异常的话,要返回发生异常的指令,那就需要对保存的值再减4(未定义指令)或减8(数据访问终止,取数据操作在执行的下一级流水中)。 - 设置
pc
,指向异常向量表中的中断处理
以上操作均由硬件完成,且可以认为是原子的。这里需要注意一点就是,cortex-a
系列在切换到irq
模式的同时,会设置cpsr
来屏蔽所有中断,也就是说,一旦响应了外部中断,处理器硬件会自动关掉所有中断,这是可以理解的,虽然lr和sp
寄存器有备份,但是对于irq
模式只存在一组,如果你再来个中断,我之前保存在lr_irq
中的pc
就会被新的寄存器上下文冲掉了,这样就完蛋了。对于cortex-a
系列支持中断抢占也是在上下文维护好后,在执行用户的中断服务程序前软件又给中断打开了,所谓的中断抢占是发生在用户的中断服务程序中。对于cortex-m
系列可不是这样的,在硬件压入寄存器后就会执行服务程序,并且不会自动关中断。抛开性能不谈,cortex-m
更能保证高优先级的中断及时得到响应。SylixOS ArmV7m 支持这篇文章就是笔者之前在利用svc
进行任务切换时遇到的问题,当时产生问题的一部分原因就是忽略了这个处理器特性。
前面说了cortex-m
系列在执行中断服务程序时没有关闭中断,并且规定了如果当前中断处于中断嵌套是不允许从当前中断直接返回到thread
模式,必须返回到被打断的中断,否则会报异常如下图:
如果利用pendsv
进行任务切换,由于pendsv
优先级最低那么自然就等到所有中断服务程序都执行完毕才回到thread
模式,这样不会产生问题。而利用svc
切换时,需要判断lr
的值来确定此时是否中断嵌套,详情参考SylixOS ArmV7m 支持。
pendsv与svc
讲了这么多处理器相关的,下面来看看SylixOS是如何实现这两种方式的。这里有读者可能会问,我就做个任务切换,就正常保存上下文、恢复上下文就行了呗,为啥非得借助svc
或者pendsv
呢。对于cortex-a
系列处理器,这样做确实可以的。但是前面讲了中断返回时的那个lr
是一个特殊值,如果一个任务被中断打断了,恢复上下文时应该使用bx lr
,此时lr
是特殊值。当你主动进行任务切换时,如果直接进行上下文的保存与恢复(不使用硬件自动弹栈功能),那么会造成不同任务上下文处理方式不同。这里之所以在任务切换时引入pendsv
或者svc
是为了引发处理器中断,比如任务A
要切换到任务B
,切换前执行svc
或者发送pendsv
,然后在svc
服务程序或者pendsv
服务程序保存任务A
的上下文,这样即使当主动切换任务时,也是利用中断来切换,保证了所有任务的处理方式是一致的。
svc实现
利用svc
进行任务切换时,保证了执行svc
后可以立刻切换任务,这也是与pendsv
的最主要不同。利用svc
切换时,行为上与cortex-a
系列保持一致,仅仅是实现上的差别,这里不再介绍具体的实现了,实现过程中遇到的问题也都记录在SylixOS ArmV7m 支持中了。
pendsv实现
pendsv
的方式是当我想要进行任务切换时,会触发pendsv
中断,当中断打开且没有任务其它中断运行时(pendsv
优先级最低),才会执行真正的任务切换。但是这种情况会和以往的实现存在差异,针对这种情况,需要修改SylixOS内核。首先来看API_InterExit
函数,这个函数在pendsv
服务函数结尾调用,然后依次调用__kernelSchedInt ----> _ScheduleInt
先看下这两个函数的实现:
在__kernelSchedInt
函数中,首先是对内核加锁,然后调用函数_ScheduleInt
,在第426行
,_SchedSwp
函数在非cortex-m
平台或是使用svc
进行任务切换时的cortex-m
平台调用,因为其它平台或是svc切换
可以理解为是立刻响应的,在_SchedSwp
函数中获取到需要调度的任务,然后利用archIntCtxLoad
函数执行恢复选中任务的上下文,进而执行任务。这里有两点需要注意:
- 对于
svc
模式或是其它平台,执行archIntCtxLoad
函数后,就立马执行恢复的线程去了,不会执行下面的函数,而且永远不会执行。因为SylixOS对于被中断打断的任务,保存的是中断发生前一时刻的现场,而不是在_ScheduleInt
函数中发生切换时的现场。对于pendsv
模式就不一样了,archIntCtxLoad
函数不会进行真时的任务切换,而是软件写入pendsv pend
寄存器触发pendsv
中断,然后返回继续执行。 - 之前提到过
__kernelSchedInt
函数中对内核加锁了,那么对于svc
模式和pendsv
模式是如何解锁的呢?对于svc
模式,_SchedSwp
函数会调用LW_SPIN_KERN_UNLOCK_SCHED(ptcbCur)
解锁内核,此时仍然是关闭中断的,当archIntCtxLoad
恢复任务上下文时就会开启中断了。对于pendsv
模式的话,不在调用_SchedSwp
函数,而是在__kernelSchedInt
函数解锁的,然后会在pendsv
服务程序中完成上下文切换。
接下来分析另一个重要的调度函数,_Schedule
函数内容如下:
_Schedule
函数一般会被__kernelExit
系列函数调用,也是就说此时拥有内核锁。先看下第366行
代码,对于svc
模式,archTaskCtxSwitch
函数会执行选择最需要执行的任务运行,并解锁内核(不开中断),然后完成任务切换,根据切换任务的上下文去打开中断,此时由于发生了任务切换,处理器会运行另一个任务的上下文,直到本任务本再次调度,才继续运行。而对于pendsv
模式,archTaskCtxSwitch
实现很简单,就是置位pendsv
中断,由于此时是关中断的,不会立马响应pendsv
中断,直到在__kernelExit
系列函数中解锁内核并开启中断。第368行
代码是因为pendsv和svc
两种实现对应的archTaskCtxSwitch
函数功能的不同,为了内核上锁解锁配对而出现的。
还有一点需要特别注意,SylixOS在使用svc
进行上下文切换时,支持信号操作,而pendsv
不支持。原因就在_Schedule
函数中,第380行
利用了调度器的返回值,在实现信号时需要这个返回值。上面说了,对于svc
的工作方式下,执行archTaskCtxSwitch
函数后,处理器会执行新的任务,直到当前任务再次切换回来,而这个ptcbCur->TCB_iSchedRet
也是我们需要的。而对于pendsv
的工作方式,执行archTaskCtxSwitch
函数后,由于此时关中断,处理器没有切换任务,而是继续运行,此时都没有发生调度,那ptcbCur->TCB_iSchedRet
调度器的返回值就没有意义了,而信号又依赖于这个值,所以对于pendsv
的工作方式,SylixOS
不支持信号。
以上就是SylixOS对于cortex-m
系列的支持过程及分析,也讲述了设计原因及设计中遇到的一些问题,也希望对遇到此类问题的战友提供帮助。