上一课:
【小黑嵌入式系统第十八课】结课总结(二)——软件部分(系统架构&调试&测试&运行&系统软件设计)
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站:人工智能
文章目录
- 一、嵌入式操作系统
- 二、嵌入式操作系统的优缺点
- 优点
- 缺点
- 三、嵌入式操作系统
- 四、实时操作系统RTOS
- 基本概念
- 五、嵌入式操作系统μC/OS-III
- 六、μC/OS-III程序设计基础
- 任务设计
- 任务创建
- 5个系统内部任务
- 任务的状态
- 任务调度
- 系统函数
- 配对性原则
- 中断服务程序调用函数的限制
- 任务必须调用某个系统函数
- 时间管理
- 控制任务的执行周期
- 控制任务的运行节奏
- 状态查询
- 临界区管理
- 资源同步 vs 行为同步(任务同步)
- 事件管理
- 关于资源同步的讨论
- 信号量
- 事件标志组
- 消息队列
- 动态内存管理
一、嵌入式操作系统
嵌入式操作系统EOS (Embedded OS):指运行在嵌入式系统中,对整个嵌入式系统所操作、控制的各种资源进行协调、调度和控制的系统软件。
-
EOS负责嵌入系统的全部软、硬件资源的分配、调度,控制、协调并发活动。
-
EOS是嵌入式应用软件的基础和开发平台,是嵌入式系统极为重要的组成部分,通常包括系统内核、与硬件相关的底层驱动软件、设备驱动接口、通信协议、文件系统、图形界面等。
二、嵌入式操作系统的优缺点
优点
- 使程序的设计和扩展变得容易,大大提高了嵌入式系统开发的效率,改变了以往嵌入式软件设计只能针对具体的应用从头做起的方式。
- 充分发挥32位CPU多任务的潜力,实现多任务设计,能够充分利用硬件资源和实现资源共享。
- 实时性和健壮性能够得到更好的保证。
缺点
- 嵌入式操作系统占用ROM/RAM等额外开销,5~10%的CPU额外负荷。
- 有一定的学习门槛。
三、嵌入式操作系统
- 按应用场合分类
- 非实时操作系统:面向消费电子产品,如个人数字助理(PDA)、移动电话、机顶盒(STB)等;
- 实时操作系统(RTOS):实时系统是一种能够在指定或者确定时间内完成系统功能,并且对外部和内部事件在同步或者异步时间内能作出及时响应的系统。面向控制、通信、医疗等领域。
- 实时操作系统RTOS的特点
- 总的来说实时操作系统是事件驱动的,能对来自外界的作用和信号在限定的时间范围内作出响应。它强调的是实时性、可靠性和灵活性,与实时应用软件相结合成为有机的整体起着核心作用,由它来管理和协调各项工作,为应用软件提供良好的运行软件环境及开发环境。IEEE 的实时UNIX分委会认为实时操作系统应具备以下的几点:
- 异步的事件响应 ,切换时间和中断延迟时间确定,优先级中断和调度,抢占式调度,内存锁定,连续文件,同步
- 一般实时操作系统 vs 嵌入式实时操作系统
- 一般实时操作系统应用于实时处理系统的上位机和实时查询系统等实时性较弱的实时系统,并且提供了开发、调试、运用一致的环境。
- 嵌入式实时操作系统应用于实时性要求高的实时控制系统,而且应用程序的开发过程是通过交叉开发来完成的,即开发环境与运行环境是不一致的。嵌入式实时操作系统具有规模小(一般在几K~几十KB 内)、可固化使用实时性强(在毫秒或微秒数量级上)等特点 。
- 总的来说实时操作系统是事件驱动的,能对来自外界的作用和信号在限定的时间范围内作出响应。它强调的是实时性、可靠性和灵活性,与实时应用软件相结合成为有机的整体起着核心作用,由它来管理和协调各项工作,为应用软件提供良好的运行软件环境及开发环境。IEEE 的实时UNIX分委会认为实时操作系统应具备以下的几点:
四、实时操作系统RTOS
基本概念
-
内核
多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通信。内核提供的最基本服务是任务切换。使用实时内核可以大大简化应用系统的设计,因为实时内核允许将应用分成若干个任务,由实时内核来管理它们。内核需要消耗一定的系统资源,比如2%~5%的CPU运行时间、RAM和ROM等。内核还提供一些必不可少的系统服务,如信号量、消息队列、延时等。 -
任务
一个任务,也称作一个线程,是一段简单的程序,该程序可以认为CPU完全属于该程序自己。每个任务被赋予一定的优先级,有它自己的一套CPU寄存器和自己的栈空间。实时应用程序的设计过程,包括如何把问题分割成多个任务,每个任务都是整个应用的某一部分。
任务优先级
任务的优先级是表示任务被调度的优先程度。每个任务都具有优先级。任务越重要,赋予的优先级应越高,越容易被调度进入运行态。 -
任务切换
当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,即CPU寄存器中的全部内容。这些内容保存在任务的当前状态保存区,即任务自已的栈之中。入栈工作完成以后,就把下一个将要运行的任务的当前状态从其栈中重新装入CPU的寄存器,并开始下一个任务的运行。这个过程就称为任务切换。 -
调度
调度是内核的主要职责之一。调度就是决定该轮到哪个任务运行了。多数实时内核是基于优先级调度法的。每个任务根据其重要程序的不同被赋予一定的优先级。基于优先级的调度法指CPU总是让处在就绪态的优先级最高的任务先运行。 -
非抢占式(合作式)内核
非抢占式内核要求每个任务自我放弃CPU 的所有权。非抢占式调度法也称作合作型多任务,各个任务彼此合作共享一个CPU。异步事件还是由中断服务来处理。中断服务可以使一个高优先级的任务由挂起(Pending,或译为“等待” )状态变为就绪状态。但中断服务以后CPU控制权还是回到原来被中断了的那个任务,直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。 -
抢占式(可剥夺式)内核
当系统响应时间很重要时,要使用抢占式内核。因此绝大多数商业上销售的实时内核都是抢占式内核。最高优先级的任务一旦就绪,总能得到CPU的控制权。当一个运行着的任务使一个比它优先级高的任务进入了就绪状态,当前任务的CPU使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务将被挂起,优先级高的那个任务开始运行。 -
中断
中断是一种硬件机制,用于通知CPU有个异步事件发生了。中断一旦被识别,CPU保存部分(或全部)上下文即部分或全部寄存器的值,跳转到专门的子程序,称为中断服务程序(ISR)。中断服务程序做事件处理,处理完成后,程序回到:- 在前后台系统中,程序回到后台程序;
- 对非抢占式内核而言,程序回到被中断了的任务;
- 对抢占式内核而言,让进入就绪态的优先级最高的任务开始运行。
-
时钟节拍
时钟节拍是特定的周期性中断。这个中断可以看作是系统心脏的脉动。中断之间的时间间隔取决于不同应用,一般在10ms到200ms之间。时钟的节拍式中断使得内核可以将任务延时若干个整数时钟节拍,以及当任务在等待事件发生时,提供等待超时的依据。时钟节拍率越快,系统的额外开销就越大。
五、嵌入式操作系统μC/OS-III
-
μC/OS—Micro Controller OS
应用面覆盖了诸多领域,满足航空电子、工业控制、医疗等市场的针对可靠性/安全性的认证标准。是一个源码公开、可移植、可裁减、可固化、占用资源少、抢占式的实时多任务操作系统内核。其绝大部分源码采用ANSI C编写,可移植性好。高校教学可免费使用。 -
µC/OS-III的特点
-
源代码开放
-
便捷的应用程序编程接口(API)
-
抢占式多任务管理
-
同优先级任务的时间片轮转调度
-
极短的关中断时间
-
确定性
-
可裁剪
-
可移植
-
可固化
-
可动态配置
-
任务数目不受限制
-
内核对象数目不受限制
-
丰富的系统服务
-
互斥型信号量
-
软件定时器
-
同时等待多个内核对象
-
直接向任务发信号、消息
-
任务寄存器
-
出错检验
-
内置性能测试
-
易于优化
-
防止死锁
-
内置的内核察觉式调试
-
六、μC/OS-III程序设计基础
任务设计
- 在基于实时操作系统的应用程序设计中,通常需要把要完成的工作分成多个任务(也称线程)来实现,每个任务只负责其中的一部分相对独立的工作,它可以认为在独享CPU。在只有一个CPU时,任何时刻都只能有一个任务得到执行。操作系统通过任务调度将CPU执行时间在不同任务之间快速切换,以达到多任务“同时”运行的效果。在基于实时操作系统的应用程序设计中,任务设计是整个应用程序的基础,其它软件设计工作都是围绕任务设计来展开。
- μC/OS-III允许应用程序有任意多个任务(仅受存储器容量限制),任务优先级数量可由用户配置,不同的任务允许拥有相同的优先级。对于不同优先级的任务,采用抢占式(可剥夺式)任务调度方式;对于相同优先级的任务,采用时间片轮转调度方式。
- 优先级设计原则
- 中断关联性:与中断服务程序(ISR)有关联的任务应该安排尽可能高的优先级,以便及时处理异步事件,提高系统的实时性。如果优先级安排得比较低,CPU有可能被优先级比较高的任务长期占用,以致于在第二次中断发生时连第一次中断还没有处理,产生信号丢失现象。
- 关键性:任务越关键安排的优先级越高,以保障其执行机会;
- 频繁性:对于周期性任务,执行越频繁,则周期越短,允许耽误的时间也越短,故应该安排的优先级也越高,以保障及时得到执行;
- 快捷性:在前面各项条件相近时,越快捷(耗时短)的任务安排的优先级越高,以使其它就绪任务的延时缩短;
- 传递性:信息传递的上游任务的优先级高于下游任务的优先级。如信号采集任务的优先级高于数据处理任务的优先级。
任务创建
- 为了使μC/OS-III知道一个任务的存在,必须先创建该任务,通过调用系统API函数OSTaskCreate()来创建一个任务。任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中建立。在开始多任务调度之前,用户必须至少创建一个用户任务。任务不能在中断服务程序(ISR)中建立。
- 任务控制块(TCB)
- μC/OS-III通过任务控制块(TCB)对任务进行管理,创建任务实际上就是给任务代码分配一个任务控制块。
- 任务控制块是一个基于链表的数据结构体,任务控制块主要用于记录任务的堆栈栈顶指针、指向下一个任务控制块的指针、任务等待的延迟时间、任务的当前状态标志与任务的优先级别等一些与任务管理有关的属性。
- 当任务的CPU使用权被剥夺时,μC/OS-III用任务控制块来保存该任务的状态,从而保证任务重新获得CPU使用权时能从断点处恢复继续执行。
5个系统内部任务
- 空闲任务 OS_IdleTask():必须创建
- 当所有其它任务都未就绪时,由于CPU仍需执行指令不能停止运行,此时将运行空闲任务。它是系统创建的第一个任务,必须创建。
- 空闲任务的优先级为最低优先级OS_CFG_PRIO_MAX-1,其它任务不能使用该最低优先级。
- 时钟节拍任务 OS_TickTask():必须创建
- 任务中的延时、等待某事件时的超时,这些都需要依赖一个周期性的时钟源来计时,称为时钟节拍或系统节拍。经历一个周期称为一个时钟节拍。
- 时钟节拍任务必须创建,其优先级由OS_CFG_TICK_TASK_PRIO(os_cfg_app.h)设定,通常设为只比最重要的用户任务的优先级略低一点。时钟节拍任务负责判定其它任务中的所有延时、超时的结束。
- 需配备一个硬件定时器(时钟节拍定时器),工作频率由OS_CFG_TICK_RATE_HZ设定在10~1000(Hz)之间。时钟节拍任务收到时钟节拍定时器ISR周期发送的信号量时,才开始它的处理工作。否则处于等待态。
- 统计任务 OS_StatTask():可选
- 这个任务能够统计总的CPU使用率(0到100%),每个任务的CPU使用率( 0到100%),每个任务的堆栈使用量。设置OS_CFG.H中的OS_CFG_STAT_TASK_EN为1使能。
定时器任务 OS_TmrTask() :可选 - 用于向用户提供较粗的定时服务。该任务可选,由OS_CFG_TMR_EN(os_cfg.h)使能。
定时器任务是一个周期运行的任务,它和时钟节拍任务使用相同的硬件定时器。通过软件方式的分频,定时器任务可实现(比时钟节拍定时器)定时精度低的软件定时器(数量仅受存储器容量限制)。 - 定时器任务提供的(软件)定时器为递减计数器,计数值减为0时,会引发一个操作,该操作由操作系统调用一个用户定义的回调函数(运行在定时器任务环境中)来实现。
- 定时器任务的优先级一般设置为中等优先级,由宏OS_CFG_TMR_TASK_PRIO(os_cfg_app.h)来设定。
- 与时钟节拍任务共用时钟节拍硬件定时器。定时器任务每收到 N个 时钟节拍定时器ISR周期发送的信号量时,才开始它的处理工作。相当于对时钟节拍定时器进行软件分频。
- 这个任务能够统计总的CPU使用率(0到100%),每个任务的CPU使用率( 0到100%),每个任务的堆栈使用量。设置OS_CFG.H中的OS_CFG_STAT_TASK_EN为1使能。
- 中断服务管理任务 OS_IntQTask() :可选
- 优先级最高为0,需要把宏OS_CFG_ISR_POST_DEFERRED置1,该任务负责延迟在ISR中调用的系统post服务函数的行为。当ISR(中断服务函数)调用UCOSIII提供的“post”函数时,要发送的目的地和数据会存入一个特别的缓冲队列中,当所有嵌套中断函数都执行完毕后UCOSIII会做任务切换,运行中断服务管理任务,中断服务管理服务任务会把缓存队列中存放的信息发送给相应的任务。好处是可以减少中断关闭的时间。
任务的状态
任务的状态有5种:休眠态、就绪态、运行态、等待态、中断服务态。
- 休眠态(Dormant)
任务已存在于存储器中,但还不受OS管理。 - 就绪态(Ready)
系统为任务分配了任务控制块,并且任务已经在就绪表中登记,这时这个任务就具有了运行的条件,此时任务的状态就是就绪态。等待的事件或延时满足,结束等待态的任务也进入就绪态。 - 运行态(Running)
任务获得CPU的使用权,正在运行。任何时刻只能有一个任务处于运行态。 - 等待态(Pending)
正在运行的任务需要延时一段时间,或者等待某个事件,这个任务就进入了等待态,此时系统就会把CPU使用权转交给别的任务,直到延时或者等待时间结束。 - 中断服务态(Interrupted)
响应中断时,当前正在运行的任务会被挂起,CPU转而去执行中断服务函数,此时任务的任务状态叫做中断服务态。
任务调度
- 任务调度器(简称调度器)负责确定CPU下一个要执行的任务。μC/OS-III支持两种任务调度算法:抢占式(可剥夺式)调度、时间片轮转调度。
- 抢占式(可剥夺式)调度:CPU执行进入就绪态的优先级最高的任务(若当前正运行的任务优先级最高,仍执行它)。当一个事件的发生使得一个更高优先级的任务就绪时,调度器会“立即”将CPU的控制权剥夺,转交给该更高优先级的任务使用,看起来像是高优先级任务“抢占”了CPU。抢占式调度中,任务级的任务调度由OS_Sched()函数完成,而中断级的任务调度由ISR结束时的OSIntExt()函数完成。
- 时间片轮转调度:有多个就绪任务(以及当前正运行的任务)处于同一优先级时,这些任务轮流运行一段指定的时间(又称时间片),一个时间片包含若干个时钟节拍。默认各任务有相等的时间片,也可用户指定各任务的时间片长度。时间片轮转任务调度由OS_SchedRoundRobin()函数完成。
- 调度点
- μC/OS-III 任务调度不可能随时都在进行,当程序调用某些系统服务函数时,调度器才会自动启动,这些时间点称为调度点。由于调度点很多,几乎可以认为“随时”都在进行任务调度。下面是μC/OS-III的任务调度点。
系统函数
配对性原则
- 对于μC/OS-III来说,大多数API是设计成成对出现的,而且一部分必须配对使用。部分API如延时,不需要配对使用。配对的函数见下表。
中断服务程序调用函数的限制
- 中断服务程序不能调用可能会导致任务调度的函数,它们主要是一些等待事件的函数,这些函数见下表。
注意:
- 未列入表中的函数
OSTaskCreate()
、OSTaskDel()
、OSTaskResume()
、OSTaskSuspend()
、OSTimeDly()
、OSTimeDlyHMSM()
、OSTimeResume()
都属于在中断服务程序中禁止调用的函数。 - 一些函数虽然没有明确地规定不能被中断服务程序调用,但因为中断服务程序的特性,一般不会使用。创建事件和删除事件的函数。与任务相关的函数
OSTaskChangePrio()
、OSTaskTimeQuantaSet()
、OSTaskStkChk()
。至于函数OSSchedLock()
和OSSchedUnlock()
,在中断服务程序中使用没有任何意义。
任务必须调用某个系统函数
- 在一定的条件下必须出让CPU占有权以便比自己优先级更低的任务能够运,一般的任务必须调用表中至少一个函数。
时间管理
控制任务的执行周期
- 在任务函数的代码中可以通过插入延时函数来控制任务周期性运行,定时闲置CPU一段时间,供其它任务使用。
控制任务的运行节奏
- 在任务函数的代码中也可以通过插入延时函数来控制任务的运行节奏。
状态查询
- 查询过程是一个无限循环过程,只有当希望的状态出现以后才能退出这个无限循环,这种情况在实时操作系统管理下是不允许的,它将剥夺低优先级任务的运行机会。解决办法是“用定时查询代替连续查询” 。
- μC/OS-III提供了若干个时间管理服务函数,可以满足任务在运行过程中对时间管理的需求。在使用时间管理服务函数时,必须十分清楚一个事实:时间管理服务函数是以系统节拍为处理单位的,实际的时间与希望的时间是有误差的,最坏的情况下误差接近一个系统节拍。因此时间管理服务函数只能用在对时间精度要求不高的场合,或者时间间隔较长的场合。
OSTimeDly()
:以时钟节拍为单位延时,延时等待时会将CPU让出。
OSTimeDlyHMSM()
:以小时(H)、分(M)、秒(S)和毫秒(m)四个参数来定义延时时间。函数在内部把这些参数转换为时钟节拍,再通过单次或多次调用OSTimeDly()
进行延时和任务调度,延时原理和调用延时函数OSTimeDly() 一样。
OSTimeDlyResume()
:µC/OS-III允许用户结束任务正处于的延时期,延时的任务可以不等待延时期满,而是通过其它任务取消其延时来使其处于就绪态。OSTimeGet()
、OSTimeSet()
:获得系统时间(可用于计算两个时间点的时间间隔)、设置系统时间
临界区管理
-
进入然后退出临界段:
OS_CRITICAL_ENTER()
、OS_CRITICAL_EXIT()
- 主要用于保证临界代码不被打断;也可用于 “资源同步”,能够在访问共享资源时保障信息的可靠性和完整性。
-
给调度器上锁解锁:
OSSchedlock()
、OSSchedUnlock()
- 给调度器上锁
OSSchedlock()
函数用于禁止任务调度,直到任务完成后调用给调度器开锁OSSchedUnlock()
函数为止。使用它有几点需要注意。OSSchedlock()
和OSSchedUnlock()
必须成对使用,也可以嵌套使用;OSSchedlock()
只是禁止了任务的调度,而没有禁止中断,此时如果允许中断,中断到来时还是会执行对应的中断服务程序;- 调用
OSSchedLock()
以后,用户的应用程序不得使用任何能将现行任务挂起的系统调用,直到配对的OSSchedUnlock()
调用为止。 - 对于用户来说,极少使用禁止然后允许调度的方法。不过,很多操作系统内部和驱动程序使用它来减少中断响应时间。
- 给调度器上锁
资源同步 vs 行为同步(任务同步)
- 资源同步保证同时占有资源的任务不超过最大允许任务数目
- 行为同步保证任务的先后次序
事件管理
- 四类事件
- 互斥信号量:Mutex
- 信号量:Sem
- 事件标志组:Flag
- 消息队列:Q
- 原则
- 先创建再使用
- 一般来说,在嵌入式系统中,事件是静态使用的,即创建后永远不删除
- 中断服务程序一般不会调用建立和删除事件函数,否则要么没有起到事件的作用,要么程序很复杂
- 中断服务程序不能调用等待事件的函数,否则可能造成程序崩溃。
- 互斥信号量
- 互斥信号量也称为mutex,专用于资源同步。互斥信号量具有一些特性:占用一个空闲优先级,以便解决优先级反转问题。
- 优先级反转问题是指高优先级任务H等待低优先级任务L占用的资源时,执行权可能被优先权低于H高于L的任务M反复抢占导致实际表现为M抢夺H的执行权。能防止优先级反转现象的信号就是互斥信号量。
- 在嵌入式系统中,经常使用互斥信号量访问共享资源来实现资源同步。而用来实现资源同步的互斥信号量在创建时初始化,这是由**OSMutexCreate ()**函数来实现的;
- **OSMutexPost ()发送互斥信号量函数与OSMutexPend ()**等待互斥信号量函数必须成对出现在同一个任务调用的函数中,因此我们需要编写一个公共的库函数,因为有多个任务可能调用这个函数 ;
- 信号量最好在系统初始化的时候创建,不要在系统运行的过程中动态地创建和删除。在确保成功地创建信号量之后,才可对信号量进行接收和发送操作。
关于资源同步的讨论
- 使用临界区管理可以实现资源同步,使用互斥信号量也可以实现资源同步。两者实际应用场合有所不同。
- 临界区管理方面,按照临界区的概念,这部分代码运行时不可分割,不允许被中断打断(在有操作系统的情况下,也不允许被其它任务打断)。1. 使用进入然后退出临界区的方法(
OS_CRITICAL_ENTER()
、OS_CRITICAL_EXIT()
),在执行临界区代码的时候,中断是禁止的(对于μC/OS-III来说,禁止了中断同时也导致任务调度停止)。在“嵌入式系统5.ppt”中提到“2)共享资源互斥性造成的临界代码”,如果把共享资源的操作处理为临界代码,显然可以实现共享资源在不同任务或中断服务程序中的共享使用。但这种方式只适合于操作共享资源时间很短的场合,否则中断禁止时间和任务调度停止时间过长会显著影响其它工作。比如一个简单的全局变量这种共享资源,进行若干操作耗时很短,就可以用临界区管理的方式来共享。如果是一个成员很多的全局结构体变量,或者一个比较大的全局数组,就不是太合适用临界区管理的方式来共享。如果是LCD,LCD操作耗时长,就不该用临界区管理的方式来共享。2.使用禁止然后允许调度的方法,在执行临界区代码的时候,任务调度是停止的,但中断并未禁止。这种方法能只能保证临界区代码不被其它任务打断,严格地说不是完整的临界区保护,如果程序员在设计时能确保所有中断都不会对临界区代码执行结果产生影响,也是可应用的。一般来说,任务调度停止的时间,可以允许比中断禁止时间长一些,但也不能过长,否则影响多任务“并行”运行的总体效果。用这种方式做共享资源管理,共享资源的操作时间可以比前面的例子长一些。但只能实现共享资源在任务之间的共享。 - 用互斥信号量来管理共享资源,则没有这些例子中的限制。当然等待互斥信号量、发送互斥信号量函数,耗时肯定比进入临界区、退出临界区函数长,用作简单全局变量之类的共享资源管理效率会没有那么高。另外一点注意:互斥信号量不能用作任务与中断服务程序之间的共享资源管理,因为中断服务程序中不能等待互斥信号量。
- 临界区管理方面,按照临界区的概念,这部分代码运行时不可分割,不允许被中断打断(在有操作系统的情况下,也不允许被其它任务打断)。1. 使用进入然后退出临界区的方法(
- 小结
- 互斥信号量可以用于任务之间的共享资源管理,对共享资源没有限制(针对只能同时由一个任务使用的共享资源而言)。
- 临界区管理首先是管理临界区代码用的,对共享资源的操作算是一种比较特殊的临界代码。把共享资源的操作视为临界区时,应该耗时很短,否则会造成中断禁止时间或任务调度停止时间过长。对于“嵌入式系统5.ppt”中提到的“1) 依靠软件产生时间严格时序的程序段、3)函数重入造成的临界代码”,如果这些代码耗时较短,可以作为临界区代码。如果耗时较长,就不适合作临界代码,而是考虑其它的方案。对于“嵌入式系统5.ppt”中提到“4) CPU字长造成的临界代码”,可以作为临界区代码。
- 注:以上提及的耗时长短,是参考CPU时钟频率的,大概地说,几百个CPU时钟周期以内的时长可以认为是耗时短。
信号量
- 在实时多任务系统中,信号量(semaphore)被广泛用于:任务间对共享资源的互斥,但更多地是用于任务和中断服务程序之间的同步、任务之间的同步。
- N为信号量值,表示发布信号量的次数累计值。信号量用于任务-任务(或任务-ISR)间同步时,N表示事件已发生了多少次。信号量用于资源共享时,N表示资源还可被多少个任务同时使用。当任务(或ISR)调用OSSemPost()函数发送信号量时,信号量值加1;当信号量值大于0,任务调用OSSemPend()函数接收信号量时,信号量值减1;当信号量值等于0,任务调用OSSemPend()函数接收信号量时,可以设置延时,延时等待(一般设置为阻塞方式,也可以设置为非阻塞),到时仍无信号量,则返回超时错误。
- 互斥信号量 vs 信号量
-
任务间同步
- 常用信号量实现任务间的同步,OSSemPend()和OSSemPost()会出现在不同任务中,但不一定成对出现。
- 用来实现任务间同步的信号量在创建时赋给初始值,一般为0,表示事件还未发生,初始值在OSSemCreate()函数中指定。
- ISR与任务同步
- 用于资源同步
- 可以使用信号量访问共享资源来实现资源同步。在使用时,注意发送信号量函数OSSemPost()与等待信号量函数OSSemPend()必须成对出现在同一个任务调用的函数中(这与互斥信号量使用方式一致)。
- 在使用信号量做资源共享时,只有任务才能使用信号量,而中断服务程序则不可以。而在使用信号量做ISR与任务间同步时,ISR可以给任务发送信号量,但不能做其它的信号量操作。
- 何时可以用普通(二进制)信号量替代互斥型信号量?——如果没有任务对共享资源的访问有截止时间的要求,可以。否则前者会造成无界优先级反转。
- 另外,计数型信号量用于某共享资源可以同时为几个任务所用时,这是互斥型信号量不能处理的(互斥信号量是二值的)。
- 用来实现资源同步的信号量在创建时初始值赋为可同时使用该资源的任务数目。在多数情况下,共享资源的该值为1。(注意与信号量做任务同步时的初值设置的差别)
- 任务信号量:任务信号量服务函数以OSTaskSem???()命名
- 每个任务都有内嵌信号量,称为任务信号量。任务信号量是在任务创建OSTaskCreate()时创建的,因此任务创建之后便可以直接使用。使用起来更方便,且速度比一般信号量要快。当事件发生时,用户若明确知道该给哪个任务发信号,此时就可以使用任务信号量。
- 任务不能等待另一任务的任务信号量;任务无需发送任务信号量给自己;与信号量时相同,只允许ISR发送任务信号量。
例:设计两个任务,它们以不同的频率让LED点亮30个时钟节拍,然后熄灭60个时钟节拍,要求这两个任务不会互相干扰。下面是两个任务的处理流程。
事件标志组
- 当任务要与多个事件的发生同步时,可以使用事件标志组。一个事件标志就是一个二值信号,事件标志组是若干二值信号的组合。
- 用事件标志组来做任务同步分为独立型同步(“或”同步)和关联型同步(“与”同步)。设一个任务与3个事件标志有关,如下图。
例:当时间到且独立按健被按下后,让LED1闪烁一下
例:当时间到或独立按健被按下后,让LED1闪烁一下
消息队列
-
一个任务或者ISR有时需要和另一个任务交流信息,这个信息传递的过程称为任务间(或ISR与任务间)的通信。有两种途径可实现:全局变量、消息队列。
-
消息可以通过消息队列作为中介发送给任务,也可直接发送给任务(μC/OS-III中,每个任务都有其内建的消息队列,称为任务消息队列,任务消息队列在任务创建时自动建立)。当有多个任务在等待消息的时候,使用外部的消息队列; 如果只有一个任务需要接收消息,可以使用该任务的任务消息队列直接向其发送消息。
-
任务在等待消息时不占用CPU时间.
-
发送的消息不会被复制一份再放置到消息队列中,而是使用引用传递。因此需保证消息内容在接收消息的任务代码内可见。
-
消息队列就象一个类似于缓冲区的对象,可以实现同步和数据通信。消息队列具有一定的容量,可以容纳多条消息。消息队列中的消息一般按照先入先出(FIFO)的方式放置,但在需要时也可安排为后入先出(LIFO,在发布消息时选择)。
-
向消息队列发送消息
OSQPost()
- 当任务往消息队列中发送消息时,可选择只将该消息发送给一个任务。当前等待消息的任务中只有最高优先级的那个将接收到消息,或最先进入等待消息列表的(同优先级)任务。也可选择以广播的形式发送消息,那么所有“等待此消息的”任务都将获得该消息。
- 如果没有任务在等待消息队列的消息,则发送消息时会判断消息队列当前是否已满 。如满则返回错误码:发送失败。
-
从消息队列接收消息
OSQPend()
- 消息队列中已存在消息,通过内核服务将消息传递给等待消息的任务中优先级最高的任务,或最先进入等待消息任务列表的(同优先级)任务。
- 如果消息队列为空,则等待消息的任务被放入等待消息的任务列表中,直到有其它任务向消息队列发送消息后,该任务才能结束等待状态或在等待超时的情况下运行。
- OSQPend()函数允许用户定义一个最长的等待时间Timeout作参数,这样可以避免该任务无休止地等待下去。
-
工作方式
- 一对一:最简单,也最常用;多对一:也经常使用;一对多:不常见,但也还是有某些场合使用,比如智能仪器仪表常常采用声、光与短信报警信号输出功能就是典型的一对多工作方式的应用
任务消息队列 - 每个任务都有它自己的内嵌消息队列,称为任务消息队列。任务消息队列是在任务创建
OSTaskCreate()
时创建的,因此任务创建之后便可以直接使用。 任务消息队列使用起来更方便。当用户明确知道该给哪个任务发消息时,此时就可以使用任务消息队列。
- 一对一:最简单,也最常用;多对一:也经常使用;一对多:不常见,但也还是有某些场合使用,比如智能仪器仪表常常采用声、光与短信报警信号输出功能就是典型的一对多工作方式的应用
-
一对一:例:让一个LED以传递过来的参数确定点亮时间
- 一对多:例:按键一按下,LED按照指定节奏闪烁,蜂鸣器按照指定节奏鸣响。
- 生产者消费者模型
- 使用信号量配合消息队列。
- 使用一个计数型信号量,初值为允许生产者发布的消息数目。如:消费者最多缓存10则消息, 则该计数型信号量的初值为10。
- 信号量用于交换队列空/满信息。
动态内存管理
-
ANSI C中,可以使用malloc()和free()两个函数来动态分配内存,在嵌入式系统中,它们一般也是可用的,但并不适合。会产生碎片,造成连续内存不够大,导致有内存也不能分配,程序执行失败;分配和释放的内存块大小不同,所以执行时间不确定。
-
为了避免上面的问题,μC/OS-III自己设计了一套动态内存分配系统。μC/OS-III的动态内存分配是以块为单位分配的,一次只能分配一个块,块的大小可以由用户来定义。系统可以管理多个堆(用于动态内存管理的空间),各个堆中的块的大小可以定义得不一样,但同一个堆中的块大小是相同的。
-
μC/OS-III的动态内存管理配合数据队列使用非常方便 。
-
动态内存管理的3个系统函数
例:让一个LED以传递过来的参数确定点亮时间,使用消息队列配合内存管理