第2章 嵌入式操作系统
嵌入式操作系统是嵌入式软件的重要组成部分,在嵌入式操作系统的支持下开发嵌入式软件,不仅极大地减少了开发的工作量,而且提高了嵌入式应用软件的可移植性。本章首先介绍嵌入式操作系统的基本概念;然后讨论嵌入式操作系统的基本功能和多内核嵌入式操作系统原理;最后介绍几种比较有代表性的嵌入式操作系统。
2.1 嵌入式操作系统概述
嵌入式操作系统是一种用于支持嵌入式应用程序的操作系统。它是一个介于应用程序和嵌入式计算机系统硬件之间的程序集合,其任务是控制和管理嵌入式系统中的硬件和软件资源,以方便嵌入式系统的使用者。
2.1.1 为什么要使用嵌入式操作系统
并不是所有的嵌入式系统上面都需要有一个嵌入式操作系统。一些本身功能很简单的嵌入式系统(如一个普通的空调控制器)出于效率和成本考虑,通常不使用嵌入式操作系统。但当一个嵌入式系统软件的复杂程度达到一定的水平,其硬件又具备充足的处理能力时,使用嵌入式操作系统就是理所当然的选择。一般说来,当一个嵌入式系统有以下需求时就要使用嵌入式操作系统。
1. 系统需要运行多个任务
一些功能简单的嵌入式系统只需要运行几个固定的任务,可以由应用任务自己来管理硬件,完成相互之间的协调工作。但是当嵌入式系统需要运行很多任务,并且任务之间的关系很复杂时,还由应用任务自己来负责管理和协调工作,就会给应用程序的开发者带来很大麻烦,软件出现错误的概率也会显著增加。如果把管理方面的工作统统交给操作系统来完成,就可以避免出现这些问题。
2. 系统需要直观的用户界面
一些嵌入式系统并不是在启动之后就默默地去运行,它们需要同用户进行交互,最好能通过窗口式的图形用户界面进行这种交互。这就要用到窗口系统,而窗口系统离不开操作系统的支持。
3. 系统需要有网络功能
在没有操作系统的情况下,支持网络功能并不是不可能的事情。比如,TCP/IP协议就可以用硬件芯片来实现。但使用这样的芯片会增加成本,并且网络协议会不断升级,用硬件芯片则无法同步升级。而在有操作系统的嵌入式计算机系统中,可以根据需要定制网络协议,以适应各种网络环境的需求,也便于跟上网络协议更新的步伐。
4. 系统需要用到数据库管理系统
一些移动信息设备需要使用移动数据库管理系统解决移动计算环境下的数据管理问题。比如,掌上计算机、PDA、车载设备、智能手机等就经常有此方面的需求。还有一些应用问题需要利用嵌入式实时数据库管理系统进行实时数据的采集和处理。无论是移动数据库管理系统还是嵌入式实时数据库管理系统都离不开操作系统的支持。
5. 系统需要不断进行二次开发
如果要经常进行二次开发工作,使用嵌入式操作系统是一个明智的选择。嵌入式操作系统提供一系列供开发人员使用的API接口。在这些接口之上进行开发,可以免去很多繁琐的底层开发工作,这不仅提高了嵌入式系统的开发效率,而且也提高了嵌入式应用软件的可移植性。
2.1.2 嵌入式操作系统与实时操作系统
在定义什么是实时操作系统之前,需要先明确什么是实时系统。简单地讲,一个实时系统是能满足以下要求的系统:当外部事件到来时,计算机能立即进行处理,使得在指定的时间内能完成对事件的处理。而且外部事件到来的时间完全是随机的,没有周期性规律。
因此一个实时系统要正确运行,除了能产生正确的处理结果之外,还必须在预定的时间之内完成处理工作。我们称前一个要求为功能的正确性,后一个要求为时间的正确性。对一个实时系统来说,这两种正确性是同等重要的。
根据对系统的响应时间是否有严格的要求,可将实时系统分为硬实时系统和软实时系统两类。硬实时系统对响应时间有一个刚性的、不可改变的限制,它不允许出现任何超出时限的错误。超时错误会导致系统失败,或系统不能实现预期目标。软实时系统对响应时间的要求是柔性的,它可以容忍偶然的超时错误出现。超时错误造成的后果并不严重,仅仅是降低了系统的吞吐量。
介绍了实时系统的概念后,我们再回过头来看一看什么是实时操作系统。实时操作系统可以这样来定义:实时操作系统是具有实时性,能支持实时系统工作的操作系统。它必须能保证实时任务在预定的时间内完成,其首要任务是调度一切可以利用的资源完成实时任务,其次才着眼于提高整个计算机系统的使用效率。
由于早期的嵌入式系统几乎都用于控制目的,因此或多或少地都有些实时要求。所以那时“嵌入式操作系统”实际上就是“实时操作系统(RTOS)”的代名词。近几年来,由于掌上计算机设备的出现,产生了一些没有实时要求的嵌入式系统。此时,“嵌入式操作系统”和“实时操作系统”就成了不同的概念。
图2.1 嵌入式操作系统和实时操作系统之间的关系
嵌入式操作系统和实时操作系统之间的关系如图2.1所示。从图中可以看到,大部分嵌入式操作系统都是实时操作系统。大部分实时操作系统也都是嵌入式操作系统。它们二者间有很大的交集,但确有不适合用在嵌入式系统上使用的实时操作系统和不支持实时应用的嵌入式操作系统。我们称属于交集部分的操作系统为实时嵌入式操作系统。
实时嵌入式操作系统分配和使用CPU时间等系统资源的策略和通用操作系统有很多不同之处。这主要体现在以下三个方面。
① 对于通用操作系统来说,保障总体的效率是主要的目标,必要时宁可牺牲个别任务的响应速度来达到提高总体效率的目的。而实时嵌入式操作系统却正好相反,必要时宁可牺牲总体效率也要保证个别任务的响应速度。
② 对于通用操作系统来说,公正性更为重要,在需要的时候会从占有资源较多的任务那里拿走一些给占有资源较少的任务。而对于实时嵌入式操作系统来说,高优先级任务的运行更为重要,必要时宁可从本来占有资源就比较少的任务那里拿走一些资源,也要保证高优先级任务的需要。相应地,通用操作系统性能的分析是统计分析、平均值分析,而实时嵌入式操作系统性能的分析则是“最坏情况”分析。
③ 通用操作系统要充分利用CPU的处理能力。而实时嵌入式操作系统需要有意让CPU的处理能力供过于求,使CPU运行于轻负荷状态,以确保系统的响应速度。
2.1.3 嵌入式操作系统的主要性能指标
嵌入式操作系统的性能指标是评价嵌入式操作系统的客观依据。可以分为时间性能指标和存储开销指标两类。
1. 嵌入式操作系统的时间性能指标
嵌入式操作系统主要有中断延迟时间、最大关中断时间、中断响应时间、中断恢复时间、中断处理时间、任务上下文切换时间、任务响应时间、系统调用执行时间等一些时间性能指标。这些时间性能指标或多或少地都要受到硬件因素的影响,如CPU速度、存储器速度、存储空间大小、高速缓存(Cache)大小等,在评价嵌入式操作系统的时间性能时,必须要考虑这些因素。
嵌入式操作系统的时间性能指标与中断有密切的关系。为此先讨论嵌入式操作系统处理中断的过程。
中断指在计算机执行期间,由于CPU外部某种预期或非预期事件的发生,导致程序的正常执行流程发生改变的过程。引起中断发生的事件称为中断请求。中断请求一旦得到响应,CPU将先保存当前被中断的任务的部分上下文,然后转到相应的中断服务程序中去执行。执行完中断服务程序后,根据嵌入式操作系统采用的调度策略,将会采取不同的后续处理步骤。下面讨论两种典型的中断处理过程:一种是非抢占式调度的操作系统处理中断的过程,另一种是抢占式调度的操作系统处理中断的过程。
如图2.2所示,嵌入式操作系统一般允许中断嵌套。中断嵌套是指如果在处理一个中断请求(中断请求A)期间有另外一个更重要的中断请求(中断请求B)发生,操作系统将暂停对当前中断请求(中断请求A)的处理,转去处理这个更重要的中断请求(中断请求B)。在下面的讨论中,为避免问题表达过于繁琐,假设在处理中断请求的过程中没有发生中断嵌套。
非抢占式调度的操作系统的处理中断过程如图2.3所示。可以看出,在非抢占式调度的操作系统中,中断服务程序运行完后,一定是继续运行被中断了的任务。这个过程的步骤如下。
图2.2 发生中断嵌套的情况
图2.3 非抢占式调度的操作系统处理中断的过程
① 发生中断请求:中断来到,但还不能被CPU响应,这也许是因为CPU还没执行完当前的指令,也许是因为中断已被操作系统或用户程序关闭。
② 响应中断:CPU执行完当前指令并且中断打开,使中断被响应。
③ 保存关键寄存器,获取中断向量,转到中断服务程序:这些工作全部由硬件完成。下文中“硬件开始处理中断请求到开始执行中断服务程序第一条指令之间的时间”指的就是完成这些工作所花费的时间。
④ 保存任务上下文:中断服务程序保存任务上下文,主要是各寄存器的内容。
⑤ 执行中断服务程序入口函数:中断服务程序调用操作系统的中断服务程序入口函数,通知操作系统系统已进入中断处理中,该入口函数会把中断嵌套层数计数器的值加1,记录下当前的中断嵌套层数。
⑥ 执行用户中断服务程序:此时实际开始对中断请求进行处理。用户中断服务程序所做的工作完全取决于实际的需要,操作系统不会加以限制。但要注意,在用户中断服务中做的事要尽可能地少,而把大部分的工作留给协同工作的任务去完成。用户中断服务程序通知某任务去做事情的方法是调用操作系统提供的任务同步或任务通信系统调用,这可能导致接收通知的任务转换到就绪状态。
⑦ 执行中断服务程序出口函数:用户的中断服务代码运行完后,调用操作系统的中断服务程序出口函数,通知操作系统系统退出此次中断处理,中断服务程序出口函数会把中断嵌套层数计数器的值减1。当嵌套层数计数器减为0时,所有中断都已经处理完。
⑧ 恢复任务上下文:恢复在进入中断处理时保存的寄存器值。
⑨ 中断返回:执行中断返回指令。
抢占式调度的操作系统处理中断的过程如图2.4所示。可以看出,在抢占式调度的操作系统中,用户中断服务程序执行完之后,将调用操作系统的中断服务程序出口函数,通知操作系统系统退出此次中断处理,中断服务程序出口函数会把中断嵌套层数计数器的值减1。当嵌套层数计数器的值为0时,表示所有中断都已处理完毕。此时系统如果没有禁止调度,操作系统的中断服务程序出口函数将执行调度程序。调度程序在执行的过程中需要判断是否应当进行任务切换。如果原先被中断的任务在所有处于就绪状态的任务中仍然是优先级最高的任务,那么不需要进行任务切换,系统将返回到被中断的任务继续执行,如果原先被中断的任务在所有处于就绪状态的任务中已经不是优先级最高的任务,那么就需要进行任务切换,系统将执行另外一个任务。这样在中断返回时将出现A和B两种不同的情况。一种A是继续运行原先被中断的任务,另一种B是运行新被调度程序选中的任务。出现情况B的原因有多种,可能是中断服务程序或其他任何一个嵌套的中断服务程序使另一个优先级更高的任务进入了就绪态,也可能是原先被中断任务的状态已经被中断服务程序改变,如已经进入阻塞状态。在出现情况B时由于要进行任务切换,操作系统中断服务程序出口函数的执行时间要长一些。
图2.4 抢占式调度的操作系统处理中断的过程
中断处理过程的第①步至第⑥步对A和B两种情况是相同的,并且每一步所做的工作与非抢占式调度的操作系统完全相同。第、、步对应于情况A,即中断处理结束后继续运行原先被中断的任务的情况。第、、步对应于情况B,即中断处理结束后运行新被调度程序选中的任务的情况。
(1)中断延迟时间
如图2.4所示,中断延迟时间指从中断请求发生到CPU响应该中断请求,并且开始执行中断服务程序所经历的延迟时间。中断延迟时间受到以下3种时间因素的影响:
① 处理高优先级中断的时间;
② 硬件开始处理中断请求到开始执行中断服务程序第一条指令之间的时间;
③ 最大关中断时间。
因为高优先级中断会屏蔽低优先级的中断,所以一个中断请求必须等优先级比它高的中断请求被处理完之后才能得到处理。又因为中断是可以嵌套的,所以“处理高优先级中断的时间”与中断嵌套的层数及每层中断的处理难度都有密切的关系。而中断嵌套的层数及每层中断的处理难度又与应用问题有直接的关系,对于不同的应用问题中断嵌套的层数可能不同,每层中断的中断服务程序的执行时间也不相同。总之嵌入式操作系统的设计者无法对“处理高优先级中断的时间”进行优化。
“硬件开始处理中断请求到开始执行中断服务程序第一条指令之间的时间”完全由硬件决定,嵌入式操作系统的设计者也无法对这一时间进行优化。在这段时间内要进行保存关键寄存器的内容、获取中断向量、并根据中断向量给出的地址转到中断服务程序等一系列的工作。
中断延迟时间与系统关中断时间有直接关系。操作系统在进入临界区代码之前要关中断,执行完临界区代码之后再打开中断。关中断的时间越长,中断延迟就越长,在严重情况下还可能引起中断请求丢失。对实时系统来说总是关心最极端情况下的处理结果,因此在确定中断延迟时间时,应当测量的是最大关中断时间,而不是平均关中断时间,更不是最小关中断时间。最大关中断时间是嵌入式操作系统的设计者必须加以优化的一个时间性能指标。
(2)最大关中断时间
最大关中断时间的长短取决于操作系统关中断的时间和应用程序关中断的时间两个因素。为了缩短最大关中断时间,在设计和编写操作系统和应用程序时应当尽可能地减少关中断的时间。在设计操作系统时,如果为了保证临界区代码的安全,采用一进入系统内核就关闭中断的方法来进行互斥,那么操作系统的设计虽然很简单,系统的安全也能够得到保障,但关中断的时间就会很长。其实通过仔细分析操作系统内核的代码,就可以发现其中存在一些非临界区。在这些非临界区中开中断,增加操作系统内核中的可抢占点,可以显著缩短关中断的时间。
(3)中断响应时间
如图2.4所示,中断响应时间指从发生中断请求到开始执行用户中断服务程序的第一条指令之间的时间。应注意中断延迟时间与中断响应时间的区别。中断延迟时间指从发生中断请求到开始执行中断服务程序的第一条指令之间的时间。中断响应时间和中断延迟时间之间的关系可以用下面的算式表示:
中断响应时间=中断延迟时间+保存任务上下文的时间+执行中断服务程序入口函数的时间
由于中断延迟时间受到处理高优先级中断的时间、硬件开始处理中断请求到开始执行中断服务程序第一条指令之间的时间、最大关中断时间的影响,所以中断响应时间将受到以下5种时间因素的影响:
① 处理高优先级中断的时间;
② 硬件开始处理中断请求到开始执行中断服务程序第一条指令之间的时间;
③ 最大关中断时间;
④ 保存任务上下文的时间;
⑤ 执行中断服务程序入口函数的时间。
任务上下文指CPU寄存器中的内容。保存任务上下文的时间主要取决于处理器的速度和任务上下文的数量。处理器的速度高,任务上下文少,保存任务上下文所花费的时间就短,反之保存任务上下文所花费的时间就长。
执行中断服务程序入口函数时所做的工作主要是把中断嵌套层数计数器的值加1,记录下当前的中断嵌套层数。某些嵌入式操作系统也会在中断服务程序入口函数中做一些其他的工作。但总的来说,执行中断服务程序入口函数所花费的时间是一个确定值。
(4)中断恢复时间
如图2.4所示,中断恢复时间是从用户中断服务程序执行完毕到继续执行原先被中断的任务或开始执行新被调度程序选中的任务之间的时间。中断恢复时间受到以下3种时间因素的影响:
① 执行中断服务程序出口函数的时间;
② 恢复任务上下文的时间;
③ 执行中断返回指令的时间。
在需要进行任务切换时,中断服务程序出口函数的执行时间会比不进行任务切换时有所延长,但总的来说,执行中断服务程序出口函数的时间是一个确定值。和保存任务上下文的时间一样,恢复任务上下文的时间主要取决于处理器的速度和任务上下文的数量。执行中断返回指令的时间完全取决于处理器的性能,但不论怎样,这一时间是很短的。
(5)中断处理时间
中断处理时间即执行用户中断服务程序所花费的时间。这一时间的长短与用户中断服务程序所要完成的工作量有关。中断服务程序所要完成的工作量取决于具体的情况,无法加以限制。所以中断处理时间这一指标与嵌入式操作系统的其他时间性能指标有一定的区别。
中断服务程序所要完成的工作量虽然不能随意减少,但缩短中断处理时间对于实时嵌入式操作系统却是非常有意义的。因为无论是否打开中断,中断服务程序的运行过程都不能被优先级较低的中断请求所中断,这会导致某些低优先级中断的中断延迟时间延长,降低了系统的实时性能。
在中断处理的工作量是一个恒定值的前提下,缩短中断处理时间的方法是,将工作在中断服务程序和中断服务任务之间进行合理分配。一般情况下,处理中断时所要做的工作包括确定产生中断请求的设备、从产生中断请求的设备处取得数据、根据得到的数据进行中断处理等。确定产生中断请求的设备、从产生中断请求的设备处取得数据一般是由中断服务程序来完成,但进行中断处理则既可以由中断服务程序来完成也可以由中断服务任务来完成。如果将全部或部分进行中断处理的工作交给中断服务任务来完成,那么中断处理时间显然就可以缩短。
将进行中断处理的工作交给中断服务任务来完成虽然可以缩短中断处理时间,但并不总能达到提高系统性能的目的,有时还得不偿失。原因如下,在将进行中断处理的工作交给中断服务任务来完成时,中断服务程序所做的工作变成了以下几项:确定产生中断请求的设备、从产生中断请求的设备取得数据、通知实际进行中断处理的中断服务任务。通知实际进行中断处理的中断服务任务可以采用信号、信号量、消息、事件等同步机制。这种通知工作需要一定的时间。如果处理中断需要花费的时间很短,这样做就很不值得。而且将进行中断处理的工作交给中断服务任务后,实际开始中断处理的时间要延迟到中断服务任务被调度程序选中之后。所以,即使中断服务任务的优先级非常高,也会有一定的调度延迟。但是在直接由中断服务程序进行中断处理的情况下,这个调度延迟是不存在的。这就是为什么在将进行中断处理的工作交给中断服务任务来完成时,并不总是能提高系统性能的原因。
(6)任务上下文切换时间
在多任务操作系统中,任务上下文切换指CPU的控制权由当前运行的任务转移到另外一个就绪任务的过程。发生任务上下文切换之后,当前运行的任务将变为就绪或者阻塞态,另一个被调度程序选中的就绪任务将投入运行。由于在操作系统运行的过程中任务上下文切换会经常发生,因此任务上下文切换时间的长短将直接影响到整个系统的性能。
如图2.5所示,任务上下文切换的过程分为3个阶段:保存退出运行的任务的上下文、选择将投入运行的任务、恢复将投入运行的任务的上下文。任务上下文切换时间就是这3个阶段所花费的时间的总和。
图2.5 任务上下文切换的过程
在这3个阶段所花费的时间中,保存退出运行的任务的上下文所花费的时间与恢复将投入运行的任务的上下文所花费的时间主要取决于处理器的速度和任务上下文的数量。选择将投入运行的任务所花费的时间则主要取决于操作系统的调度程序所采用的任务选择算法。
任务上下文指CPU寄存器中的内容。这些内容保存在任务的当前状态保存区中,如任务的堆栈或任务控制块之中。当一个任务被投入运行时这些内容将被装入到CPU寄存器中,一个任务退出运行时,CPU中各个寄存器的值将会保存到任务的当前状态保存区中。因此,CPU内部的寄存器越多,保存和恢复上下文的工作就越重。所以任务上下文切换时间与CPU有多少寄存器有直接的关系。
当嵌入式计算机带有浮点协处理器时,在进行任务上下文切换时通常也要对浮点协处理器中的寄存器内容进行保存和恢复,这更加耗费时间。由于并不是所有的任务都要使用浮点协处理器,所以操作系统可以采取某些优化策略,并不需要每次进行任务切换时都保存和恢复浮点协处理器中的寄存器内容。
选择将投入运行的任务所花费的时间虽然也受到处理器速度的影响,但更主要的取决于操作系统的调度程序所采用的任务选择算法。一些选择任务算法与系统中处于就绪状态的任务数有关。任务数少,选择任务花费的时间就短,任务数多,选择任务花费的时间就长。这对于有很强实时性要求操作系统是不能接受的。强实时的嵌入式操作系统要求选择任务所花费的时间是确定的,不能随系统中的就绪任务数发生变化。这种操作系统必须采用一种与就绪任务数无关的任务选择算法。有些任务选择算法是可以满足这个要求的。如基于优先级位图的任务选择算法就能够保证任务选择过程所花费的时间具有确定性。
(7)任务响应时间
如图2.4所示,任务响应时间指从一个任务对应的中断发生到该任务开始运行之间所经历的时间。任务响应时间又称调度延迟。如果一个任务在等待某一中断请求将其唤醒,那么这个中断请求发生后,其中断服务程序将使该任务进入就绪状态。如果这一任务有足够高的优先级,那么在当前运行的任务停止运行后(停止运行的原因是多样的,可能是因为被抢占,可能是因等待某种资源而被阻塞,也可能是因为运行时间片到期),这个新进入就绪状态的任务就将被投入运行。任务响应时间就是指这一过程所花费的时间。
任务响应时间受到以下因素的影响:
① 操作系统的调度算法;
② 系统禁止调度的时间;
③ 中断延迟时间;
④ 任务上下文切换时间;
⑤ 中断处理时间。
操作系统的调度算法是决定任务响应时间的主要因素之一。采用抢占式最高优先级优先调度算法的操作系统任务响应时间明显较短。因为这种操作系统是即时抢占的,一旦任务的状态发生了变化,操作系统马上就会进行调度,使高优先级的任务投入运行,如图2.4所示,这时任务响应时间很好计算,它的值等于中断延迟时间、执行用户中断服务程序的时间、中断恢复时间三者之和。有些操作系统并不是在任务状态发生变化后马上就能进行调度,而是要等待到某一个特定的时刻(如当前运行的任务因等待某种资源被阻塞时),所以其任务响应时间就比较长。
系统禁止调度的时间也是影响任务响应时间的主要因素之一。在操作系统禁止调度时,即使中断服务程序使一个优先级更高的任务进入就绪状态,中断处理结束后也无法切换到这个高优先级任务运行,任务响应时间将变得很长。禁止调度是一种互斥方法。一个实时性能良好的操作系统不会使用这种方法进行互斥,但操作系统经常会提供一个可以禁止/允许调度的系统调用。在应用程序中虽然可以使用这个系统调用,但使用的时候一定要慎重,以免使任务响应时间变得过长。
另外,从前面的介绍中可以看出:中断延迟时间、任务上下文切换时间、中断处理时间等因素也都会对任务响应时间发生影响。
(8)系统调用执行时间
系统调用执行时间也是评价一个嵌入式操作系统性能优劣的指标。然而,由于调用参数和系统运行情况的差别,同一个系统调用的每次执行,可能会经历不同的路径,所以其执行时间不是一个定值,而是会在一定区间之内波动。
实时嵌入式操作系统,关心的是系统调用的最大执行时间;非实时嵌入式操作系统,关心的是系统调用的平均执行时间。所以在评价系统调用执行时间时,应该根据可能的使用情况设计不同的测试用例,获取其最大值和平均值。另外,一个操作系统中的各种系统调用的使用频率是不同的,一些系统调用经常使用,另一些系统调用很少使用。所以在对一个嵌入式操作系统的系统调用执行时间进行整体评价时应对不同的系统调用赋予不同的权重。
2. 嵌入式操作系统的存储开销指标
由于成本、功耗和便携性要求等原因,嵌入式系统的存储资源一般都比较有限,因此嵌入式操作系统的存储开销也是它的重要性能指标。在这一点上嵌入式操作系统与通用操作系统有明显的区别。嵌入式操作系统主要有代码存储开销和数据存储开销两种存储开销指标。
(1)代码存储开销
代码存储开销指存储嵌入式操作系统(内核)本身所需要的内存数量。嵌入式操作系统代码的大小取决于多种因素,其值一般与操作系统的功能有直接关系,分布在从几千字节到几百字节的区间内。例如,用于无线传感器的嵌入式操作系统代码必须限制在几字节的水平,而用于数字电视和机顶盒的嵌入式操作系统代码达到几百字节也是完全可以接受的。
(2)数据存储开销
嵌入式操作系统在运行过程中需要一定数量的存储空间来存放工作数据。数据存储开销指存储这些工作数据所需要的内存数量。这些内存按用途分为以下三类:
① 操作系统变量所占的内存。这一部分内存的大小固定,不会随着操作系统的运行发生变化。
② 操作系统对象及其控制结构所占的内存。操作系统对象指任务、信号量、事件、信号、消息、软件定时器、堆等。这些对象的数目有可能会随着系统的运行发生变化。例如,在操作系统支持的任务数不固定的情况下,任务控制块的数量可以随着系统中任务数的多少发生变化。
③ 堆栈所占的内存。操作系统使用的堆栈主要有任务堆栈和中断堆栈两种。在多任务操作系统中,操作系统给每个任务提供一个单独的堆栈空间。函数调用的返回地址、函数的局部变量等都保存在任务堆栈中。任务堆栈的尺寸一般由应用程序开发者指定,但准确估计出一个接近实际需要的值是相当困难的。为了防止任务堆栈溢出往往要指定一个较大的值。在允许中断嵌套时,任务上下文(各种寄存器的值)、中断服务程序的局部变量也可以保存在任务堆栈中,但为了避免在中断嵌套层数过多时任务堆栈发生溢出,一些操作系统采用了一个中断堆栈专门来保存中断嵌套时产生的任务上下文和中断服务程序的局部变量,而不是将这些信息保存在任务堆栈中。这种做法降低了由于任务堆栈空间的实际需求难以确定所带来的问题,提高了系统的可靠性。
2.1.4 嵌入式操作系统的结构
嵌入式操作系统的结构可分为单块结构、层次结构和微内核结构三类。单块结构的嵌入式操作系统和层次结构的嵌入式操作系统又统称为一体化结构的嵌入式操作系统。
1. 单块结构的嵌入式操作系统
如图2.6所示,单块结构的嵌入式操作系统由许多模块组成,这些模块通常在核心态下运行。模块之间没有层次关系,可以根据需要随意地相互调用。在系统规模较小时,这种结构显得比较灵活。但总的来说,单块结构操作系统很难调试和维护。如果一个模块被改变,对其他的模块可能会有很大的影响,导致其他模块中出现一些错误。操作系统中包含的模块越多,模块间的调用关系越复杂,整个系统就越混乱。
2. 层次结构的嵌入式操作系统
如图2.7所示,层次结构的嵌入式操作系统按层次关系进行组织。整个系统由多个层次所组成,并且只允许上层调用下层,而下层不能反过来调用上层,操作系统的结构清晰,提高了系统的安全性。在一个理想的层次结构系统中,各层之间不能越层调用。比如第n层不能越过第n-1层直接调用第n-2层。在这种严格意义的层次系统中,很容易在不影响其他层的情况下替换其中的某一层。但在一个具体的操作系统中,出于性能方面的考虑,各层之间不会严格地遵守逐层调用的规则。而是允许上层越过直接下层调用更底层的功能。例如,应用程序发出的系统调用就可以直接到达对应的功能层。
图2.6 单块结构的嵌入式操作系统
图2.7 层次结构的嵌入式操作系统
3. 微内核结构的嵌入式操作系统
如图2.8所示,微内核结构的嵌入式操作系统只有一个很小的内核,其中只包含一些必须由内核实现的功能。而把许多不是必须由内核实现的功能都放在了内核之外,运行于用户态。微内核结构提高了操作系统的模块化程度,使其结构更清晰,更加易于调试、扩充和剪裁,也很容易实现在不同硬件平台之间的移植。
图2.8 微内核结构的嵌入式操作系统
在单块结构和层次结构两类操作系统中,操作系统的功能大部分都是由系统内核提供的。系统内核中包含的功能很多,整个操作系统几乎是一个整体,没有再划分内核部分和核外部分。因此把这两类操作系统统称为一体化结构的操作系统。这样按照操作系统内核中包括的功能的多少,操作系统就被分为了微内核结构的操作系统和一体化结构的操作系统两大类。
大部分嵌入式操作系统都是采用微内核结构,采用一体化结构的嵌入式操作系统相对很少。专门为嵌入式系统开发的操作系统几乎无一例外地采用了微内核结构,只有某些从通用操作系统演化来的系统采用了一体化结构。
嵌入式操作系统通常采用微内核结构的原因主要来自以下三个方面。
① 使用特征方面的原因:通用计算机面对的是开放的用户群,一些用户不需要的功能另一些用户可能需要。一个功能放在内核中,总会有用处。而且,内核的复杂与否也不是用户所关心的问题。然而,嵌入式系统的情况则不同。嵌入式系统的用户群及用户对功能的要求是相对封闭的。对于特定的嵌入式系统,需要哪些功能、不需要哪些功能基本上是固定的。对于一个具体的嵌入式系统而言,操作系统中的某些多余功能会永远没有用途。
② 内存资源方面的原因:相对于通用计算机而言,内存在嵌入式系统中是很有限的资源。以下几个问题都是限制嵌入式系统中内存数量的原因:第一,价格因素,对多数嵌入式系统而言,内存价格在整个系统的价格中都占一定的比例。第二,供电因素,嵌入式系统经常由电池供电,因此电源供应是一个重要问题,非常需要采用包括减小内存容量在内的各项措施节约系统的耗电。第三,散热因素,在系统电源有保障的情况下,散热也是一个问题。嵌入式系统往往要在条件很不利的情况下使用,而且还可能无法采用排风扇一类的散热措施。例如,很难给一台PDA安上一个散热风扇。因此有必要通过减少内存数量来降低系统的散热。所以,操作系统内核所占的内存数量对于嵌入式系统是个很敏感的问题,减少操作系统内核所占用的内存数量有重要意义。
③ 商业方面的原因:如果嵌入式操作系统的功能大而全,会导致价格较高,显然既不符合用户的利益也不符合供应商的利益。在销售嵌入式操作系统时应当能够根据用户的需要按模块进行销售。嵌入式操作系统采用微内核结构比采用一体化结构更易于按模块进行拆分和组合。
既然微内核结构的操作系统比一体化结构的操作系统更适合于嵌入式系统,那么哪些成分必须留在嵌入式操作系统的内核中呢?最小的内核又必须包含哪些成分呢?现在就来讨论这些问题。
① 进行任务管理与调度所需的功能必须位于内核中。包括任务调度、任务建立、任务删除、任务挂起、任务唤醒、任务属性设置、任务属性查询等功能。
② 内存管理功能必须留在在内核中。如页表的建立和管理必须由内核来实现。
③ 任务通信功能必须由内核来实现。这些功能包括互斥、同步和数据交换机制。内核中没有这些功能就无法实现任务与任务之间,以及内核与任务之间的通信。
④中断管理必须由内核来实现。嵌入式系统的CPU响应中断后会自动切换到核心态,执行中断服务程序,所以最基本的中断管理工作只能由内核来实现。
综上所述,最小的微内核中可以只包括任务管理、内存管理、任务通信和中断管理4个主要成分。
当嵌入式操作系统采用微内核结构时,放到内核之外的功能有两种存在形式:一是作为服务任务存在;二是以库函数的形式存在。
当内核外的功能以服务任务的形式存在时,在被服务的任务与提供服务的任务之间将建立一种“客户/服务器”关系。当一个任务有某种服务需求时,可以向服务任务发出请求,并由服务任务,而不是内核中的模块为发出请求的任务提供服务。但这个过程的细节通常被隐藏了起来,发出请求的任务以为服务请求交给了内核,而得到的服务也由内核提供。
以系统调用open为例,可以采用如下的实现方法:当用户任务调用open要求打开一个文件时,内核将使该用户任务进入阻塞状态,然后以此任务的名义向文件服务任务发送一个消息。当文件服务任务接收到这个消息并完成所要求的操作以后,发回一个应答消息,内核在接收到这一消息后,使请求打开文件的用户任务进入就绪状态,并根据文件服务任务发回的消息决定本次系统调用的返回值。
当内核的功能由服务任务完成时,虽然逻辑上应用软件的任务仍在高层,服务任务还是在低层为其提供服务,但是结构上则不再遵循同样的关系,因为提供服务的任务与用户任务处在同一层次上。当然,也可以让应用任务知道服务任务的存在,直接向服务任务发出服务请求。但即使是这样,也要通过内核作为中介来传递消息并加以协调,因为任务通信机制需要由内核来提供。
一体化的内核好比是一个实力雄厚的工程承包商,承接的所有工程都靠自身的力量来完成。微内核好比是一个自身没有多大施工能力的承包代理,除去必要的管理工作之外,一般的工作都是分包给其他的公司来完成。
当内核外的功能以库函数的形式存在时,库函数可以是静态链接的库函数或动态链接的库函数。UNIX/Linux上的.a文件和Windows上的.lib文件都是静态链接库。应用软件在编译/连接阶段就静态地将这种库函数的可执行代码链接进它的可执行文件中,所以在每个可执行文件中都有一份库函数的副本。UNIX/Linux上的.so文件、Windows上的.dll文件都是动态链接库。应用软件在运行时才动态地与这种库函数建立链接,并且所有的应用软件都共享库函数的同一份副本。
服务任务和库函数两种组织核外功能的方法并不互相排斥,可以混合使用,或者以前者为主,以后者作为补充。
相对于一体化结构的操作系统,微内核结构的操作系统有以下一些优点。
① 操作系统内核小,且简单,所以容易理解,容易维护。
②各种系统模块(如文件系统)、设备驱动程序、乃至中断服务程序,都可以作为独立的任务来开发,相互间的关系简单,便于调试,并且容易在其他环境下模拟。因此整个系统的开发有一条渐进的途径。
③ 系统配置灵活方便。从商业的角度还可以把内核和各种服务任务或库函数分别销售,让用户根据具体需要选购,而且也有利于其他软件开发商开发各种第三方软件。
④ 由于操作系统的内核很小,所以执行内核代码的时间十分短暂。这样,基本消除了因为执行内核代码而产生的不可抢占问题,提高了系统的实时性。可以说,微内核天生就是可抢占的。许多嵌入式操作系统之所以宣称“可抢占”,就是因为采用了微内核结构的原因。
相对于一体化结构的操作系统,微内核结构的操作系统又有以下一些缺点。
① 系统效率有时会有所降低。例如,如果将对于外部事件的处理放到内核之外,微内核在响应了外部事件所引起的中断以后,需要进行任务调度。当负责处理外部事件的服务任务被调度程序选中之后,外部事件才能得到处理。因此,原本只有一个中断延迟,现在却又增加了一个调度延迟。尽管因为“可抢占”性的增强会使调度延迟得到一定程度的缩减,但系统的综合响应速度却很可能还会有所下降。
② 系统安全性会有所降低。例如,文件系统的安全性无疑是信息安全中的一个关键问题,如果把文件系统放到内核之外,显然更容易被外来的入侵所攻破,出现安全漏洞。
2.1.5 嵌入式操作系统的组成
各种嵌入式操作系统的具体组成有很大的不同,特别是微内核结构的嵌入式操作系统与一体化结构的嵌入式操作系统有比较显著的差别。但对于同样采用微内核结构的系统而言,其组成则有一定的共同之处。如图2.9所示,当一个嵌入式操作系统采用微内核结构,并且内核之外的功能以服务任务的形式存在时,其组成可分为硬件抽象层、操作系统内核、核外服务、应用程序接口等几个部分。
1. 硬件抽象层
硬件抽象层用于屏蔽不同硬件的特征。向下它与嵌入式系统的硬件直接打交道,向上它对操作系统内核中的各个模块提供一个统一的接口,增强了系统的可移植性。当需要将嵌入式操作系统从一个硬件平台移植到另一个硬件平台上时只需修改硬件抽象层的程序代码。
图2.9 嵌入式操作系统的组成
2. 操作系统内核
像上面曾经讨论过的那样,任务管理、内存管理、任务通信和中断管理等几部分功能通常只能放在操作系统内核中,而其他的一些功能,如文件管理、电源管理、输入/输出管理、“看门狗”等,虽然可以放在内核之中,但也可以放在内核之外,以服务任务或库函数的形式存在。
3. 核外服务
一般情况下,文件管理、电源管理、输入/输出管理、“看门狗”等一些功能应当由内核之外的服务任务来实现。中断管理虽然需要由内核来实现,但这不意味着中断服务程序要承担所有的中断处理工作,完全可以把一些处理工作放在一个专门的中断服务任务中。每当有中断请求发生时,内核中的中断服务程序只要把与中断有关的信息通知中断服务任务(可以通过内核支持的某一种任务通信方式),并使这个任务进入就绪状态,具体的处理工作就可以由这个中断服务任务去完成(中断服务任务的优先级一般都很高,进入就绪状态后马上就会被调度运行)。
一些嵌入式操作系统以核外服务的形式提供窗口和网络方面的功能。但这些功能比较独立,按照经典理论它们不是操作系统的一部分,所以还是把它们归类于支撑软件比较合理。
4. 应用程序接口
应用程序接口提供一系列供应用程序开发者调用的功能。应用程序开发者通过应用程序接口达到使用嵌入式操作系统的目的。应用程序接口还可以进一步分为多个部分,包括操作系统所提供的系统调用和建立在系统调用基础上的库函数。
2.1.6 嵌入式操作系统的类型
嵌入式操作系统的种类很多,分类方法也是多样化的,下面从供应方式、实时性、历史渊源等几个角度对其进行分类并介绍它们的特点。
从嵌入式操作系统的供应方式上可将其分为商用嵌入式操作系统和开源嵌入式操作系统两类。
商用嵌入式操作系统一般功能稳定、可靠、有完善的技术支持和售后服务、辅助工具齐全。例如,WindRiver公司的VxWorks、Palm Computing公司的PalmOS、微软公司的Windows CE和凯思昊鹏公司的Hopen都是商用嵌入式操作系统。
开源嵌入式操作系统在价格方面有优势,此外由于应用软件开发者可以获得操作系统源代码,这给开发工作带来了很大方便。但开源嵌入式操作系统的缺点也非常明显,主要表现是技术支持差、系统稳定性相对较差。因此,使用这类嵌入式操作系统对开发者有较高的要求。虽然目前有一些公司可以为开源嵌入式操作系统提供技术服务,但购买这种服务的费用并不低于购买商用嵌入式操作系统。开源嵌入式操作系统的典型代表是嵌入式Linux。
从嵌入式操作系统的实时性上可将其分为实时嵌入式操作系统和非实时嵌入式操作系统两类。
实时嵌入式操作系统的特征是能满足应用程序对时间的限制和要求。它主要用在控制、军事、通信等领域。例如,WindRiver公司的VxWorks、QNX Software Systems Europe公司的QNX、Accelerated Technology公司的Nucleus都是实时嵌入式操作系统。
非实时嵌入式操作系统不关心单个任务的响应时间,它的系统平均性能一般较好,比较适合用在消费类电子产品中,如个人数字助理(PDA)、机顶盒、电子书、网络电话等。非实时嵌入式操作系统的典型代表是较早版本的Windows CE。
从嵌入式操作系统的历史渊源上可将其分为PC型嵌入式操作系统和非PC型嵌入式操作系统两类。
PC型嵌入式操作系统是由个人计算机或服务器上的操作系统演化来的嵌入式操作系统。例如,微软公司的Windows CE、嵌入式Linux等。这类操作系统经过在个人计算机或服务器上的长期运行,技术比较成熟,相关软件的开发方式用户比较熟悉,同时也积累了丰富的开发工具和应用软件。
非PC型嵌入式操作系统是专门为嵌入式系统开发的操作系统。例如,WindRiver公司的VxWorks和OSEKworks、Psion公司的Symbian、UC Berkeley公司的TinyOS等。这类操作系统一般针对某些固定的应用需求而设计,较多地应用在对系统的可靠性和实时性要求很高的领域。