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