第3章 实时操作系统 通用计算中,并发或并行理论和技术的主要价值在于提升系统性能。对嵌入式计算而言,许多活动都是同时进行的,并发是计算系统与物理系统交互的固有属性,支持多任务并发是嵌入式系统必备的计算能力。例如,发动机控制器在保证及时(不早不晚)点火的同时还需要响应外部控制命令。 图3.1计算机系统的不同视图 操作系统是计算机系统的关键组成部分,为应用软件开发和执行提供系统软件平台。如图3.1所示,不同用户通过不同接口与计算机系统进行交互。操作系统可以提供很多系统服务,包括并发多任务管理、存储管理、时间管理、I/O管理、文件系统、网络系统,以及人机界面等。当然,并不是所有系统都采用操作系统,并且操作系统自身也会带来额外运行开销。如果硬件相对简单,应用软件代码较小,则无须操作系统。 实时系统中的嵌入式计算机通常不直接与用户交互,因此,实时操作系统(realtime operating system,RTOS)的结构和提供服务的方式与通用操作系统有很大差异。典型的RTOS采用微内核(micro kernel)体系结构(如图3.2所示),其内核只提供最基本的系统服务,如任务调度和管理,其他服务位于内核之外,可根据应用需求进行剪裁和配置。 图3.2RTOS中的微内核 应用程序通过系统调用(system call)请求系统服务,系统调用的执行过程如图3.3所示。运行在用户态的应用程序发出系统调用后,进行调用参数合法性检测,再执行一条软中断指令(trap),处理器从用户态转入内核态(又称监控态、系统态),才开始执行OS内核中的系统服务程序。这一机制保证了OS的安全,隔离了应用程序之间的相互干扰。当然,也带来了状态切换的额外开销。因此,对时间敏感的硬实时系统,可能不区分用户态或内核态。 图3.3系统调用的执行过程 为了保证内核数据结构的完整性,非抢占内核(如某些版本的UNIX)在执行系统调用时禁止所有中断(关中断)。关中断方式的平均任务抢占时间优于使用互斥锁的可抢占内核(如μC/OSⅢ)。但对于非抢占内核,任务抢占只能发生在用户态,导致低优先级任务进行系统调用时,高优先级任务被阻塞,直至系统调用完成,造成优先级翻转。最坏情况下,系统调用可能需要执行几百ms,甚至有可能导致高优先级任务错失其截止期。 RTOS按预定的调度策略调度任务执行,保证所有关键任务的最坏情况可调度性,以及软实时和非实时任务的平均响应时间。“机制与策略相分离”是保证操作系统设计的灵活性的一个重要原则。RTOS通常支持优先级抢占或时间片轮转(roundrobin,RR)两种基本任务调度机制,而RM(rate monotonic,单调速率)或EDF(earliest deadline first,最早截止期优先)等调度策略由用户实现。 RTOS的主要特征如下。  提供时钟和定时器: 高分辨率的时钟和定时器对RTOS十分关键。  实时优先级: RTOS需要支持静态优先级。任务的静态优先级由程序员指定,RTOS不能改变。与之不同,通用操作系统为了提升吞吐率,可能改变用户指定的任务优先级。  快速任务抢占: 任务抢占时间指高优先级任务在抢占执行前的等待时间。RTOS一般为ms级,通用操作系统(非抢占内核)的最坏情况可能为s级。  可预测的快速中断延时(latency): 中断延时指中断发生与开始执行ISR的间隔时间。RTOS的中断延时必须有界,一般小于几ms。为此,可以采用推迟过程调用(deferred procedure call,DPC)技术,将ISR中需要完成的大部分工作放到一个较低优先级的任务中,待ISR完成后再执行。进一步可以采用中断嵌套技术,支持可抢占中断。  支持资源共享: 对于大中型应用,RTOS需要提供优先级天花板协议(priority ceiling protocol,PCP),确保共享资源访问时间有界。  内存管理: 除了大型复杂应用,RTOS一般不提供虚存和内存保护。  支持异步I/O: 传统的读写系统调用为同步阻塞式,进程向下进行前需要等待硬件完成物理I/O,并判断读写操作是否成功。异步I/O为非阻塞式,进程仅仅将读写请求发给硬件或OS队列,即可继续向下进行,无须等待物理I/O完成。 3.1反应式内核 内核有三个关键控制部件: 调度器(scheduler)、资源管理器(resource manager)、分派器(dispatcher),如图3.4所示。调度器根据调度算法(如RM或EDF)为每个任务分配处理器,设定原始优先级,并选择当前应执行的任务。资源管理器按资源访问控制协议(如优先级继承协议等)进行任务同步控制,确定任务的当前执行优先级,并为任务分配所需资源。分派器是调度器的一部分,完成新旧任务的上下文(context)切换,改变执行流,启动新任务。实时钟(realtime clock,RTC)提供时间信息,中断管理器(interrupt handler)提供异步事件服务请求信息。另外,系统实现时调度器与分派器可以合二为一。静态调度系统的任务调度表在设计时生成,运行时由分派器按静态调度表激活任务执行。 图3.4内核关键控制部件及其与其他部件的关系 大多数RTOS内核以任务为调度对象,多任务并发需要RTOS内核具有处理多任务的能力。通用操作系统一般以进程作为调度、资源分配和保护的对象,然而,线程模型的性能更优,内存开销更小,因此RTOS大多采用线程模型。对于单处理器系统,内核实际上是在保证各个任务的时间约束的前提下,交错地执行多个任务。 控制任务的时间约束依赖于系统对环境事件的控制响应要求。环境和系统的特性决定了任务产生其结果的截止期。需要定期激活的活动被定义为周期任务。从RTOS角度看,周期任务由内核按时间触发(timetriggered,TT)方式直接控制激活,因此保证了这类任务的定期性要求。而非周期任务由应用或外部事件触发(eventtriggered,ET)激活,包括显式地执行系统调用或发生与某任务相关的外部中断。即使外部中断定期到达,内核仍然可以按非周期方式处理,除非中断源保证激活速率的准确上界。 如果中断按定常速率发生或者存在最小到达间隔,则相应的非周期任务被称为偶发任务。通过假设其最坏情况(即按最大速率到达),可以保证满足偶发任务的时间约束。 当完成任务优先级划分并确定它们的时间约束、周期性和关键度后,RTOS可保证所有硬实时任务在其截止期之前完成。对软实时或非实时任务,将按尽力而为(besteffort)策略最小化其平均响应时间。 调度器可以作为编译器或代码生成器的一部分,在设计时产生调度决策; 也可以作为内核的一部分,在运行时产生调度决策。调度器可能被ISR调用,也可能作为某些系统服务的组成部分被调用。 执行流可能位于应用任务线程、ISR或RTOS内核中。当任务或ISR调用内核服务时,执行流进入内核相应的系统调用例程中。系统调用完成时,调度器负责选择下一个需要执行的任务,分派器负责引导执行流转向该任务。 3.2系 统 服 务 RTOS的内核只提供最基本的系统服务,如任务管理; 任务互斥、同步与通信; 内存管理、时间管理和I/O管理和异常与中断管理等。 3.2.1任务管理 任务是一个应用层抽象概念。在实时系统中,可以按功能或时序等属性,将一组活动或动作定义为一个任务。在RTOS中,任务往往实现为线程。 1. 多任务模型 多任务模型中,应用由多个任务构成。每个任务拥有各自的上下文,记录了任务执行时CPU各个寄存器的状态,这些状态随任务的执行而动态地变化。当系统切换执行不同的任务时,需进行任务上下文切换。新任务创建时,内核创建并维护该任务的任务控制块(task control block,TCB)。TCB是内核存储每个任务相关信息(包括上下文)的数据结构,如图3.5所示。 当任务被中断时,其上下文在TCB中被冻结,并在下一次运行时再恢复。调度器进行上下文切换时,在当前任务的TCB中保存其上下文,从下一个任务的TCB中加载其上下文(即该冻结的任务在上一次执行被中断时的CPU寄存器映像)。这个切换过程所需时间为上下文切换时间。硬实时系统必须考虑上下文切换开销。 大多数操作系统内核支持的任务调度策略有如下两种。  基于优先级(prioritydriven,PD)的调度。假设每个任务都被指定了优先级,调度器总是按优先级从高到低的顺序选择任务执行。通常优先级值小表示优先级高。  基于CPU使用比例的共享式(sharedriven,SD)调度。按任务权重(比例)进行调度,让任务的执行时间与其权重成正比。实现算法包括轮转法、公平共享、公平队列、彩票调度法(tottery)等。 RTOS一般都支持优先级抢占式调度算法,即如果任务的优先级高于其他任务,将立即抢占执行。 轮转调度中,CPU的计算时间被划分为定长的时间片,各个任务轮流占用时间片执行。使用每个时钟嘀嗒递增的计数器跟踪记录任务使用的时间片数量。 任务由任务名、ID、优先级(采用优先级调度时)、TCB、堆栈、程序代码等构成。在系统运行过程中,任务总是处于某个状态,典型的包括就绪(ready)、运行(running)和被阻塞(blocked)等,构成任务状态机(如图3.6所示)。 图3.5TCB结构示例 图3.6任务状态机  就绪: 任务已经准备好,但由于高优先级任务正在执行,因此尚未被分派处理器执行。  阻塞: 任务请求的资源尚不可用,或需要等待某事件发生,或需要延迟一段时间。  运行: 任务为最高优先级,正在处理器上执行。 就绪任务处在就绪队列中,阻塞任务处在阻塞队列中,如图3.7所示。任务被内核创建激活后置于就绪队列。调度器每次启动后首先决定哪些任务需要改变状态,再根据调度策略从就绪队列中选择下一个任务分派执行。 图3.7任务和任务队列 正在运行的任务可能由于被高优先级任务抢占而回到就绪态,也可能因为申请尚不可用的资源、等待某事件发生或需要延时执行等原因而进入阻塞态。处于阻塞态的任务因为相应的阻塞条件消解而重新进入就绪态。 典型的任务结构有如下两种。  runtocompletion任务。执行一次就不再重复执行。通常用于系统启动阶段的初始化,包括创建其他任务和共享变量等。  endlessloop任务。在系统工作期间重复执行。完成“采样计算输出”控制过程。此类任务采用无限循环结构,其中应包含阻塞调用,以便当前作业完成后挂起自己,让低优先级任务得以执行。 2. 系统服务 RTOS所提供的任务管理服务API包括创建与删除任务、任务调度和获取任务相关信息等。 任务创建后将进入就绪队列,等待调度执行。删除任务将终止任务的执行,并释放它所占用的内存。但不正确的删除操作可能导致共享数据不一致或信号量未释放,甚至造成内存泄漏,导致系统崩溃。因此,必须确保某任务已经释放其所占用的所有系统资源后才能进行删除。 系统运行过程中,由内核控制任务的状态变化,但用户也可以使用系统服务参与任务调度,如自我挂起、改变任务优先级或禁止抢占等。 3.2.2任务互斥、同步、通信 实际应用中,所有任务相互独立的应用场景非常少见,多数任务之间具有显式或隐式的依赖关系,如哲学家进餐(dining philosopher)问题、读者写者(readerwriter)问题、理发师睡觉(sleeping barber)问题和生产者消费者(producerconsumer)问题等经典的进程间通信(interprocesscommunication,IPC)或任务间协作场景。 首先,多个任务可能需要竞争使用特定的资源(文件、设备或通信通道等),而这些资源某时刻只允许单个任务独占式访问,如哲学家进餐问题中的筷子,此时需要任务间的互斥机制。其次,某些任务需要使用其他任务的计算结果,如生产者消费者问题,或计算位置偏差的任务需要使用计算当前位置任务的计算结果,此时任务间需要相互通信。如果基于共享内存(全局变量)进行通信,为了保证数据的一致性,通信双方需要互斥地访问共享内存,称为资源访问同步。即使任务间没有显式的数据传递依赖关系,各任务也可能需要按特定的顺序执行,如读者写者问题和理发师睡觉问题,或初始化任务需要先于其他任务执行等,此时任务间需要同步,称为活动同步。 计算机系统中,事件和信号是一个泛化概念,事件可由软件(内部)事件源或硬件(外部)事件源产生,信号是事件的通知。此外,操作系统中典型的事件响应机制包括事件标志(或称事件寄存器)、信号(软中断)和外部中断(硬中断)。 实际应用场景中,往往需要通信过程与同步控制两者相结合,实现特定的通信或资源共享协议。典型场景有如下几种。  任务与任务间基于二值信号量同步。  ISR与任务间基于二值信号量同步。  任务与任务间基于事件标志同步。  ISR与任务间基于事件标志同步。  ISR与任务间基于计数信号量同步。  带数据交换的会合点同步。 1. 通信模型 任务间通信可以交换信号以通知事件发生(称信号通信),或交换各自的数据(称数据通信),或者数据交换同时伴有事件通知。 嵌入式实时系统中,通信可以发生在任务与任务之间或任务与ISR之间。可以为单向无确认方式(松耦合通信),亦可为双向确认方式(紧耦合通信),或者广播式。 数据通信的任务间存在数据依赖关系,基本通信模型为生产者消费者模型。 从实现角度,通信分为共享内存(sharing memory)和消息传递(message passing)两种模式。 1) 共享内存 所有任务都通过访问相同的内存区域完成通信。这一内存区域被称为临界资源(critical resource),访问共享资源的程序代码称为临界区(critical section,CS)。当两个或多个任务读写共享内存,而最终的结果取决于任务运行的精确时序,称为竞态(race condition)。此时,除非对该区域的访问只有只读操作,否则必须考虑多个模块并发访问时的互斥问题,即将并发访问串行化,以避免竞争访问导致最终结果数据不一致。OS级的互斥访问可以采用多种技术,如互斥量(mutex)或监视器(monitor,亦称管程)等。 共享内存通信效率高,但处理竞态等问题开销大。在任务间共享多个临界资源时,如果允许抢占,还应考虑死锁和优先级反转等问题。另外,对于硬件上没有共享内存的系统,实现更加困难。 2) 消息传递 协作任务间互相发送的信号称为消息或事件。基于通信双方或多方共享的通信介质(信道、通道),以消息为单位进行通信称消息传递。典型的应用存在如下两种方式。 (1) 异步/非阻塞(asynchronous/nonblocking)方式。此方式下,收发双方无须同步。发送方不需要知道接收方是否准备好接收消息,只需按自己的步调发送消息。由于通信双方不同步,因此需要采用带缓冲的通信通道,也需要注意处理消息溢出问题。 (2) 同步/阻塞(synchronous/blocking)方式。通信开始前,收发双方必须通过某种机制进行同步,相互等待对方准备好,再进行通信。 消息传递可以基于共享内存或网络信道实现。 2. 同步模型 同步的基本含义是同时(same time)发生或同速率(same speed)执行,但在计算机系统的不同抽象层次,同步一词的具体语义有所不同。如前所述,在操作系统层,同步可划分为资源同步和活动同步两类。资源同步保证多个任务同时安全地访问共享资源,活动同步使多个任务相互等待,同时到达特定的状态。 1) 资源同步 多任务并发访问共享资源必须保证资源状态的完整性(integrity)。资源同步要求各任务的临界区代码互斥地执行,即并发任务串行化顺序执行,任何时刻只允许一个任务访问共享资源。写写操作竞争将破坏资源的完整性,写读操作竞争使读出数据不一致。互斥算法保证某任务执行临界代码时不被其他竞争此资源的任务中断。 资源同步可以基于clientserver模型,由集中式资源服务器负责协调共享资源访问请求。资源服务器基于预分配策略或启发式策略确定合法的访问请求。 2) 活动同步 将异步环境下一组并发任务通过消息通信而进行相互协作、互相等待,使得各任务按一定速度执行的过程称为任务间活动同步。具有活动同步关系的一组并发任务称为协作任务,活动同步语义为任务在同步点相互等待,步调一致地向前执行。此时任务间无数据交换,是一种“低级通信”。 活动同步是多任务协调执行顺序的操作,既可以采用同步式,也可以为异步式。具体模式包括相互等待同步(waitandsignal sync)、多任务会合点同步(rendezvous sync)、信号速率同步(credittracking synchronization,或rate sync)以及同步栅(barrier sync)等。 同步栅模式中,多个任务异步到达同步栅,最后到达者广播通知其他任务。所有任务同时跨越同步栅继续执行。单处理器环境下,“同时”意味着并发交错执行。实现同步栅包括三个动作: 某任务告知其他任务已抵达同步栅; 该任务等待其他任务抵达; 该任务收到继续执行的通知。同步栅可以基于互斥锁或条件变量实现。Ada语言提供支持同步栅的语法结构。 某些文献中,会合点指两个任务同步,同步栅指多个任务同步。支持会合点同步的语言提供会合点入口(entry)语法结构(为一回调函数)。某任务定义并发布其入口函数,并等待其他任务调用此入口函数。发布任务接受此函数调用,并将结果返回调用者。 会合点同步与事件标志类似。如果入口调用没有发生,则发布者阻塞等待。两者差别在于会合点允许双向参数传递,即入口函数接收输入参数,返回输出结果。简单的会合点同步也可以使用信号量或消息队列实现,而不使用入口函数。两个任务间使用信号量实现会合点同步不支持数据传递。 3. 系统服务 RTOS一般提供信号量、消息队列、事件、信号和条件变量等机制,支持任务间的互斥、同步与通信。注意,不同系统中事件和信号的语义有差异。 1) 信号量 信号量(semaphores)是内核对象,用于并发任务间相互同步或互斥地访问共享资源。创建信号量时,内核建立相应的信号量控制块(SCB)记录此信号量的ID、值(二进制或计数值,亦称令牌数)和等待任务队列等信息。 任务可以申请或释放信号量。信号量的值有限,任务申请(acquire)信号量时值减1,释放(release)时值加1。内核跟踪各信号量的令牌数。一旦计数为0,新的申请者将被阻塞等待,直至有任务释放该信号量。等待信号量的任务可按FIFO或优先级方式进行排队。 信号量分为二值(binary)信号量、计数(counting)信号量和互斥(mutex、mutual exclusion)量等多种类型。 (1) 二值信号量。其值为0或1,代表资源空闲或被占用。0表示不可用,1表示可用。初始化时可置为0或1,一般置为1。内核提供acquire()/wait()和release()/signal()服务原语。常用于资源访问或任务同步。 (2) 计数信号量。创建时设置令牌数上界。一旦令牌数为0,表示资源已分配完。内核提供acquire()/wait()和release()/signal()服务原语。常用于资源计数或同步控制。 (3) 互斥量。这是一种特殊的二进制信号量。创建时互斥量值初始化为0,表示资源空闲。mutex亦称锁(lock),其值为1时表示资源被使用者加锁,申请者需等待。内核提供lock()和unlock()服务原语。互斥量可用于解决数据一致性控制、任务安全删除、资源使用权分配或优先级反转控制等问题。 RTOS所提供的信号量管理服务API包括信号量创建与删除、申请与释放、清空等待队列和获取信号量相关信息等。任务申请信号量时可以采用持续等待(wait forever)、等待超时(wait with a timeout)或无等待(no waiting,信号量无效时任务不被阻塞挂起)等几种方式。清空某信号量的等待队列(flush原语)将释放队列中的所有被阻塞的任务,使之进入就绪队列。因此,清空操作可用于实现多个任务会合点同步。 信号量通常用于任务间同步。为了访问共享资源,可以使用二进制信号量,也可使用互斥量。由于信号量可被任何任务释放,即使该任务不是最初获得此信号量的任务,因此是不安全的。而互斥量基于所有权概念。某任务对资源成功加互斥锁后,则拥有此任务的所有权。其他任务只能加共享锁,不能再加互斥锁。只有互斥量的所有者才能释放此互斥量,因此,互斥量保证了共享资源的独占式访问。 【例3.1】两个任务同步点相互等待同步。二进制信号量sem初始化为不可用(值为0)。设高优先级任务先到达执行,同步点申请sem时被阻塞挂起,等待低优先级任务。低优先级任务得到执行机会,在同步点释放信号量。■ 【例3.2】多个任务会合点同步。二进制信号量sem初始化为不可用(值为0)。设多个高优先级任务先执行,各自执行到同步点时申请sem被挂起。最后低优先级任务执行,同步点时调用flush原语清空sem的等待队列,同时释放所有被阻塞的高优先级任务。■ 【例3.3】执行速率匹配。设发送信号的任务(高优先级)发送速率快于接收信号的任务(低优先级),可用计数信号量记录信号发生次数。信号量初始化为不可用(值为0)。低优先级任务先到达,申请信号量被阻塞。高优先级任务到达后,释放信号量,使低优先级任务进入就绪队列,直至高优先级任务作业完成。在低优先级任务开始执行前,允许高优先级任务按其信号发生速率多次发送信号(释放信号量),而低优先级任务根据信号量的令牌计数累积逐一响应,实现对信号突发模式的同步跟踪处理。■ 【例3.4】单一共享资源互斥访问。 共享资源包括内存单元、数据结构或I/O设备等待。可采用二值信号量sem进行资源保护。初始化时sem值为1。访问被此信号量保护的资源时首先需申请sem(值变为0),使用完成后需释放sem(值重新变为1)。如果任务申请信号量时值为0,则资源被其他任务占用,只能等待释放。此方法的风险在于信号量可能被没有使用资源的其他任务错误释放,导致一致性访问约定被破坏。安全性方法是使用互斥量mutex。■ 【例3.5】单任务重复访问资源控制。设任务及其调用的子过程都需要访问共享资源。使用sem时如果没有及时释放,将造成嵌套访问,导致任务自身锁死。此时应使用互斥量mutex。当任务对mutex加锁后,则临时拥有资源的所有权,此任务及其子过程都可以再次锁定mutex而不会被阻塞。■ 【例3.6】多资源访问控制。设资源由多个等价资源构成。可使用计数信号量或多个互斥量。■ 2) 消息队列 消息队列是一种数据缓冲区内核对象,用于任务或ISR间通过收发消息进行数据通信。发送者将消息暂存于消息队列中,直至接收者准备处理这些消息。消息队列使收发双方解耦,使他们无须同时进行收发操作。 RTOS所提供的消息队列管理服务API包括队列创建与删除、消息发送与接收、广播消息,以及获取消息队列信息等。 消息队列创建时,内核为其分配队列控制块(QCB),指定队列名和ID,根据用户指定的队列长度和最大消息长度分配内存缓冲区,并建立等待任务队列和排队策略等。消息队列为内核全局对象,不属于任何任务。 消息发送过程通常为两次复制,从发送者内存空间复制到内核的队列缓冲区,再复制到接收者的内存空间。由于数据复制开销大,减少复制次数(直接从发送者内存复制到接收者内存)或减少数据量(如发送数据指针而不是数据本身)有利于提升系统效率。 消息队列可为满或空。队列满后,发送者将被阻塞; 队列空时,接收者将被阻塞。ISR使用消息通信时不能被阻塞,异常时只能返回错误。 接收者从队首读取消息,一种方式是破坏性读取,另一种为非破坏性读取。 【例3.7】非互锁单向数据传输。非互锁单向数据传输是一种最简单的消息队列通信形式。ISR通信只能采用这种形式。■ 【例3.8】互锁单向数据传输。采用消息队列和信号量,收发双方实现握手同步,也称锁步(lockstep),实现可靠通信。如果数据错误,可以重发。■ 【例3.9】互锁双向通信(也称全双工通信)。互锁双向通信可用于client/server系统。需要两个消息队列,并使用信号量实现同步握手。■ 【例3.10】广播通信。基于一个消息队列,一个任务发送消息,多个任务接收此消息。■ 3) 事件标志 事件标志(event flag)用于标识某些事件是否发生,是一种异步的单向任务同步机制。任务只有在主动调用接收事件API时才与事件同步。事件标志可以由任务或ISR设置。事件不排队,也不进行计数,即同一事件的后一次到达将覆盖前一次事件。如果同一事件可由多个事件源产生,事件标志并不指示当前的事件源。如果需要区分,可以将它们定义为不同事件。 任务可以等待某个特定事件,也可以等待多个事件。任务等待多个事件发生时,事件之间可以是“或(or)”或“与(and)”的组合关系。ISR也可以与事件同步,但不能被阻塞。 使用事件同步时,任务可以与一个事件寄存器(event register)绑定。事件寄存器的每位为一个二进制事件标志,定义了该任务所等待的所有事件。任务通过检查事件寄存器中的事件标志与特定事件同步。 内核使用事件控制块管理事件标志,记录任务等待的事件(wanted event)、当前到达的事件(received event)、任务等待超时(timeout value)及通知条件(notification condition)等。通知条件指示任务等待多个事件时的与或组合条件。 与事件标志相关的RTOS服务包括创建或删除事件寄存器、事件标志置位或清零,以及接收事件等。 4) 信号 信号(signal)是事件发生后的一种软中断机制。信号到达时,如果当前执行的是接收此信号的任务,则其被中断,转而执行与该信号相关的异步信号例程(asynchronous signal routine,ASR)。信号与硬中断的区别在于其由任务或ISR产生,而不是由外部事件产生。并且,任务可以指定其响应的信号。 信号响应是非嵌套的,新信号被挂起,只有处理完当前信号后才能响应。信号可以被忽略(屏蔽)或阻塞。忽略意味着不发往任务,阻塞意味着信号被挂起,直至当前任务执行完特定程序块后再被中断。内核维护信号向量表和信号控制块(SCB)。信号向量表记录ASR入口地址。信号控制块记录允许信号集、忽略信号集、挂起信号集和阻塞信号集。 内核服务包括设置接收的信号(catch)、删除已设置的信号(release)、发送信号至某任务(send)、禁止产生某信号(ignore)、设置阻塞信号(block)和解除被阻塞的信号(unblock)。 可以使用信号机制实现ISR的上下部。但信号机制开销大,且与任务执行异步,导致系统执行的不确定性。 5) 条件变量 条件变量(condition variable)用于确定共享资源的状态。例如,文件或通信信道等共享资源存在不同的状态。任务可能需要等待这类资源被设置为(由其他任务造成)特殊的状态才能进行访问。条件变量与特定资源绑定,允许某任务等待其他任务改变资源状态,以满足此任务的访问条件。条件变量允许进行信号(signal)和等待(wait)两种操作。当任务对某个条件变量进行wait操作时,如果条件不满足,该任务将被阻塞,直到其他任务对此条件变量执行signal操作。 一个条件变量可以与多个条件相关联,多个任务也可能排队等待同一个条件变量。协作的任务需要预先约定各自访问共享资源的条件。 条件变量本身也属于共享资源,需要互斥访问,以避免条件的不一致。因此,需要使用锁对其进行保护。注意,条件变量不是共享资源访问的同步机制,只是用于判断共享资源的值或状态是否满足执行要求的机制。在进行条件评估之前,任务首先需要获得锁。如果访问条件不满足,任务需要释放锁并进入等待队列阻塞等待。内核需要保证任务释放锁和阻塞等待为原子操作。 内核服务包括创建共享变量(create)、任务等待条件满足(wait)、通知条件满足(signal)和通知所有等待任务条件满足(broadcast)等。某任务执行signal操作指示资源的条件已经被建立,内核将按照预定策略(优先级或FIFO)从等待队列中释放一个任务。broadcast操作将唤醒所有等待任务。 条件变量用于任务之间的同步,允许一个任务等待直至另一个任务将共享资源设置为所期望的状态。但条件变量不是作为互斥访问共享资源的同步机制,其典型应用为生产者消费者问题。 【例3.11】互斥锁和条件变量实现同步栅。如下伪代码所示,每个参与同步的任务都需要调用barrier()函数。函数的第8行获得互斥量,第9行对同步任务计数,第10行检查是否所有任务都已经到达同步栅。如果仍有任务尚未到达,barrier()函数调用者被第11行条件变量函数cond_wait()阻塞。如果调用者是同步栅的最后一个任务,则第14行重置同步栅,第16行通知所有任务同步结束。■ Pseudo code for barrier synchronization. 1typedef struct { 2mutex_t br_lock;/* 卫式互斥量 */ 3cond_t br_cond;   /* 条件变量 */ 4int br_cont;   /* 到达同步栅的任务数 */ 5int br_n_threads;   /* 参与同步栅的任务数 */ 6} barrier_t; 7barrier(barrier_t *br) { 8mutex_lock(&br->br_lock); 9br->br_count++; 10if (br->br_count < br->br_n_threads) 11cond_wait(&br->br_cond, &br->br_lock); 12else { 13br->br_count = 0; 14cond_broadcast(&br->br_cond); 15} 16mutex_unlock(&br_lock); 17} 3.2.3内存管理 内存管理涉及内存分配、内存保护和内存容量分析等问题。 1. 内存分配 标准C语言中,经常使用malloc()和free()原语进行动态内存分配或释放。然而,在实时系统中,malloc()和free()不利于保证系统可预测性。一方面,它们的执行时间不确定; 另一方面,动态分散式内存分配造成的内存碎片使内存申请的结果不可预知。 虚存管理减少了平均内存访问时间,但增加了最坏情况内存访问时间。开销在于存储页表和虚实地址映射。缺页处理的开销也很大,并伴随非常高的访问时间抖动。因此,支持虚存的操作系统需要提供内存加锁之类控制换页的机制,防止某页被换出至外存。 为了保证可预测性,RTOS一般不采用虚存技术,而是在应用程序申请内存时为其分配一块地址连续的物理内存。由于不支持虚存,内存碎片处理困难,也没有内存保护机制。用户程序与操作系统处于同一地址空间,普通功能调用和系统调用没有区别。错误使用指针很难发现。 内存分区(partitioned memory)管理是RTOS中常用的一种技术(如μC/OS)。每个内存分区由固定大小的内存块构成。任务先创建分区,再从分区中取得内存块。内存块分配和释放在常数时间内完成,且为确定的。任务可以创建和使用多个分区,从而可以使用不同大小的内存块。这种方法缺乏灵活性,且增加了内部碎片(内存块内部的剩余空间)。此外,应用所需的内存块大小难以预估,可能某些大小的内存块需求量大,而另一些块却空闲。 【例3.12】基于内存分区技术,内存空间划分为32、50和128固定块大小的三个内存池,如图3.8所示。内存控制结构(memory control struct,MCS)记录各个内存池的信息,包括块起始地址、块大小、块数和空闲块数等,并按块大小排序。内存池(空闲链表)为单向链表。内存分配与释放都发生在链首。每次申请或释放内存块时更新MCS。■ 图3.8基于内存分区技术的内存空间划分 内存耗尽时,malloc()不允许调用者等待内存可用。某些实时系统中,一旦内存分配失败,任务需要回滚至某个检查点,或者重启系统,代价过高。然而,很多情况下,内存耗尽是暂时的。如果任务能够预知内存拥塞,则可以提供更多的设计选择。例如,网络通信中,数据包可能突发到达,接收任务需要占用大量内存,使得发送任务暂时无法申请到内存以发送数据。此时,如果发送任务能够预知这种情况,就无须回滚或重启系统。因此,内存分配算法应提供申请内存时永久阻塞、超时阻塞或非阻塞等选项。 【例3.13】可以使用一组计数信号量和一个互斥量实现阻塞式内存分配函数,如图3.9所示。为每个内存池绑定一个计数信号量,其计数初值为初始化时内存池中可用空闲内存块数。为MCS绑定互斥锁。任务申请内存时须先获得计数信号量,再获得互斥锁。任务可以等待内存块可用,获得可用块,再继续执行。 图3.9阻塞式内存分配 申请内存时,任务首先申请计数信号量。如果无空闲块,则任务被阻塞等待(而不是返回内存分配失败)。如果有空闲块,则计数信号量减1,为该任务预留内存块。下一步,任务申请互斥锁,如果成功,则进一步获取内存块,并释放互斥锁。 释放内存时,任务首先申请互斥锁,释放内存块,释放互斥锁,再释放计数信号量。 注意,此设计方案不会造成优先级反转。■ 2. 内存保护 MMU(memory management unit,内存管理单元)负责虚实地址映射和内存保护。另一种选择是忽略MMU的虚实地址空间映射机制但利用其内存保护机制。此时,MMU使能,物理内存按页访问。MMU提供的页属性包括代码或数据存储标识,页访问权限(可读、可写、可执行,或它们的组合),非特权模式下是否允许CPU访问等。MMU自动检查任务访问的合法性,并触发相应的异常处理。 3. 内存容量分析 与通用计算机相比,嵌入式计算平台存储容量有限。因此,需要合理组织存储空间映射,需要在设计时分析应用的存储需求,计算存储容量上界。 栈通常用于存放局部变量,传递参数,或实现过程调用、中断响应和任务上下文切换。栈帧(stack frame)是栈中一种保存程序断点现场的固定的数据结构。 堆用于动态内存分配。堆结构有助于跟踪和管理内存分配和回收过程。malloc()从堆中申请内存,free()向堆中释放不再使用的内存。如果用完的内存没有释放,则导致内存泄漏。对长时间连续运行的嵌入式系统,内存泄漏累积可能耗尽物理内存空间,导致程序崩溃。某些系统中周期性运行垃圾回收器,自动回收(释放)不再使用的内存,原则上无须程序员再显式地释放内存。 系统进行内存碎片整理和垃圾回收时要求正在运行的所有任务都暂停,对于实时系统而言,难以采用。 过程调用或中断处理均需使用栈。如果应用超出所分配的栈空间,则发生栈溢出(stack overflow),可能改写其他内存空间的数据,导致不可预知的后果。栈空间分析计算应用中栈大小的上界。假设任务执行不被中断,且程序中不使用递归,则可以遍历函数(或过程)调用图(call graph)确定所需栈空间上界。 函数调用图是控制流图(control flow graph,CFG)的一种扩展形式。CFG是一个有向图,其中点集由程序的基本块组成,边集则表示基本块之间的控制流。通过在CFG中引入调用(call)边和返回(return)边分别连接调用函数和被调函数,描述函数调用的控制转移过程,构成调用图,如图3.10所示。如果不使用递归,函数调用可以通过内联(inlining)实现,即将被调函数的代码复制到调用函数中,那么,函数调用图退化为内联后的CFG。 图3.10函数调用图举例 调用图描述函数的相互调用情况,可以通过跟踪调用图的关键路径得到调用和返回序列。据此,如果每个栈帧的大小已知,则可计算最坏情况下栈的大小。如果考虑中断驱动的系统,中断嵌套等问题导致软件的栈空间分析更加复杂。 堆分析比栈分析困难。一方面,程序所使用的堆空间大小取决于输入数据值,而且只有在运行时才能知道; 另一方面,堆空间大小还取决于动态存储分配和垃圾回收算法的细节。 3.2.4时间管理 时钟和时间服务是所有OS都提供的基础设施。高分辨率时钟(ns级)可以通过将硬时钟映射到用户空间而实现。在计算机系统中存在着许多硬件计时器,如实时钟、时间戳计数器(time stamp counter,TSC )和可编程间隔定时器(programmable interval timer,PIT)等。 1. 实时钟 实时钟用于记录当前时间和日期。实时钟由专门的电池供电,与PIT无关。应用程序通过OS系统调用,得到时间服务,如获取当前时间等。 2. 系统时钟 OS提供的时间服务基于软件时钟,称系统时钟(system clock)。系统时钟基于PIT对系统晶振时钟信号计数。OS根据所接收的PIT时钟中断,维护系统时钟。 PIT也称为定时器芯片,可用于事件计数、时间测量、周期性事件生成或系统时序控制等。每秒产生的定时器中断数称为定时中断速率,由输入时钟源的频率和PIT的设置(定时计数值)决定。PIT按定时计数值对时钟信号递减计数,计数为0时产生一次中断请求,称为时钟嘀嗒(tick),代表了时间单位。如定时速率为100tick/s,则每10ms一次tick。实时内核的一个重要功能是周期性响应tick,并据此触发任务调度。时钟中断ISR中将进行绝对时间(墙钟)和流逝时间(系统上电后)更新。 硬实时系统的时序约束往往为ms级,因此,系统时钟需要细粒度分辨率(两次tick的间隔时间)。虽然硬件时钟(系统晶振)的分辨率为ns级,但在OS中实现高分辨率时钟还是很困难,原因有如下两点。  中断服务开销(包括上下文切换等)过高,以及存在中断响应时间抖动(BCRT与WCRT之差)。  系统调用响应时间的抖动。中断响应的优先级高于系统调用,时间服务系统调用可能被抢占,是造成其响应时间抖动的一个因素。 商用RTOS的系统调用响应时间抖动范围为ms级,因此,比此值更低的系统时钟分辨率没有意义。 某些实时应用需要ns级时间分辨率。一种方式是将硬时钟映射到用户空间,应用程序可以按常规的读内存的方式直接读硬时钟,而无须通过系统调用。例如,用户程序可以直接读Pentium的时间戳计数器。这个计数器在系统上电时被置为0,每CPU CLK递增,分辨率为ns级。这一方法的问题在于降低了应用程序的可移植性。 3. 定时器 定时器为时钟中断计数器。RTOS通常提供周期定时器和非周期(单次)定时器两种。  周期定时器。用于周期性活动或事件响应,如周期采样等。周期定时器周期性超时和重置。  非周期定时器。用于单次超时,如看门狗定时器。看门狗定时器可用于检测任务错失截止期或系统失效。每次应用看门狗定时器都需要重新设置。一旦发现超时,将进入任务异常处理或重启系统。 4. 定时事件管理 RTOS维护一个预处理定时器队列和一个系统定时器队列(如图3.11所示)。定时器队列按照各个定时器超时的先后顺序排队。每个定时器与其ISR相关联,定义定时器超时时应完成的操作。 图3.11定时器队列 RTOS响应系统时钟中断时需要完成以下任务。  递增软时钟计数。  处理定时器事件。发生时钟中断时,RTOS内核搜索各定时器的超时事件。一旦发现定时器事件,则将其ISR插入就绪队列(ready queue,RQ),等待调度执行。  更新就绪队列。由于发生了新的定时器事件,表示新的任务到达或等待此事件的任务就绪。RTOS需要整理等待队列(waiting queue,WQ),将就绪任务移至就绪队列。如果该任务的优先级高于正在执行的任务,将可能发生抢占。  更新执行预算(时间片)。每次时钟中断调度器都要递减正在执行任务的时间片。如果其剩余时间片为0,则被剥夺执行。 定时器的结构有多种,如表3.1中的基于升序链表、基于最小堆和基于时间轮(timing wheel)等,不同应用场景下需要考虑它们的效率和复杂度。 表3.1常用定时器实现算法复杂度 实 现 方 式StartTimerStopTimerPerTickBookkeeping 基于链表O(1)O(n)O(n) 基于升序链表O(n)O(1)O(1) 基于最小堆O(lgn)O(1)O(1) 基于时间轮O(1)O(1)O(1) 假设系统有大量的定时器,使用升序链表性能低下,添加一个定时器的复杂度是O(n),此时时间轮比较适合。时间轮由固定大小的数组构成(如图3.12所示,类似于循环缓冲区),每个数组元素为一个时间槽,代表时间单位,即软定时器的精度。时间轮以恒定速率顺时针转动,每转动一步(称一次tick),槽指针(clock dial)就指向下一个槽。每个槽绑定一个超时事件回调函数双向链表,代表同一个超时时间的一系列定时器(事件)。设置新的定时事件时,以当前槽指针为基准,将其挂载到对应的时间槽位置。注意,插入定时事件的误差为最小超时(定时器精度)的二分之一。通过时间轮排序的时间表可以高效地更新、设置(install)和撤销定时器事件。 图3.12时间轮工作举例 超时频率决定定时器的精度。例如,如果定义每个tick的间隔为50ms,则每个槽代表时间流逝了50ms,定时器的最小超时事件也只能是50ms。 假设时间轮有N个槽,tick间隔为si,则时间轮转动一周的时间为T = siN。插入定时器时可以直接计算出要放在哪个槽。假设在t时间后到期,则insertslot = [curslot + (t/si)]%N,即插入数组下标为insertslot的槽位,复杂度为O(1)。 典型的时间轮操作包括定时器启停和超时处理。 基本时间轮结构的时间槽数量固定,即超时定时器数量固定。超出范围的定时事件可用一个附加的溢出事件缓存临时保存,待时间轮转到合适槽位时进行处理。如图3.13所示,400ms事件需要在槽2时处理,500ms事件需要在槽3时处理等。溢出事件缓存需要按升序排序,每次tick时都需要进行检查。这一问题也可采用层次化时间轮方法进行解决。层次化时间轮基于ticksPerWheel(一轮的tick数)、tickDuration(一个tick的持续时间)以及timeUnit(时间单位)3个参数设计。 图3.13具有溢出事件缓存的时间轮 每个时间槽所绑定的定时事件应同时触发执行,但如果每个槽的事件数无法确定,时间轮算法无法保证各事件的响应时间有界。 RTOS提供的定时器管理API一般包括硬件时钟操作、软件定时器操作和实时钟或系统时钟访问操作等几大类,供BSP、系统软件和应用软件调用。 3.2.5I/O管理 嵌入式实时系统往往用于对特定的设备进行控制或管理。对I/O操作的理解依赖于设计者的视角和对硬件细节了解的需要。 对系统软件开发者而言,I/O操作指与设备进行交互和对其进行编程,使设备完成I/O请求初始化,在系统与设备之间进行数据传输,并在操作完成时通知请求者。 程序员需要理解设备的物理属性和访问方法,如设备与系统的组织结构,控制寄存器定义等。对于多实例设备,定位正确的实例是设备通信的内容。 开发设备驱动程序还需了解设备操作过程中出现错误应如何处理。 对RTOS而言,I/O操作需要为I/O请求定位正确的I/O设备,确定正确的设备驱动程序,并向设备驱动程序发送I/O请求。有时也需要RTOS保证访问同步。RTOS需要向应用软件开发者提供隐藏设备特殊性和规格的抽象。应用开发者的目标是以一种简单统一的方法与系统中的所有设备通信,并向终端用户提供有用的数据。 1. I/O系统 I/O系统由I/O设备、设备驱动和I/O子系统构成。I/O系统的编程模型主要涉及设备类型、设备端口与寻址、I/O控制方式等问题。 1) 设备模式 设备模式(或类型)包括字符设备(charactermode)和块设备(blockmode)两类。字符设备以单个字符为数据单位,允许进行无结构的数据传输。通常采用串行通信方式,一次传输1字节。字符设备通常为串口或键盘等简单设备。设备缓冲用于系统与设备之间的传输速率匹配。块设备以数据块(如1024字节)为单位进行数据传输。某些协议强制要求数据必须结构化,即如果块大小不足,必须填满,否则为错误。 2) 设备接口 设备接口(端口)一般由控制寄存器、地址寄存器、数据寄存器和状态寄存器等组成。系统中所有设备的端口寄存器地址构成I/O地址空间。如果I/O地址空间单独编址,称为独立编址方式或端口映射方式; 如果I/O地址空间与内存地址空间统一编址,称为内存映射方式。独立编址方法需要使用专门的I/O指令进行I/O操作。所有I/O设备都必须通过设备控制寄存器进行初始化。 3) 设备控制方式 在以轮询或中断模式进行输入输出的程序I/O方式下,设备与内存间的数据传输需要通过处理器寄存器进行中继。因为存在多次数据复制,这种方式不能满足高速I/O的要求。DMA方式通过专用芯片控制设备与内存之间直接进行数据传输。数据传输前,需要处理器指定传输源和目的地址,以及需要传输的数据量。一旦DMA开始,则无须处理器介入。此时,数据传输率取决于设备、内存和DMA控制器的性能。 2. I/O子系统 所有与特定设备相关的操作代码组成设备驱动程序。每个设备驱动程序处理一种类型的设备,负责接收上层应用的设备独立的I/O操作请求,向设备控制器发送设备依赖的命令,监测命令执行并向上层返回执行结果。每个I/O设备驱动都提供一组专门的I/O编程API。 在嵌入式系统中,I/O子系统的目标在于向内核和应用开发者隐藏各种设备的特殊信息,提供访问所有设备的统一方法。I/O子系统定义了一组标准的I/O操作,所有设备驱动都需要遵从和支持。设备驱动程序负责将标准I/O操作API与特定设备的I/O操作相关联。I/O子系统为应用程序提供了一个设备抽象层(或称硬件抽象层)或虚拟化层。 典型的标准I/O操作API包括创建(create)、撤销(destroy)、打开(open)、关闭(close)、读(read)、写(write)和控制(ioctl)等。  create()在I/O子系统中建立指定I/O设备的一个虚拟化实例,使设备可用于读写等操作。创建设备实例时设备驱动将完成设备地址空间映射、中断号分配、ISR加载以及设备状态初始化等工作。调用create()者将得到设备实例的引用号。  destroy()从I/O子系统中撤销已建立的I/O设备实例,使之不再可用。调用destroy()时设备驱动程序将完成一系列清除设备映射的工作,并释放相应的内存空间。  open()为后续的读写操作做准备。设备创建后可能处于禁用(disabled)状态,open()将其置于可用(enabled)状态,并指定其使用模式,如只读、只写或接收控制命令等。由于create()和open()的功能存在重叠,某些系统中,两者被合二为一。  close()使打开的设备不可用。例如,使设备进入休眠态以节省能耗。close()与destroy()也可能合二为一。  read()和write()完成数据读写而无须理解设备操作的具体细节。使用时须指定读写数据量和内存地址。  ioctl()向设备发送控制命令,或设置设备驱动程序的运行参数。 I/O子系统通常维护I/O设备驱动表(I/O driver table)进行I/O标准API与特殊设备驱动函数的映射,供创建设备的虚拟实例时使用。 【例3.14】I/O设备驱动表如图3.14所示。表中第一行为I/O标准API名,第一列为特定设备类型的通用设备名,表中内容为相应设备的标准API函数指针。当I/O子系统进行设备加载时,函数指针被填入表中。■ 图3.14I/O设备驱动表 I/O子系统利用设备表建立设备与设备驱动之间的关联,记录设备的虚拟化实例。新创建的实例被分配一个唯一的名字并插入表中。 【例3.15】设备表及其与设备驱动表的关联如图3.15所示。设备表中,每个设备实例按通用设备名和实例号命名,记录该实例的一般信息,包括设备驱动以及数据结构。■ 图3.15设备表与设备驱动表的关联 3.2.6异常与中断管理 主流嵌入式处理器体系结构都提供打断正常执行流的异常和中断处理机制。异常和中断的作用包括内部错误或特殊状态管理、服务请求管理和硬件并发。嵌入式应用系统往往需要在程序执行发生错误时进行异常处理和恢复,避免出现停机。嵌入式处理器一般有普通和特权两种执行状态,某些指令只能在特权状态下执行,如禁止外部中断指令,否则产生异常。某些外设可以与系统处理器并行工作而无须过多干预,此时中断可以作为外设与处理器正在执行的应用之间的一种通信机制。当外设完成所分配的工作后,可通过中断通知处理器。这类外设包括间隔定时器和网卡等。 1. 异常与中断 一般CISC架构采用以中断为核心概念的体系,包括硬中断(外部中断)和软中断(内部中断,含指令异常和陷阱)。而RISC架构采用以异常为核心概念的体系,外设产生的异步事件称中断。本书参照RISC架构的描述方式。 异常是强制处理器进入特权状态执行指令的事件,包括同步异常和异步异常两类。内部事件产生同步异常,如执行读写指令产生内存对齐异常、除法指令异常等。外部事件产生异步异常,与指令执行无关,如外设的复位信号产生复位异常。嵌入式系统中的通信处理器模块收到数据包时也会产生异步异常。中断是外设事件产生的异步异常。异常与中断或同步异常与异步异常的差别在于产生异常的事件源,同步异常(简称异常)由处理器事件产生,异步异常(简称中断)由外设事件产生。异常分为精确和非精确两类,中断分为可屏蔽和不可屏蔽两种。 1) 精确异常 对精确异常,程序计数器(PC)准确指向造成异常的指令地址,处理器处理异常返回后将从此处重新执行。在现代处理器的指令和数据流水线中,按写指令的顺序而不是执行的顺序向处理器提交异常。处理器架构保证在异常指令之后进入流水线的指令不改变处理器状态。 2) 非精确异常 芯片厂商为了处理器性能,引入许多高级技术,包括推测指令和数据加载、指令和数据流水线化,以及cache机制等。例如,处理器可以按非顺序访存模式乱序执行浮点和整数访存操作。如果处理器采用多流水线或预取等技术,准确确定造成异常的指令和数据是不可能的,称为非精确异常。此时,PC值对异常处理没有意义。 可由软件禁止(阻塞)或允许的中断称可屏蔽中断,反之为不可屏蔽中断(nonmaskable interrupt,NMI)。硬件复位等不可屏蔽中断需要处理器立即响应和处理。许多处理器设有专门的NMI请求引脚。 可以屏蔽某个中断源的中断请求,此时即便中断请求到达,但处理器不予响应。也可以屏蔽某一优先级(含低于此优先级)的所有中断请求,甚至屏蔽所有中断源的中断请求(称为“关中断”)。进行中断屏蔽的可能原因包括降低中断响应负载、保证程序代码的可重入或需要进行原子操作(如保存现场)等。注意,异常不可以被屏蔽或禁止。 所有处理器按预定顺序(优先级)处理异常,一般NMI最高,其次是精确异常,然后是非精确异常,最后是可屏蔽中断。对应用和RTOS而言,异常和中断的优先级高于任务的优先级。可以允许高优先级异常抢占低优先级异常,称为中断嵌套。响应某中断请求时,低优先级或同优先级的中断请求被挂起。同一优先级可以有多个中断源,一般按FCFS规则进行处理,不允许相互抢占。 异常处理涉及异常的触发源、实现控制转换的硬件和确定异常向量存储位置的机制等。可编程中断控制器(PIC)协助处理器管理多个中断源,包括中断允许或禁止、分配优先级,并将最高优先级中断请求发往处理器。中断表罗列了所有中断相关信息。中断服务程序(ISR)也称中断向量。中断向量表保存所有异常处理程序和中断向量的入口地址。 【例3.16】中断表如表3.2所示,其中IRQ为PIC中断请求引脚编号,处理器据此识别中断源并执行相应的ISR。■ 表3.2中断表 中断源优先级向量地址IRQ最大频率描述 安全气囊传感器 最高14h8N/A安全气囊 刹车踏板传感器 高18h7N/A刹车系统处置 油量传感器中1Bh620Hz汽油量测量 实时钟低1Dh5100Hz时钟嘀嗒10ms/次 2. 异常响应过程 发生异常后,处理器进入异常处理程序前,按以下步骤执行。 (1) 保存当前执行流断点(NPC)和当前处理器状态。 (2) 关中断。 (3) 确定异常原因,将相应的异常处理程序入口地址赋给PC。 (4) 将执行流转移至异常处理程序入口并开始执行。 异常处理执行完成后,在返回被中断的执行流前,处理器按以下步骤执行。 (1) 恢复处理器状态。 (2) 开中断。 (3) 返回被中断的执行流,从其断点处恢复执行。 异常处理程序(ESR或ISR)完成以下工作。 (1) 切换至异常栈帧。 (2) 保存附加处理器状态信息,亦称保存现场。典型工作为将通用寄存器入栈。 (3) 屏蔽当前中断级,但允许更高优先级中断发生。 (4) 完成最少量的响应工作,而由相应的任务完成主要处理过程。 (5) 恢复现场。 对允许中断嵌套的系统,如果ISR是非可重入的,ISR中应禁止同优先级中断,也不应调用不可重入的API函数(如malloc()或printf()),还应避免自我阻塞或挂起行为。 外部事件为非累积式,即同一中断的后来者覆盖先前到达者。为了不丢失中断请求或影响任务调度,禁止中断的时间应尽量短。同时,中断响应的时间也应尽量短。事件处理有两种模式: 一种是在ISR中完成所有处理工作; 另一种是两阶段模式,即只在ISR中确认外设状态和服务请求,而将事件处理的主要工作交给相应的任务去执行完成。此时,ISR结束后将进行任务调度,选择合适的任务执行。两阶段模式,一方面减少了ISR的执行时间; 另一方面,由于中断优先级高于任务优先级,这种模式可以将低优先级工作交给低优先级任务完成,避免了在ISR中完成低优先级的工作,减少了对高优先级任务的不利影响。两阶段法提升了系统的并发性,其代价为延长了低优先级事件的响应时间。 μC/OSⅡ的两阶段中断与任务响应时序如图3.16所示。 图3.16两阶段中断与任务响应时序 (1) 任务响应时间。从中断到达到任务开始执行的时间。 (2) 中断延迟时间。从中断到达到处理器进入ISR之前的时长(latency)。 (3) 中断响应时间。从中断到达到ISR结束的持续时间,或等价于中断延迟时间。 (4) 中断恢复时间。ISR结束前恢复现场的时间。 其中,中断响应时间一般包括: ① 处理器确认中断原因并为相应的中断处理进行初始化所需的时间。 ② 如果处理器正在响应高优先级中断,低优先级中断被阻塞的时间。 ③ 中断禁止时间。如果中断被禁止,中断延迟亦相应延长。 ④ ISR保存中断上下文并响应中断,进行中断服务的时间。 由于中断禁止时间难以预测,最坏情况下可能导致中断响应时间无界。由于中断禁止主要用于保证数据共享的原子性,因此可以采用循环队列等技术,避免进行中断禁止。但必须注意,使用中断禁止的鲁棒性最高。 任务的响应时间包括调度器执行时间(称调度延迟)和任务上下文切换时间。 3. 伪中断 伪中断(spurious interrupt)是中断输入引脚上的一种持续时间非常短的信号。这种信号由信号的差错产生,可靠的系统设计需要对伪中断进行处理。 外设产生中断触发的信号有两种: 电平触发和边缘触发。数字信号一般为边缘触发,模拟信号为电平触发。使用电平触发时,通常需要设定信号的触发电平阈值。数字信号存在毛刺或抖动,需要进行消抖处理。模拟信号存在波动,需要进行滤波。 3.3RTOS主要性能指标和测试套 对应用开发而言,RTOS的平台选择非常关键,其选择应依据相应的功能和性能指标。主要包含如下量化参考指标。 (1) 支持的优先级数量。如64、128或256,以及是否支持同优先级。优先级数一般与所支持的并发任务数相关。 (2) 中断响应速度。即从中断产生到进入中断服务程序的时间。 (3) 上下文切换时间。即任务切换时间,典型值为1ms。 (4) 系统服务的执行时间上下界。 (5) 系统存储开销(footprint)。代码大小以及资源使用, 堆栈、ROM及RAM的占用。 RTOS的功能性定性指标如下。 (1) 使用的任务调度算法。如时间片轮转调度、加权轮转调度(weighted RR)、先入先出FCFS或优先级调度等。任务调度算法决定了任务响应时间的可分析性。 (2) 使用的资源管理协议。如是否支持防死锁(deadlock)和优先级反转(priority inversion)等提高系统可靠性的功能。 (3) 所支持的微处理器/微控制器、I/O设备、GUI。 (4) 调试功能。是否具有调试功能(尤其是多线程、多核)以及支持的调试工具。 (5) 授权方式及服务。是按产品型号计费、产品数量计费还是一次性授权。参考文档的完整程度,以及相应的技术支持。 高并发性是典型的嵌入式实时系统特征,应用任务与RTOS服务交错执行,如图3.17所示。RTOS是保证系统并发行为可预测性的关键,需要提供合适的调度、同步和通信机制,并进行可预测的中断管理。因此,需要采用包含系统运行时关键特征的测试套对RTOS的可预测性指标进行评估。 图3.17应用任务与OS交错执行 Rhealstone是实时操作系统最常用的测试套,可以度量如下6个关键指标(如图3.18所示)。 图3.18Rhealstone的指标示意图 (1) 任务切换时间tts。指在相同优先级的任务间进行一次上下文切换的时间。此时间由操作系统内核数据结构的性能决定。 (2) 任务抢占时间ttp。指高优先级任务在抢占当前任务执行前的等待时间,由任务切换时间、发现高优先级任务事件的时间和任务分发时间三部分组成。 (3) 中断延时时间til。指中断发生与开始执行ISR的间隔时间(示意图与ttp类似),由CPU识别中断的硬件延迟(delay)、完成当前指令的剩余时间、当前任务上下文保存时间三部分构成。 (4) 信号量混洗(semaphore shuffing)时间tss。指低优先级任务释放信号量,被其阻塞的高优先级任务获得此信号量并开始执行的间隔时间。 (5) 优先级翻转时间上界tup。设操作系统发现优先级翻转后,转而执行占有资源的任务T1的时间为t1,T1释放资源到被阻塞任务获得资源重新开始的时间为t2,则tup=t1+t2。 (6) 数据交换时间(datagram throughput time)tdt。指不使用共享内存或指针时,两个任务可以传输的数据量(KB)。用于度量消息传递原语的实现效率。 Rhealstone值为这6个指标的加权平均,如下式: Rhealstone Metric=a1tts+a2ttp+a3til+a4tss+a5tup+a6tdt 其中: a1,a2,…,a6为实验常数。 Rhealstone刻画了RTOS内核和硬件的组合特征,但其指标选择的合理性论证并不充分,且对如何评估调度器保证任务满足其截止期的能力没有考虑,如没有评估高优先级任务的阻塞时间或阻塞链是否有界等特征指标。RTOS系统服务执行时间是否有界,如内核可抢占度等其他指标也可作为选择时的参考。 3.4典型的RTOS RTOS是支持实时系统设计和运行的操作系统,其应用通常包括汽车引擎控制、轨道交通、工业机器人、飞行器控制系统等。实时操作系统一般提供抢占式调度机制,即重要的高优先级任务可以剥夺低优先级任务对CPU的使用权。当实时任务在等待所需资源而无法执行时,RTOS可以将其CPU的使用权释放给其他就绪的任务,从而降低系统的总体响应时间。通常RTOS提供以下典型功能和服务。 (1) 任务的创建、暂停、删除。 (2) 基于静态优先级(fixedpriority)的抢占式(preemptive)任务调度。 (3) 进程间通信(基于消息、消息邮箱、管道)。 (4) 基于信号量(semaphore)的进程间同步。 (5) 资源访问控制(并发控制与防止互锁)。 (6) 临界区(critical section)控制。 (7) 驱动程序的管理与接口。 (8) MMU内存管理、内存动态申请与分配。 (9) 其他功能,如GUI用户界面和支持TCP/IP网络。 μC/OSⅡ、FreeRTOS和RTEMS是常用的开放源码免费RTOS,相关资料比较多。VxWorks是商业的RTOS,安全性和可靠性高,用于航空航天、轨道交通和卫星等安全关键应用。以Linux为基础的RTOS应用也越来越多,它们支持复杂的文件、数据库、网络通信协议。 3.4.1μC/OSⅡ μC/OSⅡ的前身是μC/OS,最早于1992年由美国嵌入式系统专家设计开发,其当前版本为μC/OSⅢ。 μC/OSⅡ具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点,最小内核可以编译至2KB。μC/OSⅡ已经移植到了几乎所有知名的CPU上。虽然这款操作系统的源码开放,但用于商用目的时需要收费。 μC/OSⅡ也是在国内研究最为广泛的嵌入式实时操作系统之一。μC/OSⅡ最多可以支持 64个任务,系统保留了4个最高优先级的任务和4个最低优先级的任务,所有用户可以使用的任务数有56个。 3.4.2FreeRTOS FreeRTOS 是一款多任务、抢占式的实时嵌入式操作系统,可移植性好,具有可裁剪性,且内存占用较小等优势。FreeRTOS 主要的功能包括任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。FreeRTOS 操作系统可以被方便地移植到不同处理器上工作,现已提供了ARM、MSP430、AVR、PIC、C8051F等30多个不同硬件平台的移植工作,具有良好的可移植性。FreeRTOS占用内存资源少,代码量小。同少数其他实时操作系统(如μC/OSⅡ)一样,可以在小型单片机上运行。FreeRTOS 完全免费的操作系统,其最新版本为9.0.0版。 3.4.3RTEMS RTEMS(real time executive for missile/military/multiprocessor system)是一个开源的无版税实时嵌入操作系统。RTEMS最早应用于美国国防系统,现在由OAR公司负责版本的升级与维护,在航空航天、通信、军工和民用领域有着极为广泛的应用。 同大多数嵌入式操作系统一样, RTEMS采用微内核设计思想,将内核主要功能集成在一个小的执行体中,附加的功能在包裹内核层的外层实现,提供事件驱动基于优先级的可抢占调度,以及可选的单调速率(RM)调度方式。应用可以根据实际系统配置裁剪、链接相应的资源。 为了加速应用程序的开发,RTEMS提供了大量的符合RTEID(real time executive interface definition)接口、POSIX标准接口和ITRON架构接口的API。另外,RTEMS还移植了FreeBSD的TCP/IP协议栈,提供完善的网络应用接口。 RTEMS的内核既适用于紧耦合又适合于松耦合目标系统硬件的配置。此外,RTEMS提供简单和灵活的实时多处理器功能,支持由同构和异构混合的处理器组成的系统。虽然RTEMS提出了对多处理器进行支持的设计思路并对多处理器支持层的接口进行了定义,但是具体到某个系统,因为涉及系统结构的不同,多处理器通信层的实现需要根据实际的硬件结构进行设计。 3.5RTOS标准 通用操作系统标准强调功能性和可移植性,目的在于利于应用系统开发和维护。与之不同,RTOS主要应用于安全关键系统,需要有严格的标准以保证其功能和时间行为符合产品要求。 典型的RTOS标准包括POSIXRT标准、OSEK/VDX标准、AUTOSAR OS标准和ARINC 653标准。 3.5.1POSIXRT标准 POSIX(portable operating system interface based on UNIX operating system)主要关注不同UNIX操作系统中应用的源代码级移植问题。目前,在非UNIX系统中,POSIX也得到了广泛应用。 POSIX仅仅定义了操作系统服务的接口和这些服务的语义,并没有规定这些服务如何实现。为了实现源码级兼容,POSIX定义了操作系统需要提供哪些系统调用,以及这些调用的参数和调用的语义。 POSIX标准包含如下几部分。 (1) POSIX.1。系统接口和系统调用参数。 (2) POSIX.2。用户接口与工具。 (3) POSIX.3。验证符合POSIX的测试方法。 (4) POSIX.4。实时扩展。 其中,POSIX.4也被称为POSIXRT,是POSIX的实时扩展,强调系统行为的可预测性。其主要内容如下。 ① 调度算法。必须支持固定优先级抢占调度,偶发服务器调度。 ② 系统调用性能。指定了大多数操作系统服务的最坏执行时间要求。 ③ 优先级级数。最少32级。 ④ 定时器。支持周期和单次定时器(watchdog timer)、高分辨率定时、执行时间预算管理(时间测量和任务执行时间控制)。 ⑤ 实时文件系统。可选。可以提前在存储器(硬盘)上部署文件系统,使文件访问延迟(delay)可预测。 ⑥ 内存加锁。可选。定义了mlockall()锁定进程的所有页,mlock()锁定一部分页,mlockpage()锁定当前页,以及对应的释放函数。 ⑦ 虚存管理。包括取消指定实时任务采用虚存模式的服务。 ⑧ 多线程支持。强制支持线程。线程为实时应用的调度实体,可以有各自的时序约束,也可以指定一组线程的时间约束。 ⑨ 互斥、同步与通信。互斥同步基于优先级继承协议,等待和信号同步基于条件变量,数据共享基于共享内存对象,任务间通信基于优先级消息队列。 POSIXRT是实时系统中最成功的标准之一,众多商用内核都采用了这一标准。由于POSIX内容庞大,为了适应不同嵌入式系统的需求,POSIX.13分为如下不同实现版本。 (1) PSE51(minimal realtime system profile)。针对小微嵌入式系统。删除了许多通用操作系统的复杂功能。规定并发实体为线程,不支持进程。输入输出可以通过预定义的设备文件,但不采用完整的文件系统。实现此版本仅需几千行代码,内存空间几十KB。 (2) PSE52(realtime controller profile)。针对机器人控制等应用。与PSE51类似,可提升文件操作能力,包括创建和读写操作,使之成为一个简单文件系统。 (3) PSE53(dedicated realtime system profile)。针对航空电子等大型应用。基于多进程模型,并提供隔离保护。 (4) PSE54(multipurpose realtime system profile)。针对实时与非实时应用并存的系统。支持POSIX和POSIXRT的主要功能。 3.5.2OSEK/VDX标准 OSEK/VDX(open systems and the corresponding interfaces for automotive electronics/vehicle distributed eXecutive)为汽车电子分布式控制单元工业标准,主要目标是定义汽车软件的支撑环境,包括应用程序编程接口、实时操作系统和网络管理等内容。 无论是ECU内部通信,还是ECU之间的外部通信,都可以使用这一接口标准。ECU之间的交互层通信需要通过网络层和数据链路层。OSEK/VDX COM只定义了对这些层次的规格要求,其实现可以基于不同的网络协议。 典型的OSEK/VDX系统具有如下典型特征。 (1) 可扩展性。支持8位及以上位宽的众多处理器。不要求存储保护。 (2) 可移植性。采用ISO/ANSIC接口简化软件移植。未定义I/O接口标准。 (3) 可配置性。采用OIL语言(OSEK implementation language)实现系统服务和开销调整。 (4) 软件部件静态分配。编译时指定系统所需应用软件部件和操作系统部件,包括任务数、代码、所需资源和服务等。使系统可以在ROM上部署。 (5) 调试支持。通过ORTI(OSEK run time interface)提供任务跟踪和上下文交换时间等软件调试信息。 (6) 支持时间触发架构。提供OSEKTime OS定义,支持在OSEK/VDX框架中集成时间触发体系结构TTA。 3.5.3AUTOSAR OS标准 AUTOSAR(automotive open system architecture,汽车开放系统架构)联盟成立于2003年,主要成员包括BMW集团、大陆集团和西门子汽车电子公司等。联盟成立的主要目的是设计开发一套开放的汽车电子架构行业标准,该标准于2004年推出1.0版本并取名为AUTOSAR 1.0。 AUTOSAR基于OSEK/VDX定义了汽车软件架构标准。AUTOSAR OS对OSEK/VDX进行了多种扩展,包括内存保护、操作系统服务、截止期监视、执行时间监视、多核支持,以及实现TTA的调度表支持等。 据统计,当前一辆高档汽车配置了超过上百个ECU,车上软件的代码量已经超过了一千万行。未来的车辆功能需求(如高级自动驾驶)将向车辆引入具有高度复杂的计算资源要求,并且必须满足严格的完整性安全约束的新型汽车软件。这些软件实现的功能包括环境感知和行为规划,并将车辆与外部后端和交通基础设施系统相集成。为了适应外部环境的动态变化和自身功能更新,汽车软件在整个车辆的生命周期中都可能需要重构。AUTOSAR经典平台标准强调深嵌入式ECU的需求,却不能很好地满足上述新的要求,原有的AUTOSAR标准难以应用于智能汽车电子系统。因此,AUTOSAR联盟于2015年提出了新型系统架构AUTOSAR(adaptive platform,AP),并将原有的AUTOSAR重新命名为AUTOSAR CP(CP,classic platform)。第一个AP可用版本于2018年10月推出。AP对AUTOSAR原标准内容进行了大量更新,不再采用实时系统OSEK,而是采用了基于POSIX标准的操作系统,如Linux,使得AUTOSAR AP的应用程序发生了巨大变化。AP的应用程序拥有独立且巨大的虚拟地址空间,支持多线程、共享内存等高级特性,采用C++编程语言,以面向对象的思想进行开发,并且可使用所有标准的POSIX API。AUTOSAR AP主要提供高性能计算和通信机制,并且提供了灵活的软件配置方法,如软件远程更新(OTA)。对电气信号和汽车专用总线系统的访问等CP功能可以被集成到AP中。与CAN等传统汽车通信技术相比,一直增长的带宽需求使得汽车中引入了以太网,可以提供更高的带宽和网络交换功能,实现更加有效的长信息传输和点对点通信。 关于AUTOSAR架构的进一步讨论,参见9.8节。 3.5.4ARINC 653标准 航空电子系统在联合架构(federated architecture,FA)时代,飞行器上的每个数字化功能模块被部署到独立的硬件平台,各平台之间基于总线方式(如1553B)相互连接,从而形成一个功能完善的航空电子系统。FA系统本质上属于分布式架构,数据基于消息方式在模块之间传递,系统监控数据传输过程,并能及时处理传输错误。各模块之间耦合度较低,系统具有天然的故障包容能力,一个模块上发生的错误不会蔓延到另一个模块,模块之间鲜有数据结构的共享。 2000年以后,基于硬件平台共享机制的综合化航空电子(integrated modular avionics,IMA)技术开始崭露头角。与FA架构方案类似,IMA也采用分布式架构理念,各功能模块被部署在不同的逻辑分区,而这些逻辑分区之间共享同一硬件平台。与FA架构相比,IMA 的优势在于资源的整合及其优化配置。在FA架构中,每个航电模块配备独立的软硬件资源环境,随着硬件性能的增强,系统资源(如处理能力、存储空间、网络带宽)变得越来越富裕,但是富裕的资源并不能被有效利用,一定程度上造成了浪费。此外,为了提升系统可靠性,每个模块还配备了独立的冗余备份,进一步加大了资源的浪费,增加了系统重量。鉴于此,IMA 方案将这些独立的模块集中部署于同一硬件平台,之前各模块专属硬件平台的富裕资源可以用于其他功能模块,系统软硬件资源得以有效利用。同时,多个功能模块还可以共用冗余备份资源,进一步节省了硬件资源。 ARINC 653(avionics application standard software interface)为航空实时系统的事实软件规范,针对安全关键实时系统的实现、验证和执行,支持在相同硬件系统上部署多种应用软件。ARINC定义了IMA标准,为各个组件定义了相应的功能需求及接口标准,降低了硬件成本。IMA定义了分布式多处理器系统的操作系统接口标准(avionics application software standard interface),支持共享内存和网络通信。 APEX(application/executive)定义了应用与系统服务之间的接口规范以及调度机制,包括分区管理、进程管理、时间管理、分区间通信、分区内通信、健康管理等。使用这个标准能够使航空电子设备供应商和系统集成商在同一个硬件平台上部署多个航电应用模块,同时保持系统符合严格的航空电子安全标准,如 RTCA DO178C。 运行时,各个软件子系统占用独立的物理内存分区,采用静态循环调度。每个分区时序隔离,执行时间不能超过循环调度所分配的时间。每个分区包含一个或多个应用进程,采用固定优先级调度。调度点将检查是否发生错失截止期异常。 不同分区的进程通过消息进行通信,如图3.19所示。支持两种消息类型: 采样消息(sampling message)和排队消息(queuing message)。采样消息方式下,新消息覆盖旧消息; 排队消息基于FIFO队列。 图3.19APEX中的分区管理和分区间通信 分区内的进程基于传统机制进行通信,包括缓冲区、信号量、事件等。 ARINC 653的2010版由概述、服务、扩展服务、符合性测试、服务子集、性能6部分组成。其中服务部分定义了应用软件的基本操作环境,包括系统功能和57个服务的需求。6个主要功能为分区管理、进程管理、时间管理、分区内及分区间通信,以及健康监测,采用自然语言描述。服务需求即APEX接口,采用由自然语言和结构化语言组合而成的APEX服务规约语法描述。 STOP服务需求规约示例如下: procedure STOP (PROCESS_ID : in PROCESS_ID_TYPE; RETURN_CODE : out RETURN_CODE_TYPE) is error when (PROCESS_ID does not identify an existing process or identifies the current process)  RETURN_CODE := INVALID_PARAM; when (the state of the specified process is DORMANT)  RETURN_CODE := NO_ACTION; normal set the specified process state to DORMANT; if (current process is error handler and PROCESS_ID is process which the error handler preempted) then reset the partition’s LOCK_LEVEL counter (i.e., enable preemption); end if; if (specified process is waiting in a process queue) then remove the process from the process queue; end if; stop any time counters associated with the specified process; RETURN_CODE := NO_ERROR; end STOP; 3.6本 章 小 结 本章讨论了实时操作系统的体系结构和典型的系统服务。实时操作系统是实时嵌入式系统的关键组件,安全关键系统对其实时性和可靠性要求严苛。实时操作系统通常采用微内核架构,内核可以仅提供任务调度和时间管理等最小化核心服务,利于按需定制,更有利于核心代码的形式化验证。API服务执行时间有界是保证系统时序行为可预测性的必然要求,通常需要参考相应的工业标准进行严格的测试和验证。 思考题 1. 当前实时操作系统内核在支持关键控制应用方面主要存在哪些不足? 2. 为了满足时序行为可预测性的要求,实时操作系统内核需要具备哪些特征? 3. 简述μC/OSⅡ、FreeRTOS和RTEMS实时性的区别。 4. 实时操作系统一般采用哪些方法保证I/O行为的时间可预测性? 5. 描述实时操作系统支持事件触发和时间触发的执行机制。 6. 解释“资源同步”和“任务同步”的含义。