最近几年,Linux操作系统在桌面和服务器系统等领域的应用取得了很大的成功。它的存在已经对这些领域中的传统霸主,例如微软的Windows和Sun公司的SunOS/Solaris操作系统等造成了极大的威胁。这主要得益于其较低的使用开销和更高的应用性能:现在,Linux操作系统加高端奔腾处理器构成的计算机系统在性能上已经远远超过了同等价位的运行着Solaris的基于SPARC处理器的计算机系统;Linux能够取得成功的另一个主要原因在于它是一个开放源码的系统软件,Linux用户可以享受到世界各地Linux爱好者提供的支持。
就在Linux系统在桌面和服务器领域应用风头正劲的时候,业界内外普遍地认为Linux也会成功打入到嵌入式系统中,成为主流操作系统之一。但是,事与愿违,现实中的情形远没有那么乐观。那些为桌面和服务器系统提供Linux操作系统软件的开发商们并不热衷于将Linux引入到嵌入式系统领域。而其他的一些已经在嵌入式Linux领域开拓市场的公司,比如Lineo和MontaVista,也一直没有获得稳定的收益。为什么Linux没有能够在嵌入式领域中获得它在桌面系统中同样的辉煌呢?原因就是桌面系统和嵌入式系统对操作系统的需求有所差异。
桌面系统和嵌入式系统对操作系统的需求有很多差异,而且都很关键。我们在这里先讨论几个最重要的问题,这些问题已经足够体现出Linux系统在嵌入式系统领域所面临的挑战。这些挑战都是在之前的桌面系统应用中未曾遇到的,主要包括:
● 中断延迟(interrupt latency)
● 线程响应时间(thread response time)
● 调度策略(scheduling)
● 设备驱动程序
● 可靠性和安全性
以上这些都是技术层面上的挑战。而在将Linux系统引入嵌入式领域时,还有很多的非技术性层面上的挑战也是必须要考虑的。 技术挑战
中断延迟
实时系统(Real-Time System)是指能够在限定的时间(一般是很短的时间范围)内对系统中发生的某类事件(比如从某个外围设备传来的中断请求)进行处理的系统。如果系统对这些事件的响应出现了问题,比如未能在限定时间内对其做出相应处理,就会导致系统出现故障。绝大多数嵌入式系统都有很高的实时性需求,而桌面系统却不一定。在嵌入式系统测试中,衡量系统实时性的最主要参数有两个:一个是中断延迟时间的长短,另一个是线程上下文切换时间的长短。
中断延迟是指从产生中断请求到相应中断服务程序的第一条指令被执行之间的这段时间。由于中断具备有优先级而且可以嵌套产生,因此可以测知优先级最高的中断在执行时的延迟时间。测试表明,产生中断延迟的原因除了处理器响应时间外,更重要的是操作系统往往会大大增加中断被延迟的时间。在操作系统运行过程中,存在着一些关键的操作。这些操作在执行时,操作系统会禁止在其间插入任何中断。因此如果一个中断请求在操作系统禁止中断的这段时间里产生,那么对它的处理就会始终保持在挂起状态,直到操作系统重新允许中断插入。最终,中断延迟在最坏情况下的数值是和操作系统关键操作中的指令序列在中断请求产生后继续执行的时间紧密相关。
实时系统就是要确保系统中的关键事件能够在限定的时间段内被处理。操作系统开发商会提供给用户一个表示中断延迟的数值以体现产品的实时性能。这个数值是在实验室环境下测得的,它可能是平均值,典型值,或者是在最好情况下的系统中断延迟。但在实际应用过程中,最坏情况下的延迟中断才是用户最需要考虑的。那么如何才能得到这个数值呢?瑞典的一个研究小组对操作系统中禁止中断产生的代码区域进行分析,试图用这种方法对当前一款商业化的实时操作系统进行研究并得到系统中最坏情况下的中断延迟的大小。
这项研究使用了先进的程序流分析方法来确定操作系统中所有的关键操作(即那些“禁止插入中断”的代码)区域的位置和结构。研究人员还利用了周期精确的模型来测定选定代码区域的被执行次数。但是研究结果并不让人感到振奋。因为用于禁止和允许中断的指令所需的源操作数在执行时才能确定,所以研究人员不能用静态分析的方法得到关键操作的执行情况。还有一些其他的程序流问题,比如关键操作序列的嵌套出现等等,也阻碍了研究的进展。最终据研究人员估测,大概只有一半数目的“禁止插入中断”的关键操作区域能够被确定,而另一半大约有600个区域会对中断响应时间产生未知的影响。
研究人员评估了他们能够确定的那些关键操作区域的执行时间。这些区域的执行情况非常复杂,有很多已经不是以简单的顺序结构执行了,少数几个区域甚至包含了三个嵌套的循环,而且还有一些关键操作区域的循环次数是不定的。在研究人员提供的报告中,可以看到一个具有嵌套循环的关键操作区域的执行周期数的估测为26729。试想在一个主频为100MHz的微处理器上,仅仅是这样一个区域就要消耗大约250微秒的时间。相信没有任何一个实时操作系统的开发商会愿意公开这个量级上的中断延迟。 线程响应时间
线程响应时间是指从产生中断请求到由该中断服务程序唤起的线程中的第一条指令被执行之间的这段时间。和中断延迟一样,线程响应时间也是衡量系统实时性能的一个重要因素。嵌入式应用的设计者往往用线程实现程序所需的一些功能,比如设备操作代码,因为这样做便于进行调试而且会减少那些在执行过程中禁止插入中断的代码数量,有助于减少最坏情况下的中断延迟时间。
在线程的执行过程中,有一个重要的问题在于响应高优先级中断的线程的执行会受到其他低优先级中断的影响。在高优先级的线程执行过程中是允许插入中断的,因此可以有任意多的低优先级中断请求产生。这些低优先级中断请求的相关中断服务程序都要被依次执行,大大增加了线程响应时间。这是一个典型的“优先级翻转”的例子,即低优先级任务的执行延误了高优先级任务的工作。在桌面系统中,出现这样的翻转是无碍大局的。
实时操作系统应该能够提供一种方法来防止这种优先级翻转问题的出现。有一个简单却很有效的方法是由设计者在关键的中断处理线程中对中断的优先级加以区分。当某个线程被调度时,系统内核就限制住那些优先级比该线程低的中断产生。直到在没有更高优先级的线程要执行时,才重新允许插入低优先级的中断。
调度策略
Linux并没有提供和中断相关联的线程优先级。实时操作系统却需要进行基于线程优先级的调度,用来确保在某事件发生时,系统中的关键线程能够立即执行。
标准的Linux调度器使用的是“公平”的启发式调度策略。这主要是因为Linux继承了Unix的特性,是分时共享的交互式操作系统。因此,程序设计者就不可能指定一个具有绝对最高优先级的线程。当中断服务程序启动某个线程去处理相应事件时,Linux调度器很有可能会先选择一些其他的线程优先执行。也正因为如此,我们完全不可能确定在最坏情况下的线程响应时间。
与早些时候的v2.4内核相比,Linux的v2.6内核做了一些调度性能上的改进,但是对实时性的改造仍旧不在其中。按照Red Hat公司的说法:“线程库的实现几乎没有对实时性的支持。系统可以选用一些调度参数,但是这些参数往往是起不到任何作用的。出现这种情况的主要原因是内核中的大部分实现都不遵守实时性的规则……而且内核中的很多其他方面也没有能够给予提高实时性所需的必要支持。”
除去中断延迟时间较长以外,Linux运行时会发生在某一段时间内禁止抢占的现象,即使在v2.6内核中加入了“抢占补丁”后仍旧存在这个问题。有测试数据表明Linux的v2.6内核在最坏情况下的响应时间是1.2毫秒。这个数值虽然比2.4版本的20毫秒有了很大的提高,但是一个实时嵌入式操作系统在最坏情况下的响应时间最差也应该是在亚微秒这个量级上(需要依赖于具体的硬件配置),因此Linux相对于这个标准还有很大的差距。而且Linux系统中实测到的时间并不能反映出理论上的最坏情况,这一点在实时系统中是绝不允许的。
设备驱动程序
如果设备驱动代码运行在各自受保护的地址空间中,就会大大减少给系统带来的风险。现在,具有虚拟设备驱动结构的操作系统摒弃了传统的将设备驱动程序运行在内核物理存储空间的方法。在这种结构中,有些虚拟设备驱动由中断服务程序唤起,在内核空间以外对设备进行管理。还有些驱动采用的是非中断驱动的方式,直接在被映射到驱动进程内的设备相关的存储地址空间内进行操作。这种实现需要通过一套灵活、强大并且有效的API使得虚拟设备驱动能够安全地使用它所需的物理设备资源。
但是Linux系统缺乏这种虚拟设备驱动结构。虽然现在的Linux能够为应用提供受保护的虚拟存储空间,但是它还是将复杂的设备驱动程序加入到了自己的物理内核地址空间中执行,这就平添了很多风险。传统的增加新Linux设备驱动的方法是将这个驱动代码编译为目标文件,然后将其链接进内核中或者由内核模块装载器将它动态地装载。这些驱动程序很难被调试而且可以不受限制地访问任意物理存储空间。
Linux的单核设计在系统的稳定性、可靠性以及安全性等诸多方面存在着问题。一个更好的结构,比如在一些嵌入式操作系统中已经采用了微核结构,就将设备驱动程序、网络协议栈、文件系统和其他复杂的软件放到应用地址空间中,这样就不会危害到内核以及其他应用了。
上述Linux操作系统在嵌入式系统领域面临的种种技术挑战已经引起了业界的重视。目前已经有多种措施来提高Linux系统的实时性能。主要策略有:
● 增加实时子内核。如RTLinux,由两个子内核构成,一个用于Linux环境,一个用于实时环境。另外遵循GPL的RTAI(实时应用程序接口)也类似于这种方式,采取硬件虚拟化技术对Linux内核做了硬实时(Hard Real-Time)的扩充。这两种方法可以有效改善系统中断延迟时间的问题。
● 为Linux打实时补丁。可以借助Linux操作系统的源代码补丁来提高系统的实时性能。当前主要的实时补丁有低响应时间补丁、抢先任务补丁以及实时调度程序补丁等等。虽然打过补丁的Linux系统能够改善系统实时性能,但是仍旧不能够用于任务繁重的实时嵌入式系统应用中。
技术的改进为Linux在嵌入式系统中的应用扫除了一些障碍,但是如果Linux操作系统想在嵌入式领域夺取一席之地,要做的事情还远不止这些。
非技术因素
貌似免费
Linux的广泛应用很大程度上来源于大众对“Linux是免费的”这个概念的误解。在现实情况中,使用嵌入式Linux产品的相关费用是非常昂贵的。例如,MontaVista、Hard Hat Linux的开发商,每年要向每个使用其产品的嵌入式系统开发人员收取1.5万美元的费用。按照五年计算,一个由五人组成的嵌入式系统开发团队就需要支付37.5万美元。与此相比,很多由开发商独立设计提供的嵌入式系统解决方案的花销都要低很多。
还有一些使用Linux的嵌入式系统开发者选择直接从网上下载Linux资源,然后按照“业内权威”的意见和建议来解决在Linux使用中碰到的各种问题。他们以为这样做就避免了使用商业化产品带来的巨额花销,但实际上他们低估了用于支持Linux使用带来的内部消耗。有些公司宣称他们只需要一个全职的Linux高手做员工就能解决一切问题。而根据一个保守的估计,这些公司每年要为这个全职的员工支付15万美元的薪水,那么五年下来的开销就是75万美元,这远远大于它们去采用那些专门的嵌入式系统解决方案所需的费用。而且还有一个问题不能忽视,那就是虽然一个“权威”可以解决很多的Linux问题,但是他们显然还是不如那些产品研发者对自己的东西了解得透彻。
相对于其他专门的嵌入式系统解决方案,支持Linux的开销太高,所以有很多曾经试图采用嵌入式Linux系统后来又发现这个实际情况的用户给Linux找了个新的形容词:貌似免费。
长期的支持
大部分的嵌入式产品都有一定的市场周期,一般是几年。有些航空电子设备前期开发要耗费十几年的时间,而它真正的使用时间也不过就是比研发时间长几年。甚至某些手持设备从开始在工厂里研发到最后在市场上消失,整个生命周期也就是几年的光景。
嵌入式系统的开发人员往往希望他们的产品所依托的开发商具有稳定的、经得起考验的运作模式。因为他们必须要依赖这些开发商在较长时间内来支持他们的产品,更关键的是还要能够继续支持他们进行后续产品的开发。
很多嵌入式Linux开发商已经意识到他们并不能够满足嵌入式系统开发者所要求的长期支持和服务。惟一的解决方法就是适当地增加售后支持工程师的队伍。而目前大部分嵌入式Linux开发商采用的运营方式是以相对低廉的价格出售他们的产品,并以此作为主要盈利方式。
有一些已经意识到了这一困境的开发商为了卖出产品,就开始对Linux进行一些自己独有的嵌入式系统扩充,或者是增加一些迎合市场需求的软件。这么做使他们陷入了另一个境地:通过增加那些独有的嵌入式系统扩充,Linux开发商将成为另一个自行设计的专用嵌入式系统解决方案的提供商,而不再是一个纯粹的嵌入式Linux开发商。Linux社团不会接受更不会去提倡Linux开发商们的这种行为,因为他们这么做已经违背了Linux倡导的自由精神。
Linux在嵌入式系统领域的应用面临着重重挑战,有技术层面上的,更有非技术层面上的。这些挑战造成了当前Linux在嵌入式系统领域举步维艰的局面。到底Linux是不是适合于嵌入式系统的应用是我们当前务必要认真思考的一个问题,毕竟Linux操作系统从诞生之日起就是为桌面和服务器系统打造的,也许将它应用于嵌入式领域只是我们的一相情愿。 GPL
Linux是受GPL约束的。GPL规范了各家公司如何才能够将GPL许可的软件整合到它们自己的产品中再销售给用户。将GPL软件整合进产品中会导致一些技术问题。但还有一些问题是经常被忽视的,那就是由GPL许可证本身带来的问题。其中与各方利益关系最密切的就是如果在自己开发的产品中采用了一些GPL软件,那么是不是就意味着这个产品也要受到GPL的约束。
操作系统在整个嵌入式系统中的地位是举足轻重的。在GPL的条款中强调:“你必须使你发布或出版的作品(它包含程序的全部或一部分,或包含由程序的全部或部分衍生的作品)允许第三方作为整体按许可证条款免费使用。”正是这一条款,使得开发商们不可能愿意冒风险去将GPL软件整合进自己的产品中。
有很多公司花费了大量的资金去研究使用GPL软件可能带来的问题,还有一些用户团队也正致力于研究这些因为软件许可证而导致的问题。但是他们发现由于对GPL的解读各有不同,这里存在着大量的“灰色地带”。即使是那些在Linux团体中很有声望的人士对于GPL应用的观点也往往不一致。一个最值得注意的问题就是Linux中的“可加载模块”。这个概念在前文中已经提到过了,Linux用户是可以动态地将模块直接加载到内核当中的。Linux社团中的一些很著名的人物坚称“可加载模块”必须遵守GPL许可证。然而,Linus Torvalds——Linux之父却持有相反的观点。他认为“可加载模块”不应该受到GPL的约束。还有其他一些人觉得“可加载模块”是否要遵循GPL许可证是要根据该模块与内核间是不是存在某些类似的风格(比如是否共享了一些数据结构等等)来判断的。正是因为存在着规则解读不明的情况,所以很多公司和用户都对GPL感到不满:因为灰色地带就意味着风险。
GPL对那些试图将开源软件整合进自己产品的公司还提出了另一个挑战,那就是关于这些公司是否会存在专利侵权和著作权被侵犯的问题。GPL并不对用户提供保证以避免遵守许可证的代码被侵权,同样GPL也不能够保证被许可的代码没有侵犯已有的专利。实际上,GPL已经明确地说明了它没有办法做这样的保证声明,这使得用户在使用GPL软件后只能自己去面对那些可能的专利和版权纠纷。如果这些纠纷在产品已经量产并推向市场后发生,那么开发商必须面对的就是复杂的召回工作或者是付给专利持有者巨额的费用。
开放的标准
Linux在有些人的印象中是开放标准的。其实并非如此,Linux只是一款开放源码软件。开放源码的软件是指可以自由使用,而且是可以自由修改的软件。而开放的标准是指一个规范,这个规范是能够被自由地阅读和实现的。开放标准可以增强技术实现的移植性,还避免了技术被某家开发商垄断的情形出现。因为Linux是没有标准来规范其发行的,所以各家开发商在发布各自的产品时对Linux所做的改动存在着较大差异,这直接导致了很多应用不能够跨系统移植。
POSIX(可移植的操作系统接口)就是开放标准的一个例子。它描述的是操作系统中的应用接口标准。已经有很多不同的操作系统开发商在他们各自的产品中提供了符合POSIX标准的接口。这就使得应用的开发者摆脱了只能局限于一家操作系统的困境,其编写的代码可以不加修改很方便地移植到其他的遵守POSIX标准的操作系统上。
在Unix操作系统中就早已定义了通用的接口集合。这个集合就是今天POSIX标准的基础。有人可能会想既然Linux是脱胎于Unix的,那么Linux也应该是符合POSIX标准才对。但事实上,虽然Linux目前具有一套类似POSIX的接口,但在很多关键的地方它们是并不一致的,比如之前谈到过的调度问题、信号以及线程的实现等等。即使是最新版本的Linux线程库(NPTL,Native POSIX Thread Library for Linux)也不是完全和POSIX兼容的。开发者们会发现他们编写的可移植的POSIX应用并不能够在Linux系统上正常工作,反之亦然。
这些不一致带来的后果可能是灾难性的。例如,二者对setuid系统调用的实现差异就会导致很大的问题。POSIX标准中要求只要有一个线程调用了setuid,那么和该线程同属一个进程的所有线程都会受到这次调用的影响。但是在Linux系统中,只有调用了setuid的那个线程才会做出相应的反应,而其他线程仍旧维持原状。一些POSIX进程在创建时可能具有超级用户的权限(比如系统管理员编写的程序),因此它们具有完全的系统访问权,比如可以删除计算机上的任何用户文件等等。出于安全原因的考虑,在这些程序完成一些只有超级用户才能完成的工作后,往往会调用setuid来降低它们自己的权限。如果这样一个符合POSIX标准的程序运行在Linux系统上,我们会发现那些在调用setuid之前创建的线程在调用后都还保持着超级用户的权限。那么对于一个有害的程序,也许它不能够在符合POSIX标准的系统上兴风作浪,但它却能够在Linux系统中干尽坏事。
也许有人会考虑开源操作系统的开发者们也可以进行开放的操作系统标准的研究。但实际上,Linus Torvalds本人就是不赞同POSIX的,他说:“POSIX是一个发育不健全的标准,是成不了气候的。我们不准备按照POSIX标准来设计操作系统。”那么Linux会成为一个与POSIX标准相兼容的操作系统么?Linus又说:“现在的情况是,没有理由去改变现状。”