目录
RTOS 的核心是任务管理,而任务切换是任务管理的重中之重,系统中任务切换的过程是决定操作系统运行效率和稳定性的因素之一,尤其是对于实时操作系统。
1 PendSV 异常
PendSV(
Pended Service Call
,可挂起服务调用)异常,是一个对
RTOS
非常重要的异常。 正如 PendSV
异常的名称一样,
PendSV
异常是一个可以挂起的异常,这主要体现在
PendSV
异 常能够被挂起而延迟执行,那么 RTOS
就能够利用
PendSV
异常的这一特点,等待
CPU
处理完其他重要的事务后,再处理 PendSV
异常。
利用 PendSV
异常的可挂起特点,
µC/OS-III
将
PendSV
的中断优先级配置为最低的中断优 先级,这么一来,PendSV
异常的中断服务函数就会在其他所有中断处理完成后才被执行。 µC/OS-III 就是将任务切换的过程放到
PendSV
异常的中断服务函数中处理的。
要挂起 PendSV
异常(触发
PendSV
异常)也非常简单,只需将
ICSR
寄存器(中断控制状 态寄存器)中断的 PENDSVSET
为置
1
,即可挂起
PendSV
异常,再挂起
PendSV
异常
PendSV 的中断服务函数并不会立马被执行,但也不会被忽略,而是会等 CPU
处理完所有中断优先级不 小于 PendSV
异常中断优先级的中断后,在再处理
PendSV
异常的中断处理函数。
2 µC/OS-III 何时触发任务切换
µC/OS-III 要触发任务切换无需在应用程序代码中做任务特殊的处理,因为
µC/OS-III
是一 个抢占式的内核,系统会保证当前执行的任务,一定是系统中任务优先级最高的就绪态任务, 因此 µC/OS-III
会在一定的条件下自动触发任务切换,
µC/OS-III
自动触发任务切换的条件,如下表所示:
条件 |
任务向另外一个任务发送信号或消息
|
任务调用延时函数(函数
OSTimeDly()
或函数
OSTimeDlyHMSM()
)
|
任务等待一个还未发生的事件发生
|
任务的挂起状态被终止
|
任务被创建
|
任务被删除
|
内核对象被删除
|
任务修改了自身或其他任务的任务优先级
|
任务调用函数
OSTaskSuspend()
挂起自身
|
任务调用函数
OSTaskResue()
恢复其他被函数
OSTaskSuspend()
挂起的任务
|
中断嵌套结束
|
使用函数
OSSchedUnblock()
解锁任务调度器
|
任务调用函数
OSSchedRoundRobinYield()
弃用剩余的时间片
|
在应用程序中调用函数
OSSched()
|
当上表中的条件触发时,µC/OS-III
就会自动地触发任务切换。当然,要注意的是,触发任务切换并不一定会切换任务,而仅仅是保证系统运行的任务为系统中任务优先级最高的就绪态任务。
3 µC/OS-III 如何触发任务切换
在表
中的条件发生后,
µC/OS-III
就会自动地触发任务切换。在
µC/OS-III
中有两个用于触发任务切换的函数,分别为函数 OSSched()
和函数
OSIntExit()
,这两个函数的不同在于,函数OSSched()
是在任务中使用的,而函数
OSIntExit()
是用于中断中的。
3.1 函数 OSSched()
函数 OSSched()
用于在任务中触发任务切换,一般情况下,该函数会在满足表
中的条件(除中断嵌套结束和在应用程序中调用函数 OSSched()
)时,被
µC/OS-III
自动调用。当然, 应用程序也可以在任务中调用 OSSched()
,手动触发任务切换。
3.2 函数 OSIntExit()
函数
OSIntExit()
用于告诉
µC/OS-III
一个中断的中断服务函数执行完毕,并在需要的时候进行任务切换。
3.3 挂起 PendSV 异常
通过函数
OSSched()
和函数OSIntExit()分别调用了函数
OS_TASK_SW()
和函数
OSIntCtxSw()
挂起
PendSV
异常,以触发任 务切换。
首先看一下函数 OS_TASK_SW()
,此函数实际上是一个定义在文件
os_cpu.h
中的宏定义, 具体的定义如下所示:
#define OS_TASK_SW() OSCtxSw()
可以看到,函数 OS_TASK_SW()
被定义为
OSCtxSw()
,而函数
OSCtxSw()
与函数 OSIntCtxSw()是被定义在文件
os_cpu_a.asm
中用于表示同一段汇编代码的标号,如下所示:
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制状态寄存器的地址
NVIC_PENDSVSET EQU 0x10000000 ; PENDSVSET 位掩码
OSCtxSw
OSIntCtxSw
; 将 ICSR 寄存器中的 PENDSVSET 位置 1
; 挂起 PendSV 异常
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
从上面的代码中可以看出,函数 OSCtxSw()
与函数
OSIntCtxSw()
就用于挂起
PendSV
异常 的,因为 PendSV
异常的中断优先级被设置为最低的中断优先级,因此在所有中断的中断服务函数处理完成后,就会跳转到 PendSV
异常的中断服务函数中,在
PendSV
的中断服务函数中就进行了任务切换的操作。
4 µC/OS-III 如何进行任务切换
通过上一小节的分析,了解了在函数 OSSched()
和函数
OSIntExit()
中会挂起
PendSV
异常,那么 PendSV
异常就能够在所有中断的中断服务函数处理完成后,执行任务切换的操作。 µC/OS-III 提供了
PendSV
异常的中断服务函数,以完成任务切换的操作。
任务切换的核心就是任务栈中数据的入栈与出栈。入栈操作能
够保存当前任务在任务切换前的上下文信息,而出栈操作则是将切换后任务的上下文信息从任 务的任务栈总恢复出来,这样任务才能够正常运行。