上一课:
【小黑嵌入式系统第十课】μC/OS-III概况——实时操作系统的特点、基本概念(内核&任务&中断)、与硬件的关系&实现
文章目录
- 一、任务设计
- 1.1 任务概述
- 1.2 任务的类型
- 1.2.1 单次执行类任务(运行至完成型)
- 1.2.2.周期执行类任务(无限循环型)
- 1.2.3 事件触发执行类任务(一种无限循环型)
- 1.3 任务的划分
- 1.3.1 任务划分的目标
- 1.3.2 任务的优先级
- 二、任务管理
- 2.1 任务创建
- 2.2 任务的基本状态
- 2.3 系统内部任务
- 2.3.1 空闲任务 `OS_IdleTask()` (`os_core.c`)
- 2.3.2 时钟节拍任务 `OS_TickTask() `(os_tick.c)
- 2.2.3 定时器任务 `OS_TmrTask()` (os_tmr.c)
- 三、任务调度
- 3.1 任务调度器
- 3.2 调度点
- 四、系统函数概述
- 4.1 基本原则
- 4.1.1 配对性原则
- 4.1.2 中断服务程序调用函数的限制
- 4.1.3 任务必须调用某个系统函数
- 4.2 系统函数的分类
一、任务设计
1.1 任务概述
在基于实时操作系统的应用程序设计中,通常需要把要完成的工作分成多个任务(也称线程)来实现,每个任务只负责其中的一部分相对独立的工作,它可以认为在独享CPU。
在只有一个CPU时,任何时刻都只能有一个任务得到执行。操作系统通过任务调度将CPU执行时间在不同任务之间快速切换,以达到多任务“同时”运行的效果。
μC/OS-III
允许应用程序有任意多个任务(仅受存储器容量限制),任务优先级数量可由用户配置,不同的任务允许拥有相同的优先级。
对于不同优先级的任务,采用抢占式(可剥夺式)任务调度方式;对于相同优先级的任务,采用时间片轮转调度方式。
在基于实时操作系统的应用程序设计中,任务设计是整个应用程序的基础,其它软件设计工作都是围绕任务设计来展开。
1.2 任务的类型
按照执行方式分类:
1.2.1 单次执行类任务(运行至完成型)
1.2.2.周期执行类任务(无限循环型)
1.2.3 事件触发执行类任务(一种无限循环型)
无限循环型的任务中,必须调用等待某个事件或延时的系统API函数(而非自己编写的不受操作系统管理的等待函数,如CyDelay()
),否则将会导致其它优先级更低的任务无法得到执行。(原因见“任务调度”部分)
当任务在等待一个事件或延时时,它不会占用CPU时间,此期间CPU可被系统分配给其它任务执行。
1.3 任务的划分
1.3.1 任务划分的目标
在对一个具体的嵌入式应用系统进行任务划分时,可以有不同的任务划分方案。为了选择最佳划分方案,就必须知道任务划分的目标。
-
首要目标是满足“实时性”指标:即使在最坏的情况下,系统中所有对实时性有要求的功能都能够正常实现;
-
任务数目合理:对于同一个应用系统,合理的合并一些任务,使任务数目适当少一些还是比较有利;但任务数目少并不一定能保证设计是多么优秀或多么有效率。
-
简化软件系统:一个任务要实现其功能,除了需要操作系统的调度功能支持外,还需要操作系统的其它服务功能支持,合理划分任务,可以减少对操作系统的服务要求,简化软件系统;
-
降低资源需求:合理划分任务,减少或简化任务之间的同步和通信需求,就可以减少相应数据结构的内存规模,从而降低对系统资源的需求。
1.3.2 任务的优先级
任务的优先级安排原则如下:
-
中断关联性:与中断服务程序(ISR)有关联的任务应该安排尽可能高的优先级,以便及时处理异步事件,提高系统的实时性。
如果优先级安排得比较低,CPU有可能被优先级比较高的任务长期占用,以致于在第二次中断发生时连第一次中断还没有处理,产生信号丢失现象。 -
关键性:任务越关键安排的优先级越高,以保障其执行机会;
-
频繁性:对于周期性任务,执行越频繁,则周期越短,允许耽误的时间也越短,故应该安排的优先级也越高,以保障及时得到执行;
-
快捷性:在前面各项条件相近时,越快捷(耗时短)的任务安排的优先级越高,以使其它就绪任务的延时缩短;
-
传递性:信息传递的上游任务的优先级高于下游任务的优先级。如信号采集任务的优先级高于数据处理任务的优先级。
二、任务管理
2.1 任务创建
为了使μC/OS-III知道一个任务的存在,必须先创建该任务,通过调用系统API函数
OSTaskCreate()
来创建一个任务。
- 任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中建立。
- 在开始多任务调度之前,用户必须至少创建一个用户任务。
- 任务不能在中断服务程序(ISR)中建立。
- μC/OS-III通过任务控制块(TCB)对任务进行管理,创建任务实际上就是给任务代码分配一个任务控制块。
- 任务控制块是一个基于链表的数据结构体,任务控制块主要用于记录任务的堆栈栈顶指针、指向下一个任务控制块的指针、任务等待的延迟时间、任务的当前状态标志与任务的优先级别等一些与任务管理有关的属性。
- 当任务的CPU使用权被剥夺时,μC/OS-III用任务控制块来保存该任务的状态,从而保证任务重新获得CPU使用权时能从断点处恢复继续执行。
任务的相关资源(图中未含任务控制块):
- 任务栈是一个后进先出(LIFO)的线性表。
每个任务均需有一个栈(任务栈),用于存储局部变量、传递的函数参数、返回地址及CPU寄存器的值。 - 每个任务均需有一个优先级,取值范围
0~OS_CFG_PRIO_MAX-1
,用户不能使用最高优先级0和最低优先级 。 - 任务的具体实现对应于任务函数,任务函数的参数
p_arg
值由任务创建函数OSTaskCreate()
传递而来。
任务函数由系统择机调用,而不能由用户主动调用。
2.2 任务的基本状态
任务的5种基本状态及转换关系:
简化地说,任务的状态有5种:休眠态、就绪态、运行态、等待态、中断服务态。
任务被创建后,将由不受操作系统管理的休眠态转换为就绪态,由任务调度器决定何时使用CPU运行(运行态)。
任务状态的转换由执行了某些特定的OS API函数(或中断进入退出)引起。
2.3 系统内部任务
μC/OS-III共有5个系统内部任务:
- 空闲任务
OS_IdleTask()
- 时钟节拍任务
OS_TickTask()
- 统计任务
OS_StatTask()
- 定时器任务
OS_TmrTask()
- 中断服务管理任务
OS_IntQTask()
2.3.1 空闲任务 OS_IdleTask()
(os_core.c
)
当所有其它任务都未就绪时,由于CPU仍需执行指令不能停止运行,此时将运行空闲任务。
它是系统创建的第一个任务,必须创建。空闲任务的优先级为最低优先级OS_CFG_PRIO_MAX-1
,其它任务不能使用该最低优先级。
2.3.2 时钟节拍任务 OS_TickTask()
(os_tick.c)
任务中的延时、等待某事件时的超时,这些都需要依赖一个周期性的时钟源来计时,称为时钟节拍或系统节拍。经历一个周期称为一个时钟节拍。
时钟节拍任务必须创建,其优先级由OS_CFG_TICK_TASK_PRIO
(os_cfg_app.h)设定,通常设为只比最重要的用户任务的优先级略低一点。时钟节拍任务负责判定其它任务中的所有延时、超时的结束。
- 需配备一个硬件定时器(时钟节拍定时器),工作频率由
OS_CFG_TICK_RATE_HZ
设定在10~1000(Hz)之间。 - 时钟节拍任务收到时钟节拍定时器
ISR
周期发送的信号量时,才开始它的处理工作。否则处于等待态。
2.2.3 定时器任务 OS_TmrTask()
(os_tmr.c)
用于向用户提供较粗的定时服务。该任务可选,由OS_CFG_TMR_EN
(os_cfg.h)使能。
定时器任务是一个周期运行的任务,它和时钟节拍任务使用相同的硬件定时器。通过软件方式的分频,定时器任务可实现(比时钟节拍定时器)定时精度低的软件定时器(数量仅受存储器容量限制)。
定时器任务提供的(软件)定时器为递减计数器,计数值减为0时,会引发一个操作,该操作由操作系统调用一个用户定义的回调函数(运行在定时器任务环境中)来实现。
定时器任务的优先级一般设置为中等优先级,由宏OS_CFG_TMR_TASK_PRIO
(os_cfg_app.h)来设定。
时钟节拍ISR和定时器任务的关系:
与时钟节拍任务共用时钟节拍硬件定时器。
定时器任务每收到 N个 时钟节拍定时器ISR周期发送的信号量时,才开始它的处理工作。相当于对时钟节拍定时器进行软件分频。
由定时器任务管理的所有定时器都拥有同样的时间分辨率,即1/OS_CFG_TMR_TASK_RATE_HZ秒,其常用推荐值为0.1秒。
三、任务调度
3.1 任务调度器
任务调度器(简称调度器)负责确定CPU下一个要执行的任务。
μC/OS-III支持两种任务调度算法:抢占式(可剥夺式)调度、时间片轮转调度。
- 抢占式(可剥夺式)调度:CPU执行进入就绪态的优先级最高的任务(若当前正运行的任务优先级最高,仍执行它)。
当一个事件的发生使得一个更高优先级的任务就绪时,调度器会“立即”将CPU的控制权剥夺,转交给该更高优先级的任务使用,看起来像是高优先级任务“抢占”了CPU。
- 时间片轮转调度:有多个就绪任务(以及当前正运行的任务)处于同一优先级时,这些任务轮流运行一段指定的时间(又称时间片),一个时间片包含若干个时钟节拍。
默认各任务有相等的时间片,也可用户指定各任务的时间片长度。
抢占式调度中,任务级的任务调度由OS_Sched()
函数完成,而中断级的任务调度由ISR结束时的OSIntExt()
函数完成。
时间片轮转任务调度由OS_SchedRoundRobin()
函数完成。
3.2 调度点
μC/OS-III 任务调度不可能随时都在进行,当程序调用某些系统服务函数时,调度器才会自动启动,这些时间点称为调度点。
由于调度点很多,几乎可以认为“随时”都在进行任务调度。
四、系统函数概述
4.1 基本原则
4.1.1 配对性原则
对于μC/OS-III来说,大多数API是设计成成对出现的,而且一部分必须配对使用。部分API如延时,不需要配对使用。配对的函数见下表。
4.1.2 中断服务程序调用函数的限制
中断服务程序不能调用可能会导致任务调度的函数,它们主要是一些等待事件的函数,这些函数见下表。
注意:未列入表中的函数OSTaskCreate()
、OSTaskDel()
、OSTaskResume()
、OSTaskSuspend()
、OSTimeDly()
、OSTimeDlyHMSM()
、OSTimeResume()
都属于在中断服务程序中禁止调用的函数。
一些函数虽然没有明确地规定不能被中断服务程序调用,但因为中断服务程序的特性,一般不会使用。
- 1.创建事件和删除事件的函数。
- 2.与任务相关的函数
OSTaskChangePrio()
、OSTaskTimeQuantaSet()
、OSTaskStkChk()
。至于函数OSSchedLock()
和OSSchedUnlock()
,在中断服务程序中使用没有任何意义。
4.1.3 任务必须调用某个系统函数
因为μC/OS-III是完全基于优先级的操作系统,所以在一定的条件下必须出让CPU占有权以便比自己优先级更低的任务能够运行,这是通过调用部分系统函数来实现的,这些函数见下表。一般的任务必须调用表中至少一个函数,只有一种情况例外,即单次执行的任务,因为任务删除后肯定出让CPU,所以可以不调用表中的函数。
4.2 系统函数的分类
系统管理函数是一些与μC/OS-III内核或功能相关的一些函数,详见下表:
μC/OS-III的初始化函数有2个:OSInit()
和OSStart()
,它们不能在任何任务和中断服务程序中使用,仅在main()
函数中按照一定的规范被调用,其中OSInit()
函数初始化μC/OS-III内部变量,OSStart()
函数启动多任务环境。
μC/OS-III具有简单的动态内存管理能力。μC/OS-III的动态内存管理函数见下表:
任务管理函数是操作与任务相关功能的函数,详见下表:
μC/OS-III把信号量等都称为事件,管理它们的就是事件管理函数。μC/OS-III具有的事件有普通信号量、互斥信号量、事件标志组和消息队列,这些都是μC/OS-III用于同步与通讯的工具。
一般的操作系统都提供时间管理的函数,最基本的就是延时函数,μC/OS-III也不例外,μC/OS-III所具有的时间管理函数见下表: