一、什么是任务?
在裸机系统中,系统的主体就是 main 函数里面顺序执行的无限循环,这个无限循环里
面 CPU 按照顺序完成各种事情。在多任务系统中,我们根据功能的不同,把整个系统分割
成一个个独立的且无法返回的函数,这个函数我们称为任务。具体代码如下:
void task_entry (void *parg)
{
/* 任务主体,无限循环且不能返回 */
for (;;)
{
/* 任务主体代码 */
}
}
二、操作模式以及特权级别
1、操作模式:处理者模式(handler mode,以后不再把 handler 中译——译注)和线程模式(thread mode)。引入两个模式的本意,是用于区别异常服务例程的代码——包括中断服务例程的代码,普通应用程序的代码。
2、特权级别:特权级和用户级,主要是提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。
合法的操作流程如下图:(如果看不懂可以参考CM3权威指南)
三、Cortex-M3 的双堆栈机制
1、堆栈指针:
主堆栈指针(MSP),或写作SP_main。这是缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
进程堆栈指针(PSP),或写作SP_process。用于常规的应用程序代码(不处于异常服用例程中时)。
2、当
CONTROL[1]=0
时,只使用
MSP
,此时用户程序和异常
handler
共享同一个堆栈。
3、当
CONTROL[1]=1
时,线程模式将不再使用
MSP
,而改用
PSP
(
handler
模式永远使用
MSP)。
进入异常时的自动压栈使用的是进程堆栈,进入异常 handler 后才自动改为 MSP,退出异常时切换回PSP,并且从进程堆栈上弹出数据。
四、异常返回
当异常服务例程执行完毕后,需要很正式地做一个“异常返回”动作序列,从而恢复先前的系 统状态,才能使被中断的程序得以继续执行。从形式上看,有3种途径可以触发异常返回序列,如 表9.2所示。而不管使用哪一种,都需要用到先前储到LR的EXC_RETURN。
在进入异常服务程序后,将自动更新LR的值为特殊的EXC_RETURN。这是一个高 28位全为1的值,只有[3:0]的值有特殊含义,如表9.3所示。当异常服务例程把这个值送往PC时,就 会启动处理器的中断返回序列。因为LR的值是由CM3自动设置的,所以只要没有特殊需求,就不要 改动它。
如果主程序在线程模式下运行,并且在使用MSP时被中断,则在服务例程中LR=0xFFFF_FFF9(主 程序被打断前的LR已被自动入栈)。 如果主程序在线程模式下运行,并且在使用PSP时被中断,则在服务例程中LR=0xFFFF_FFFD(主程序被打断前的LR已被自动入栈)。
如果主程序在Handler模式下运行,则在服务例程中LR=0xFFFF_FFF1(主程序被打断前的LR已被 自动入栈)。这时的所谓“主程序”,其实更可能是被抢占的服务例程。事实上,在嵌套时,更深 层ISR所看到的LR总是0xFFFF_FFF1,如图9.5所示。
五、什么是临界段
临界段用一句话概括就是一段在执行的时候不能被中断的代码段。
在 FreeRTOS 里面,这个临界段最常出现的就是对全局变量的操作。那么什么情况下临界段会被打断?一个是系统调度,还有一个就是外部中断。在FreeRTOS,系统调度,最终也是产生 PendSV中断,在 PendSV Handler 里面实现任务的切换,所以还是可以归结为中断。既然这样,FreeRTOS对临界段的保护最终还是回到对中断的开和关的控制。
为了快速地开关中断, Cortex-M
内核专门设置了一条
CPS
指令,有
4
种用法。
1 CPSID I ;PRIMASK=1 ;关中断
2 CPSIE I ;PRIMASK=0 ;开中断
3 CPSID F ;FAULTMASK=1 ;关异常
4 CPSIE F ;FAULTMASK=0 ;开异常
PRIMASK
和
FAULTMAST
是
Cortex-M内核 里面三个中断屏蔽寄存 器中的两个,还有一个是 BASEPRI。
在 FreeRTOS中,对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大
于等于
BASEPRI
的值的中断会被屏蔽,小于
BASEPRI
的值的中断则不会被屏蔽,不受FreeRTOS 管理。
六、空闲任务和阻塞延时
空闲任务:
空闲任务是系统在【启动调度器】的时候创建的优先级最低的任务,空闲任 务主体主要是做一些系统内存的清理工作。当任务需要延时,进入阻塞状态,那 CPU
又去干什么事情了?如果没有其它任务可以 运行,RTOS
都会为
CPU
创建一个空闲任务,这个时候
CPU
就运行空闲任务。
阻塞延时:即任务需要延时的时候,任务会放弃CPU
的使用权,
CPU
可以去干其它的事情,当任务延时时间到,重新获取CPU
使用权,任务继续运行,这样就充分地利用了
CPU
的资源。
七、任务优先级
就绪列表
pxReadyTasksLists[ configMAX_PRIORITIES ]
是一个数组,数组里面存的是就绪任务的 TCB
(准确来说是
TCB
里面的
xStateListItem
节点),数组的下标对应任务的 优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0
的链表。任务在创建的时候,会根据任务的优先级将任务插入到就绪列表不同的位置。相同优先级的任务插入到就绪列表里面的同一条链表中,即时间片的支持。
八、延时列表
在
FreeRTOS
中,有一个任务延时列表(实际上有两个,为了方便讲解原理,我们假装合并为一个,其实两个的作用是一样的),当任务需要延时的时候,则先将任务挂起, 即先将任务从就绪列表删除,然后插入到任务延时列表,同时更新下一个任务的解锁时刻
变量:
xNextTaskUnblockTime 的值。
xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时的值 xTicksToDelay。当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪。
任务延时列表表维护着一条双向链表,每个节点代表了正在延时的任务,节点按照延 时时间大小做升序排列。当每次时基中断(SysTick
中断)来临时,就拿系统时基计数器的 值 xTickCount
与下一个任务的解锁时刻变量
xNextTaskUnblockTime
的值相比较,如果相 等,则表示有任务延时到期,需要将该任务就绪,否则只是单纯地更新系统时基计数器 xTickCount 的值,然后进行任务切换。
九、时间片
FreeRTOS
与隔壁的
RT-Thread
和
μC/OS
一样,都支持时间片的功能。所谓时间片就
是同一个优先级下可以有多个任务,每个任务轮流地享有相同的
CPU
时间,享有
CPU
的
时间我们叫时间片。在
RTOS
中,最小的时间单位为一个
tick
,即
SysTick
的中断周期,
RT-Thread
和
μC/OS
可以指定时间片的大小为多个
tick
,但是
FreeRTOS
不一样,时间片只
能是一个
tick
。与其说
FreeRTOS
支持时间片,倒不如说它的时间片就是正常的任务调度。