RTOS系统基本概念

时间:15-09-07 栏目:技术 作者:liva 评论:1 点击: 3,292 次

最近一直在从事智能硬件的工作,操作系统也是常用的RTOS,如FREERTOS,对于有些基本的概念还不是很清楚,下面一篇文章会简单介绍一下,讲的不错:

1 任务(task)

      实时操作系统任务的概念与我们通常所说的通用计算机操作系统不同,通用操作系统的任务是指提交给计算机的一项工作,一个任务可以包括多个进程,而一个进程 又可以包括多个线程。RTOS的任务是由计算机所执行的一项活动,也就是一段程序,该程序可以认为CPU完全只属于该程序自己,它大致等同于分时操作系统 中的进程(Process)的概念。

       实时应用程序的设计过程包括如何把问题划分成多个任务,每个任务都是整个应用的某一部分,它包括一段程序和与这个程序有关的数据及计算机资源(有它自己的一套CPU寄存器和堆栈空间等)。如图5所示。

 2013090322085846

图 5 多任务

图6给出了一个多任务系 统的内存分配。在内存里,操作系统本身以及各个任务都被指定有各自动堆栈区。有一个自由存储池被操作系统用来生成消息通道或用作公共数据区,供各个任务用 来交换数据。另外,还有供操作系统和所有任务共同使用的若干变量。注意:具体的内存分配情况与CPU紧密有关,而且与操作系统也有一些关系。

 2013090322101196

图 6 多任务的内存分配图

几个任务可以执行同一个 程序,但它们之间并无关系,因为它们使用各自动堆栈、各自的消息通道和其它资源。例如,一个实时系统有三个A/D转换器,可以生成三个任务,各使用一个 ADC,三个任务运行同一个程序,但被指定用于不同的资源(ADC),使用各自动堆栈区和各自动消息通道来将输入数据传送到其它任务去。这三个任务是独立 运行的,哪一个任务准备好,就运行哪一个任务。

由于若干任务使用同一个程序,因此在实时多任务系统中,程序必须是可重入的。除了用户程序的可重入性之外,还有内核的可重入性。

实时操作系统的内核启动后一般都会创建两个任务:根任务(root task)和空闲任务(idle task)。

根任务(root task)

根任务通常是内核启动后创建的第一个任务,再由它根据用户的需要创建其它一些任务。

空闲任务(idle task)

如果没有任务处于就绪队 列,空闲任务将被执行,也就是说,空闲任务是优先级最低的任务,总处于就绪队列的末尾,在处理器空闲时调度程序就自动运行它。空闲任务看起来与其它用户任 务一样,只是它是一个不作任何事情的循环,对上层软件开发者来说,可以完全不知道其存在,应用程序不能删除空闲任务。

A, 任务的特点

任务就是一个具有独立功能的无限循环的程序段的一次运行活动。具有动态性、并发性、异步独立性的特点。

1.动态性:

任务的状态是不断变化的,一般分为:休眠态(dormant), 就绪态(ready),运行态(running),挂起态(suspended)等。

2.并发性:

系统中同时存在多个任务,它们宏观上是同时运行的

3.异步独立性:

任务是系统中独立运行的基本单元,也是内核分配和调度的基本单元,每个任务各自按相互独立的不可预知的速度运行,走走停停。

每个任务都要按排一个决定其重要性的优先级,都有一个无限循环的程序段规定其功能(如一个C语言过程),并相应有一个数据段、堆栈段及一个任务控制块TCB(用于保存CPU的现场,状态等)。

B. 任务的状态

系统中的一个任务可以处于各种状态,最基本的状态有四种:运行(Executing),就绪(Ready),等待(waiting,通常又称为挂起,suspend),休眠(Dormant)。图7 显示了在一个任务中这几种状态之间的关系。

 2013090322102295

图 7 一个任务可能的状态迁移图

1.休眠:

一个休眠的任务是指没有被初始化的未被创建的任务,或任务的执行被终止的任务(任务删除),也可以认为是系统中不存在了的任务。操作系统一般不为处于休眠状态的任务分配TCB。

任务在它们被创建之前处于休眠状态。当它们被删除后,又重新回到休眠状态。可以说休眠状态是一个任务的起点和终点。

2.运行:

处于运行态的任务拥有CPU控制权并正在执行。任何时刻都只有一个任务处于运行态。

处于运行态的任务可以被中断打断,当中断发生时,转向中断处理程序(ISR),原来正在运行的任务暂时不能运行,就进入了中断状态。

3.就绪:

就绪状态的任务是指运行的一切条件都准备好马上就能运行的任务。例如,刚被创建的任务就处于就绪状态。但就绪态的任务要成为运行态,就必须比所有处于就绪态的任务的优先级高。

4.等待:

任务发生阻塞,被移到任务等待队列,等待系统实时事件的发生而唤醒。从而转为就绪或运行。

任务有激活和非激活两 种。非激活的任务是休眠态(dormant)的任务, 它不竞争CPU。激活的任务具有运行,挂起和就绪三种状态。每个激活的任务都需安排一优先级 (0-255), 具有唯一的任务标识号(1-最大任务数),应用的最大激活任务数需要在配置表中配置。调度程序根据优先级和引起重调度的系统调用将任务 由一个状态变为另一个状态。

内核为每个激活的任务分配一任务控制块(TCB)和任务堆栈,以保存任务在非运行状态时的任务状态信息即上下文。

任务可创建其它的任务。它们也可以删除、挂起、唤醒任务,查询任务的状态,改变它们自身或其任务的优先级。任务还可锁住调度使其他任务抢占它,以运行其关键的临界代码区。

C. 任务控制块与任务的上下文切换

任务控制块(TCB)用来描述任务,每一个任务都与一个TCB相关联。TCB包括了任务的当前状态、优先级、要等待的事件或资源、任务的程序代码的起始地址、初始堆栈指针、寄存器内容等信息。调度程序在任务状态切换时要用到这些信息。

在多任务系统中,上下文 切换(context switch)指的是当处理器的控制权由运行任务转移到另外一个就绪任务时所发生的事件序列。当前运行的任务转为就绪,挂起,或删 除时,另外一个被选定的就绪任务就成为当前任务。上下文切换包括保存当前任务的状态,决定哪个任务运行,恢复将要运行的那个任务的状态。保存和恢复上下文 是依赖于相关的处理器的。

任务切换过程增加了应用程序的额外负荷。CPU的内部寄存器越多,额外负荷就越重。做任务切换所需要的时间取决于CPU有多少寄存器要入栈。实时内核的性能不应该以每秒钟能做多少次任务切换来评价。

D. 实时嵌入式系统的任务划分原则

任务是代码运行的一个映 象,从系统的角度看,任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、I/O设备及内存空间等系统资源,并独立于其它任务,与它们一起并发 运行(宏观上如此)。操作系统内核通过一定的指示来进行任务的切换,这些指示都是来自对内核的系统调用。

在应用程序中,任务在表面上具有与普通函数相似的格式,但任务有着自己较明显的特点:

1. 任务具有任务初始化的起点(如获取一些系统对象的ID等);

2. 具有存放执行内容的私用数据区(如任务创建时明确定义的用户堆栈和堆栈);

3. 任务的主体结构表现为一个无限循环体或有明确的终止(任务不同于函数,无返回)。

在设计一个较为复杂的多任务应用时,进行的合理的任务划分对系统的运行效率、实时性和吞吐量影响极大。任务分解过细会引起任务频繁切换的开销增加,而任务分解不够彻底会造成原本可以并行的操作只能按顺序串行完成,从而减少了系统的吞吐量。

为了达到系统效率和吞吐量之间的平衡与折衷,在应用设计应遵循如下的任务分解规则(假设下述任务的发生都依赖于唯一的触发条件,如两个任务能够满足下面的条件之一,它们可以合理地分开):

1. 时间:两个任务所依赖的周期条件具有不同的频率和时间段;

2. 异步性:两个任务所依赖的条件没有相互的时间关系;

3. 优先级:两个任务所依赖的条件需要有不同的优先级;

4. 清晰性/可维护性:两个任务可以可在功能上或逻辑上互相分开。

从软件工程和面向对象的设计方法来看,各个模块(任务)间数据的通信量应该尽量小,并且最好少出现控制耦合(即一个任务可控制另一个任务的执行流程或功能),如非得出现,这应采取相应的措施(任务间通信)使他们实现同步或互斥。避免可能引起的临界资源冲突。

在设计一个复杂应用时,上面的任务分解原则仅能作一初步的参考,真正设计时还需要更多的实际分析和设计经验,才能使系统达到预定性能指标和效率。

2  互斥

实现任务间通信最简便的方法是使用共享数据结构。特别是当所有的任务都在一个单一地址空间下,能使用全程变量、指针、缓冲区、链表、循环缓冲区等,使用共享数据结构通信就更为容易。虽然共享数据区的方法简化了任务间的信息交互,但是必须保证每个任务在处理共享数据时的排它性,以避免竞争和数据的破坏。

两个或多个任务访问某些共享数据,其最后的执行结果取决于任务运行的精确时序,这称为竞争条件(race conditions)。调试包含有竞争条件的程序是一件很头痛的事情,大多数情况的运行结果都很好,但在极少数情况下发生了一些无法解释的奇怪现象。实际上凡是涉及到共享内存、共享文件,以及其它任何共享资源的情况都可能引发类似的错误。要避免这类错误,需要以某种手段确保当一个任务使用一个共享资源时,其它任务不能做同样的操作,这就是互斥(mutual exclusion)。

避免竞争条件的问题也可 以用一种抽象的方式进行描述,我们把对共享资源进行访问的程序片段称作临界区(critical region)或临界段 (critical section)。如果能够适当安排使多个任务不可能同时处于临界区,就可以避免竞争条件。尽管这样可以防止竞争条件,但它还不能保 证使用共享资源的并发任务能够正确和高效地进行操作。对于一个好的解决方案,需要满足以下四个条件:

1.任何两个任务不能同时处于临界区;

2.不应对CPU的速度和数目作任何假设;

3.临界区外的任务不得阻塞其它任务;

4.不得使任务在临界区外无休止地等待。

在单处理器环境下,多任务并发执行,实际上它们并不重叠,而是交替执行。与共享资源打交道时,使之满足互斥条件最一般的方法有:关中断、使用测试并置位指令、禁止做任务切换或利用信号量等。下面我们将分别进行介绍。

A. 关中断和开中断

处理共享资源时保证互斥,最简便快捷的方法是关中断和开中断。

中断被关闭后,时钟中断也被屏蔽了,CPU只有在发生时钟或其它中断时才会进行任务切换,关中断也就意味着CPU将不会被切换到其它任务,因此在访问共享资源时可以不用担心其它任务的可能介入。

可是,必须小心,关中断的时间不能太长,因为它会影响整个系统的中断响应时间,即中断延迟时间。

当改变或复制某几个变量的值时,应想到这种方法来做。这也是在中断服务子程序中处理共享变量或共享数据结构的唯一方法。在任何情况下,关中断的时间都要尽量短。

如果采用某种实时内核,一般来说,关中断地最长时间不超过内核本身的关中断时间,就不会影响系统中断延迟。当然得知道内核里中断关了多久。凡是好的实时内核,厂商都提供这方面的数据。

对于上层应用程序,一般我们不主张使用关中断的方法来实现互斥,至少它不应该作为通用的互斥机制。

需要注意的是:该方法不适用于多处理器环境。

B. 测试并置位

如果不使用实时内核,当 两个任务共享一个资源时,一定要约定好,先测试某一个全局变量,如果该变量为0,则允许该任务A与共享资源打交道。为防止另一个任务B也要使用该资源,前 者只要简单地将全局变量置为1,这通常称为测试并置位(Test-And-Set,简称为TAS)。

TAS操作可能是微处理器的单独一条不会被中断地指令,或者是在程序关中断做TAS操作再开中断。有些微处理器有硬件的TAS指令,如Motorola的68000系列。

这种机制通常存在一些较为严重的缺点,例如:一般使用了忙等待策略这会消耗处理器时间,而且还可能会出现饿死和死锁问题。

在实际应用中,这种方法使用并不多。

C. 禁止,然后允许任务切换

如果任务不与中断服务子 程序共享变量或数据结构,可以禁止、然后允许任务切换。注意,此时虽然任务切换被禁止了,但中断还是开着的。如果这时中断来了,中断服务子程序会在这一临 界区内立即执行。中断服务子程序结束时,尽管可能有优先级高的任务已经进入就绪态,内核还是返回到原来被中断了的任务。直到执行完给任务切换开锁函数,内 核再查看有没有优先级更高的任务被中断服务子程序激活而进入进入就绪态,如果有,则做任务切换。

虽然这种方法是可行的,但应尽量避免禁止任务切换之类的操作,因为内核最主要的功能就是任务的调度与协调。禁止任务切换显然与内核的初衷相违背。

D. 信号量(semaphore)

信号量是1965年Edgser Dijkstra提出的一种方法。信号量实际上是一种约定机制。在多任务内核中普遍使用信号量用于:

控制共享资源的使用权(满足互斥条件);

标志某事件的发生;

使两个任务的行为同步。

信号量的操作有两种:P 和V。对一个信号量进行P操作就是检查其值是否大于0,若是,则将其值减1并继续执行;否则当前任务将被阻塞,而且此时P操作并没有结束。检查数值、改变 数值,以及可能发生的任务阻塞操作均作为一个单一的、不可分割的原子操作(atomic action)完成。即保证一旦一个信号量操作开始,则在操作完 成或阻塞之前,别的任务均不允许访问该信号量。与P操作相对应,对一个信号量进行V操作就是递增信号量的值(它同样是一个不可分割的原子操作),如果一个 或多个任务正因为该信号量而阻塞,无法完成一个先前的P操作,则由操作系统选择其中的一个(例如,随机挑选或选择优先级最高的任务),并允许其完成它的V 操作。

二进制信号量(binary semaphore)是一种经常使用的特殊信号量,它是只有两个值(0和1)的变量。二进制信号量象是一把钥匙,任务要运行下去,得先拿到这把钥匙。如果信号量已被任务占用,该任务只得挂起,直到信号量被当前使用者释放。

大多数实时操作系统都不允许在中断处理程序中进行P操作(如果允许P操作,一般也要求调用后立即返回,不能等待,即不允许发生阻塞),但一般允许V操作。

信号量常被用过了头。处 理简单的共享变量也使用信号量则是多余的。请求和释放信号量的过程是要花相当的时间的。有时这种额外的负荷是不必要的。用户可能只需要关中断、开中断来处 理简单的共享变量,以提高效率。然而如果关中断时间长了会影响中断延迟时间,就有必要使用信号量了。

3 函数的可重入性

可重入型(Reentrancy)函数可以被一个以上的任务调用,而不必担心数据的破坏。可重入型函数任何时候都可以被中断,一段时间以后又可以运行,而相应数据不会丢失。

      可重入型函数或者只使用局部变量,即变量保存在寄存器或堆栈中。如果使用全局变量,则要对全局变量予以保护。

应用程序中的不可重入型函数引起的错误很可能在测试时发现不了,直到产品到了现场问题出现。在使用不可重入型函数时一定要小心。

4 同步

如果各个任务是独立运行的,则它们之间就不存在同步问题,但实时系统中,通常几个任务总是协同工作,需要在确定的时间里执行各自的功能,这就产生同步问题。

对于单个的任务而言,所谓同步就是使它能在指定的时间执行。实时操作系统都提供时钟功能,一个任务可以通过系统调用来使自己挂起一段时间或者挂起到某一指定的时刻。

通常我们所谓的任务同步,主要是指两个或两个以上的任务需要协调执行的情况。实现同步主要有两种方式:信号量和事件。

A. 用信号量实现同步

可以利用信号量使某任务与中断服务程序同步(或者是与另一个任务同步,这两个任务间没有数据交换)。与实现互斥功能的信号量(类似于一把钥匙)不同,完成同步功能的信号量类似于通行标志。

同步可以分为单向同步 (unilateral rendezvous)和双向同步(bilateral rendezvous)。单向同步,例如,一个任务做I/O操作,然后 等待信号回应,当I/O操作完成,中断服务程序(或另外一个任务)发出信号,该任务得到信号后,继续执行。

如果内核支持计数信号量,信号量的值表示尚未得到处理的事件数。请注意,可能会有一个以上的任务在等待同一事件的发生,则这种情况下内核会根据以下原则之一发信号给相应的任务:

发信号给等待事件发生的任务中优先级最高的任务;

      发信号给最先开始等待事件发生的那个任务;

根据不同的应用,发信号以标识事件发生的中断服务或任务也可以是多个。

两个任务可以使用两个信号量同步它们的行为,这称为双向同步。双向同步类似于单向同步,只是两个任务要相互同步。

注意:在任务与中断服务之间不能使用双向同步,因为在中断服务中不能等待一个信号量。

B. 事件(event)

当某任务要与多个事件同 步时,要使用事件标志(event flag)。若任务需要与任何事件之一发生同步,可称为独立型同步 (disjunctive synchronization,即逻辑或关系)。任务也可以和若干事件都发生了同步,称为关联型同步 (conjunctive synchronization,逻辑与关系)。独立型同步和关联型同步如图8所示。

2013090322103801

图 8 独立型和关联型同步

可以用多个事件的组合发 信号给多个任务,典型地,8个、16个或32个事件可以组合在一起,取决于所使用的内核。每个事件对应其中一位。任务或中断服务程序可以给某一位置置位或 复位,当任务所需的事件都发生了,这个任务继续执行。至于哪个任务该继续执行,是在一组新的事件发生时确定的,也就是在事件位置位时做判断。

5 任务间的通信

有时很需要任务间的通信和中断服务与任务间的通信,这种信息传递被称为任务间的通信。任务间通信主要有两种途径:通过共享数据结构或发消息给另一个任务。任务间的通信主要涉及通信机制的选择与实现、临界区域的保护以及死锁的预防等问题。

A. 共享数据结构

在多任务系统中,共享内 存是任务间通信最简单、最迅速的方法。特别是在实时操作系统环境下,高优先级任务与低优先级的任务共享同一块内存时,经常会造成共享数据的冲突。因此,在 设计任务间的通信时必须避免共享数据冲突。使用共享数据结构的缺点是,通常一个任务不知道共享数据结构何时被中断服务程序或其它任务修改了,除非采取同步 措施,或者它以查询发生周期性查询该变量的值,如果要避免这种情况,可以考虑使用邮箱(mail box)或消息队列(message queue)。

共享数据结构最简单的实现方式就是全局变量。使用全程变量时,必须保证每个任务或中断服务程序独占该变量。前面已经提到中断服务中保证独占地唯一方法就是关中断。如果两个任务共享某个全局变量,可以采用前面介绍的关中断或信号量等方法。

如果要用共享内存实现较大数据的传送,可能需要考虑采用特殊的缓冲区数据结构来避免共享数据冲突。

在传送与时间相关的数据时(例如数据处理速度大于数据的输入速度),一般采用“乒乓”缓冲结构。它由两块缓冲区构成,通过硬件或软件来控制两个缓冲区间的切换。其典型应用有磁盘控制器、图形接口卡等设备。

环缓冲结构类似于 FIFO表,但它比FIFO表易于管理。在环缓冲结构中,并发的输入和输出可用通过头尾指针来控制。数据从尾指针处写入,从头指针处读出。环缓冲结构与信 号量一起使用可以控制资源的并发使用。例如在访问内存、打印机等资源时,可以将资源的请求置于尾指针指向的存储区,资源分配程序从头指针中取出数据后按照 请求分配资源。

B. 消息邮箱

邮箱是大多数多任务操作 系统任务间通信的一种方式。它是公认的一块内存区域,由一个集中调度者来控制各任务对其的读写,从而实现任务间传递数据的目的。任务可以通过post操作 来写这块内存,或通过pend操作来读取这块内存的数据。这种pend操作与简单轮询邮箱的区别在于:前者在等待数据时处于挂起(suspend)状态, 不占用任何CPU资源;后者则是占用CPU,不停地检查邮箱。邮箱传递的数据一般是一个标志(flag)、单个数据,或者是指向链表或队列的指针。在具体 实现时,数据一旦从邮箱读出来,邮箱就置成空状态。这样,尽管有多个任务能对同一个邮箱执行pend操作,但只有一个任务能从邮箱中取出数据。

在基于任务控制块(TCB)模型的任务管理系统中,邮箱是最容易实现的。在这种模型中,一般都有一个监管任务和两个列表(任务资源列表和资源状态列表),任务资源列表和资源状态列表保持协调一致。

当超级任务被系统调用或硬件中断激活后,它首先检查是否有任务在邮箱中处于pend状态。如果邮箱中数据就绪就重启该任务。类似地,如果某任务已执行post操作,操作系统则确保数据置于邮箱中,并更新其状态。

邮箱除了上述的post和pend操作外,还可以有accept操作。accept操作允许任务在邮箱数据就绪的情况下可立即读出数据,否则返回错误代码。此外,在邮箱的pend操作中还可以添加超时控制来防止死锁。

C. 消息队列

      队列可以认为是由许多邮箱排列而成,因而可以由上述相同的资源表来实现。其操作相应地有qpost操作、qpend操作和qaccept操作。发送和接收 消息的任务约定,队列所传递的数据也应该是指针,而不是数组。通常,先进入消息队列的消息先传递给任务,即先进先出原则(FIFO),有些实时内核也支持 后进先出(LIFO)的方式。

6 中断处理

中断是一种硬件机制,用 于通知CPU有个异步事件发生了。异步事件是指无一定时序关系的随机发生的事件。如外部设备完成数据传输,实时控制设备出现异常情况等。中断一旦被识 别,CPU就保存部分(或全部)上下文,即部分或全部寄存器值,跳转到专门的子程序,称为中断服务子程序(ISR)。中断服务子程序做事件处理,处理完成 后,程序回到:

在前后台系统中,程序回到后台程序;

对非抢占内核,程序回到被中断地任务;

对抢占内核,让进入就绪态的优先级最高的任务开始运行。

中断使得CPU可以在事 件发生时才予以处理,而不必让微处理器连续不断地查询(polling)是否有事件发生。通过两条特殊指令:关中断 (disable interrupt)和开中断(enable interrupt)可以让微处理器不响应或响应中断。在实时环境下,关中断的时间应尽 可能的短,因为关中断影响中断延迟时间,关中断时间太长可能会引起中断丢失。微处理器一般允许中断嵌套,也就是说,在中断服务期间,微处理器可以识别另一 个更重要的中断,并服务于那个更重要的中断。

2013090322095625

图 9 中断嵌套

中断延迟

可能实时内核最重要的指标就是中断关了多长时间,所有实时系统在进入临界区代码段之前都要关中断,执行完临界代码之后再开中断。中断延迟由以下表达式给出:

     中断延迟 = 关中断地最长时间 + 开始执行中断服务子程序的第一条指令的时间

     关中断是实时内核最重要的指标之一,它直接影响应用程序对实时事件的响应速度。关中断的时间很大程度取决于微处理器的架构以及编译器所生成的代码质量。

中断响应

        中断响应定义为从中断发生到开始执行用户的中断服务子程序代码来处理这个中断的时间。中断响应时间包括开始处理这个中断前的全部开销。典型地,执行用户代码之前要保护现场,将CPU的各寄存器存入堆栈。这段时间将被记作中断响应时间。

对于前后台系统,保存寄存器以后立即执行用户代码,中断响应时间为:

中断响应时间 = 中断延迟 + 保存CPU内部寄存器的时间

对于非抢占内核,微处理器保存内部寄存器以后,用户的中断服务子程序全部立即得到执行。非抢占内核的重点响应时间为:

中断响应时间 = 中断延迟 + 保存CPU内部寄存器的时间

对于抢占内核,则要先调用一个特定的函数,该函数通知内核即将进行中断服务,使得内核可以跟踪中断的嵌套。抢占内核的中断响应时间为:

中断响应时间 = 中断延迟 + 保存CPU内部寄存器的时间

+ 内核进入中断服务函数的执行时间

中断响应时间是系统在最坏情况下的响应中断的时间,例如某系统100次中有99次在50微秒内响应中断,只有一次响应中断的时间是250微秒,则只能认为中断响应时间是250微秒。

中断恢复时间(interrupt recovery)

中断恢复时间是微处理器返回到被中断了大程序代码所需要的时间。在前后台系统中,中断恢复时间很简单,只包括恢复CPU内部寄存器值得时间和执行中断返回指令的时间。中断恢复时间为:

中断恢复时间 = 恢复CPU内部寄存器值的时间 + 执行中断返回指令的时间

和前后台系统一样,非抢占内核的恢复时间也很简单,只包括恢复CPU内部寄存器值的时间和执行中断返回指令的时间。

对于抢占内核,中断恢复 要复杂一些。典型地,在中断服务子程序的末尾,要调用一个由实时内核提供的函数,用于判断是否脱离了所有的中断嵌套。如果脱离了嵌套(即已经返回到被中断 了大任务级时),内核要判断,由于中断服务子程序的执行,是否使得一个优先级更高的任务进入了就绪态。如果是,则要让这个优先级更高的任务开始运行。在这 种情况下,被中断了大任务只有重新成为优先级最高的任务而进入就绪态时才能继续运行。对于抢占内核,中断恢复时间为:

中断恢复时间 = 判断是否有优先级更高的任务进入了就绪态的时间

+恢复那个优先级更高任务的CPU内部寄存器值的时间

+ 执行中断返回指令的时间

图10至12分别给出了前后台系统、非抢占内核和抢占内核相应的中断延迟、响应和恢复过程。

 2013090322101451

图 10 前后台系统的中断延迟、响应和恢复过程

 2013090322102404

图 11 非抢占内核的中断延迟、响应和恢复过程

 2013090322103321

图 12 抢占内核的中断延迟、响应和恢复过程

注意:抢占内核的中断返回函数将决定是返回到被中断的任务,还是由于中断服务程序使优先级更高的任务进入就绪状态而让最高优先级的任务运行。在后一种情况下,恢复中断的时间要稍一些,因为内核要做任务切换。

中断处理时间

虽然中断服务的处理时间 应尽可能的短,但对处理时间并没有绝对的限制。不能说中断服务必须全部小于100微秒,500微秒或1微秒。如果中断服务是在任何给定的时间开始,且中断 服务程序代码是应用程序中最重要的代码,则中断服务需要多长时间就应该给它多长时间。然而在大多数情况下,中断服务子程序应识别中断来源,从产生中断地设 备取得数据或状态,并通知真正做该事件处理的那个任务。当然应该考虑到是否通知一个任务去做事件处理所花的时间比处理这个事件所花的时间还多。在中断服务 中通知一个任务做事件处理(通过信号量或信息队列等)是需要一定时间的,如果事件处理需花的时间短于给一个任务发通知的时间,就应该考虑在中断服务子程序 中做事件处理并在中断服务子程序中开中断,以允许优先级更高的中断进入并优先得到服务。

7 非屏蔽中断(NMI)

有时,中断服务必须来得 尽可能地块,内核引起的延时变得不可忍受。在这种情况下,可以使用非屏蔽中断(non-maskable interrupt),绝大多数微处理器有非屏 蔽中断功能。通常非屏蔽中断留做紧急处理用,如断电时保存重要的信息。然而,如果应用程序没有这方面的要求,非屏蔽中断可用于时间要求最苛刻的中断服务。

在非屏蔽中断的中断服务子程序中,不能使用内核提供的服务,因为非屏蔽中断是关不掉的,故不能在非屏蔽中断中处理临界区代码。

8 时钟节拍(clock tick)

在实时系统中, 一般不能缺少实时时钟,它是实时软件运行的必不可少的硬件设施。实时时钟单纯地提供一个规则的脉冲序列,脉冲之间的间隔可以作为系统的时间基准称为时基,时基的大小代表了实时时钟的精度,这个精度取决于系统的要求。

为了计准时间间隔,一个 很重要的问题是CPU与时钟应同步工作。同步的方法可以用硬件,也可以用软件。软件方法是使CPU能用程序启动、停止时钟工作,设置时基的大小,并在启动 后,利用实时时钟中断信号的方法来对准系统的时钟。每当实时时钟的时基到时,它就引起中断,中断响应后实时时钟又开始工作,时基到时又引起中断,这样达到 与CPU的同步。显然,软件方法具有简单、灵活、易实现和低成本的优点,可以很方便修改实时时钟的设置和系统时间的表示,且可以在不增加硬件的基础上非常 灵活地用软件模拟多个“软时钟”,因此,在实时系统中广泛采用此种方法。但由于中断的延迟,对系统的时钟可能会造成一定的误差,因此在设计中通常将实时时 钟中断的优先级设置的很高,一般仅次于非屏蔽中断。系统的时间精度要求的越高,时钟中断的频度就越高,这样执行时钟ISR的时间就会增多,系统的开销就会 增大,就会影响系统的其他的工作,因此,应使时钟ISR程序竟尽可能的短,同时要考虑时间精度。

硬件所做的工作仅仅是按已知时间间隔产生时钟中断。其它与时间有关的工作都必须由软件驱动程序来完成。不同操作系统的时钟驱动程序完成的功能可能不同,但一般包括如下内容:

1.维护日期时间;

2.防止任务的运行时间超过其允许的时间;

3.对CPU的使用情况进行统计;

4.处理系统或用户程序提出的定时服务;

时钟节拍是特定的周期性 中断。这个中断可以看作是系统心脏的脉动。中断之间的时间间隔取决于不同的应用,一般在10毫秒到200毫秒之间。时钟的节拍式中断使得内核可以将任务延 时若干个整数时钟节拍,以及当任务等待事件发生时,提供等待超时的依据。时钟节拍频率越快,系统的额外开销就越大。时钟节拍的实际频率取决于用户应用程序 的精度。

各种实时内核都有将任务延时若干个时钟节拍的功能。然而这并不意味着延时的精度是一个时钟节拍,只是在每个时钟节拍中断到来时对任务延时做一次裁决而已。

上述情况在所有的实时内核中都会出现,这与CPU负荷有关,也可能与系统设计不正确有关。以下是这类问题可能的解决方案:

1.增加微处理器的时钟频率;

2.增加时钟节拍的频率;

3.重新安排任务的优先级;

4.避免使用浮点运算(如果非使用不可,尽量使用单精度数);

5.使用能较好地优化程序代码的编译器;

6.时间要求苛刻的代码用汇编语言编写;

7.如果可能,用同一家族的更快的微处理器做系统升级。如从8086向80186升级,从68000向68020升级等。

不管怎么样,抖动总是存在的。

9 死锁

A. 死锁的产生

死锁的定义:

若一个进程集合中的每一个进程都在等待只能由本集合中的另一个进程才能引发的事件,则这种情况被视为死锁(deadlock)。

由于所有的进程都在等待,所以没有一个进程能够触发那个(些)能够唤醒本集合中另一个进程的事件,于是所有的进程都将永远地等待下去。

多数情况下,进程是等待本集合中另一个进程释放的资源。换句话说,就是每个进程都在等待另一个进程所占有的资源。但因为所有进程都无法运行,因而无法释放资源,于是所有进程都不能被唤醒。至于进程数及申请的资源数并不重要。

当多个任务竞争同样的两 个或多个临界资源时,就可能会出现死锁。在实时多任务操作系统环境下,互斥、循环等待、占有等待、禁止抢占都有可能产生死锁。死锁在多任务操作系统中是个 很严重的问题,往往不可能靠测试来发现和消除。由于死锁出现的概率很小,很难发现,解决死锁也往往要追溯前因后果。

当任务在所分配的时隙内因得不到所需的资源而不能完成任务处理时,就会出现“饥荒”。饥荒与死锁的不同之处在于:饥荒至少有一个任务能利用其所需的资源,但是其它任务则得不到资源;而在死锁的情况下,所有的任务都因得不到所需的资源而被迫处于阻塞状态。

Coffman等人1971年总结出了死锁发生的四个条件:

1.互斥(mutual exclusion)条件,每个资源或者被分配给一个进程或者空闲,即不可共享;

2.保持和等待(hold and wait)条件,已分配到了一些资源的进程可以申请新的资源;

3.非剥夺(no preemption)条件,已分配给一进程的资源不可被剥夺,只能被占有它的进程释放;

4.循环等待(circular wait)条件,系统必然有一条由两个或两个以上的进程组成的循环链,链中的每一个进程都在等待相邻进程占用的资源。

以上四个条件是死锁发生的必要条件,只要一条或多条不成立,死锁就不会发生。

B. 死锁的处理

处理死锁主要有四种策略:

1.忽略该问题;

最简单的解决死锁的问题 是对死锁视而不见,首先要了解死锁发生的频率、系统因其它原因崩溃的频率、以及死锁有多严重,如果死锁平均每50年发生一次,而系统每个月会因硬件故障或 操作系统错误等而崩溃一次,那么就可以不用不惜工本地去消除死锁。另外,可能解决死锁的代价太大,忽略它也是在方便性和正确性之间的折中考虑。

例如系统中进程的数目受 有多少进程表项(PCB)的制约,如果一个fork调用由于当前没有空闲PCB而失败,那么一种合理的办法是等待一段随机的时间后重试,但这有可能是个无 休止的循环,这实际上就是死锁。虽然发生这类事件的可能概率很小,但它的确是存在的。UNIX解决这类问题的方法就是忽略它。

这种方法在我们实际应用中比较常见,例如内存控制块(MCB)、任务控制块(TCB),定时器控制块(TCB)、进程控制块(PCB)等的管理我们都是采用这种处理方法,具体配置情况一般根据经验在调试过程中不断调整得出的,与具体的系统环境有关。

2.检测死锁并恢复;

利用资源分配图检查是否存在环路,可以分析给定的申请/释放序列是否将导致死锁。系统监视资源的申请和释放情况,每次资源被申请或释放时立即刷新资源分配图,检测释放存在环路,如果存在,则撤销环路中的一个进程,如果仍不能破除死锁,则撤销再另一个进程,直到环路被破坏。

更为简单的一种处理方法是不维护资源分配图,而是周期性检测进程是否连续阻塞超过一定时间,如1小时。一旦发现这样的进程则将其撤销。

撤销一个进程时必须同时消除可能导致的所有副作用。

实时系统中一般很少因为资源不可用而将进程中途终止并重新执行,因为有些操作是不能重复进行的,例如文件的更新。

进程间通信,尤其是板间通信时,我们经常采用超时机制(根据情况重发或者其它处理)来消除进程可能发生的死锁。

3.谨慎地对资源进行动态分配,避免死锁;

死锁避免不是通过对进程随意强加一些规则,而是通过对每一次资源申请进行认真的分析来判断它是否能安全地分配,条件是必须事先获得一些特定的信息。

“银行家算法”最早由 Dijkstra于1965年发表。其原理类似于一个小银行的存取款过程,将进程比作客户,资源比作贷款,操作系统比作银行家。这种算法能确保分配给所有 任务的资源都不可能超过系统可用资源。这样就可以预留一部分可用资源来满足其它任务的需求。遗憾的是,这种算法的实时性不是很好,并且任务所需的资源的优 先级往往是未知的。

4.通过破坏产生死锁的四个必要条件来预防死锁发生。

对于某些不可共享的资源,必须采用互斥保护措施。当然,也可以通过其它技术手段来使得这些资源变为可共享资源,这时就可以去掉互斥的保护措施,前面已有详细介绍。

通常我们采用带有超时控制的信号灯。这样,信号灯在超时后不再保护临界资源,临界资源可以被其它任务使用,从而化解了可能的死锁。目前,内核大多允许用户在申请信号量时定义等待超时。

当某一任务占用资源A并 申请资源B,另一任务占用资源B并申请资源A时,就会出现循环等待。一个消除循环等待行之有效的方法就是:强加给资源一个次序,并且迫使所有的任务在申请 资源时必须以递增的次序。例如,设计如下的资源次序:磁盘(1)、打印机(2)、监视器(3)。如果某个任务希望使用打印机和监视器,它就必须先申请打印 机,然后申请监视器。可以证明,采用这种方案可以消除死锁,遗憾的是几乎找不出一种使每个人都满意的编号次序,由于潜在的资源以及各种不同用途的数目,以 至于使编号根本无法使用。

当任务申请到某一可用的 资源,并且在它能够申请到另一可用的资源之前,一直不释放前一个资源时,就会出现占有等待。一个可行的解决办法就是,在同一时间分配给任务所有需要(包括 潜在需要)的资源,这样有可能延长响应时间,甚至有可能导致其它任务产生饥荒。另一个办法就是决不允许任何任务在同一时刻锁住多个资源。

最后,禁止抢占也会导致 死锁。也就是说,如果一个低优先级的任务占有信号灯保护的某一资源,另一个高优先级的任务中断低优先级的任务的运行,并处于等待该信号灯状态时,由于低优 先级的任务不可能释放其信号灯,这样高优先级的任务将一直等待下去。这就是所谓的“优先级逆转”。如果我们允许高优先级的任务能够抢占低优先级任务,就不 会出现死锁。然而,这样也可能导致低优先级任务“饥荒”以及其它干扰问题,例如I/O操作问题。

 

声明: 本文由( liva )原创编译,转载请保留链接: RTOS系统基本概念

RTOS系统基本概念:目前有1 条留言

  1. 沙发
    吞拿鱼 China Google Chrome Windows :

    记录生活工作点滴的同时还满足部分人的需求,赞一个!

    2015-09-09 下午 12:53 [回复]

发表评论


购物推荐

赞助商

© 2013 enjoydiy.com. Design by zijiao. 59 queries in 0.241 seconds, using 21.38MB memory