利用Java隔离机制应对软件复杂性
硬件规模的指数式增长显著提高了嵌入式系统软件的容量和复杂性。
对许多嵌入式软件产品而言,每个新版产品中所含的软件数量几乎每隔18到36个月就要翻倍。管理软件规模的这种快速扩张所面临的挑战要求创新的方法和改进的软件工程规则。
处理现代嵌入式系统软件不断增长的规模和复杂性的要点之一是将这些大型系统分成许多较小的组件,每个组件与相邻组件之间都通过抽象封装和隔离屏障实现隔离。这些屏障可确保每个组件的开发人员不会受其它组件的干扰而影响其正确操作。
使用分而治之方法管理软件复杂性的总体策略是软件工程的基本原则。然而,对嵌入式和实时软件来说这种方法的实用性至今也未被人们完全认识到,这里面有两大原因:
1. 大多数嵌入式系统要比相应的台式系统和“大型机”系统小得多。
2. 许多早期采用大型机(非嵌入式)软件工程组织提供的面向对象的封装和隔离原则的想法忽略了这样的事实,即嵌入式实时软件工程具有特殊要求,这些要求是主流抽象服务所无法满足的。
更有甚者,非嵌入式软件开发人员几乎经常被告知要忽略资源约束问题,以便专注于功能的正确性、通用性,并方便软件维护。然而,嵌入式实时软件开发人员无法忽视这些问题。正是这些问题使得嵌入式实时软件的开发和维护变得如此具有挑战性。
基于这个理由,需要利用高效嵌入式实时软件工程中的封装和隔离机制帮助开发人员在每个组件开发人员要求的抽象等级对资源约束问题进行隔离和封装。本文将介绍一些实用的机制来帮助嵌入式实时Java软件开发人员应对这些挑战。
在典型的嵌入式实时软件系统中,每个关键软件组件代表了不同的要求和考虑。对于在实时嵌入式系统设计中使用Java的开发人员来说,有必要仔细权衡硬实时和软实时性能,以达到特定的目标要求。
硬实时约束指的是在错误时间执行的操作将导致零或可能负值。“硬实时”的含义是指在实施之前利用理论性的静态分析技术证明与所有时序约束条件的兼容性。软实时约束指的是在错误时间(太早或太迟)执行的操作将导致正值,即使在正确时间执行时具有更大的值。期望目标是软实时系统使用经验(统计)测量和资源预算的启发式执行来提高软件与时序约束一致的概率。
需要注意的是,硬实时和软实时之间的差异并不取决于针对期限性或周期性任务定义的时间范围。软实时可能有100μs的到期时间,而硬实时系统的到期时间可能达3秒。
在高可靠的Java程序中满足实时约束条件是可能的。开发人员必须认识到一些特殊Java方法的优缺点,例如自动碎片收集,并仔细选择最合适的机制来满足每个特殊系统要求。对于想利用Java语言提高开发效能并降低软件维护成本的开发人员来说,可以参考Kelvin Nilsen编写的“可扩展Java实时系统开发指南”中的建议集。
从实用角度讲,软实时Java开发指南通常是首选方案,除非有特殊约束将它们排除在外。这是因为软实时Java提供了最佳的开发工具和软件维护效能。软实时Java指南包括了使用JSE类型的传统Java API以及专门实现的虚拟机来支持实时行为。这些虚拟机支持在所有同步锁定实现中的占先式和增量式实时碎片收集、固定优先级调度以及优先级继承。
而硬实时Java技术并不使用自动碎片收集。相反,是在更直接的编程器控制下对动态存储器进行分配和去分配。硬实时Java指南能够使部署速度快上3倍,而使用的存储器容量不到传统Java实现要求的十分之一。而且确定性响应延时更要好上千倍。对于安全性很重要的开发来说,Java编程人员可以使用硬实时技术的受限子集。
临时对象的存储器分配
由于Java是面向对象的编程语言,所有结构化数据都表现为对象。在传统的Java运行时环境中,所有对象都位于一个称为堆(heap)的存储区内,这些对象用的存储器将被自动碎片收集器重新声明。在针对硬实时开发的建议指南中没有自动的碎片收集器。
在传统的模块结构化语言(如Ada, Modula和Pascal)中,对指定过程局部声明的记录(相当于Java的对象)在内部嵌套过程中是可见的。因此不可能将一个对象的地址直接挎贝进编程人员声明的变量中。这就为临时对象的堆栈分配提供了一种安全机制。但Java不是模块结构化语言。它更直接得益于C和C++,因此不支持嵌套过程的声明。
在C和C++中,编程人员还可以声明属于一个函数局部的结构。这些结构被分配在运行时堆栈上,它们的存储器在返回封闭函数时将被自动再次声明。由于C和C++不允许内部嵌套函数,函数共享访问其堆栈分配的局部结构的唯一方式是将这些对象的地址用全局变量或作为输入参数传递给调用的子程序。
这种常见的C和C++操作的危险之一是缺少编译器强制性保护。这种保护可以确保堆栈分配对象的参考存活时间不比它们参考的对象更长。利用C和C++可以很容易为不再存在的对象创建活动指针。不管何时发生,这些编程错误都是最难调试的。
根据硬实时Java指南,通过一些特殊配置,可以允许安全性很关键的Java组件利用与C和C++编程语言相似的编程结构在运行时堆栈上分配对象。与C和C++不同的是,静态强制的编程人员注释能使安全性关键的Java编译器确保堆栈分配的存储器不会形成活动指针(图1和图2)。
图1中从第3行到第6行中每个语句都分配一个新的Complex对象。根据硬实时Java开发惯例,所有4个被分配的对象都是在包含该代码的方法的堆栈活动帧中分配的。
图1:包含堆栈分配对象的程序例子。
在图2中,各种@ScopedPure注释代表所有方法的输入参考参数,包括其固有的这个参数,可能指位于一些外部嵌套方法的堆栈分配活动帧中的对象。在识别出这个注释后,安全性关键的Java编译器就能确保这些输入参数永远不会被挎贝到比参考参数所参考的对象更长寿命的任何变量。
图2:堆栈可分配Complex类的源代码包含有丰富的@ScopedPure注释,表明所有方法的输入参考参数可以参考一些外部嵌套的堆栈分配活动帧中的对象。
临时对象的安全堆栈分配的关键好处包括:
1. 非常快速的临时存储器分配和去分配
2. 非常可靠的临时存储器分配,因为堆栈从来不会被分割,最大堆栈尺寸可以通过静态分析决定
3. 用于Java实时规范(RTSJ)运行时检查的性能和外形开销以及关键RTSJ程序组件破坏时不会被放弃的风险组成了存储器协议。
这使得安全性关键的Java编程人员拥有与更传统的语言(如Ada,C和C++)相当的能力。
硬实时组件和软实时组件之间的协作
大多数任务关键型软件系统由多个软件层组成,其中较低层更静态,具有更严格的实时约束条件和更高的性能要求。而较高的软件层一般需要支持更动态的行为,要求动态分配数据结构,甚至动态装载和卸载代码。高可靠、任务关键型Java指南支持硬实时组件和软实时组件之间的协同配合。软实时任务关键组件采用运行在实时增强型虚拟机上的标准JSE库实现。
硬实时组件运行时的外形和吞吐量效率通常非常接近优化过的C(在5%到10%之内),超过在重要的CPU增强测试标准上实现的典型最优化Java性能的三倍。许多重要的关键任务需求可以通过它来满足,比如:
1. 便携式和非常高效的设备驱动程序(可能但不必具有硬实时约束)可以用安全性关键的Java编程注释实现。
2. 与使用Java固有接口(JNI)相比,如果将安全关键性Java环境用作传统Java和固有Java之间的中间媒体,并实现与用其它语言编写的传统(“固有”)组件连接的接口,那么这个接口更有效也更安全。
3. 性能关键性代码(如傅里叶分析和矩阵运算)用作安全关键性Java组件比用作传统Java代码或通过JNI与Java相连的传统C代码更有效率。
和传统Java组件一起实现控制的选择性共享
实现硬实时组件与传统Java组件高效可靠集成的推荐方法是使用硬实时和传统Java域之间共享的限制性对象。共享对象通常位于硬实时域中,并不参与碎片收集。
由于这些是硬实时对象,它们永远不会被重新分配。这就极大地简化了实现,改进了执行的效率。
设备驱动程序的实现例子
下面介绍一个简单的完全用Java编写的中断处理型设备驱动软件组件。图3所示的接口声明用作软和硬实时域之间的桥梁。对于任何实现这些接口、刚好与传统Java域共享的硬实时Java对象,传统Java环境只能执行这些接口中定义的方法。
图3:这些接口描述了硬和软实时环境之间的共享控制。
图4定义了DeviceBuffer类,图5为代表中断服务例程的中断类提供了类常数的声明和实例变量。图6和图7完成了中断类的实现。
图4:共享设备驱动程序缓存的实现。
图5中第24行到26行所示的ceilingPriority()方法是必要的,因为这个类实现javax.realtime.util.sc.Atomic接口。对于实现这类接口的类来说,要求编程人员提供这种方法-该方法在类体内生成了符合语法的标志,它可以用来建立有关对象同步行为的静态属性。
图5:为中断类指定恒定和即时变量声明。
图6中第28行到43行所示的构造器对中断处理器操作要求的三个I/O端口进行了实例化。对于任何给定的硬件配置,存储器和I/O地址空间的某些范围将用作I/O端口,它们可接受硬实时Java组件的访问。
图6:第28行到第43行给出了构造器,而第45行到58行提供了实际的中断处理代码。
为了确保这个硬实时Java组件能够访问请求的I/O端口,在这些I/O端口被实例化时需要执行范围检查。一旦实例化后,在读取或写入I/O端口时就不需要再另外检查了。除了实例化必要的I/O端口外,中断构造器也要分配存储器来描述一对输入缓冲器。
图6中的45行到58行提供实际的中断处理代码。这种方法被声明为同步的,因为在运行时,所有相同或较低优先等级的其它中断被禁止运行。因为这个类被声明用来实现Atomic接口,安全关键性Java字节代码检查器可以确保每个同步方法的本体是受执行时间约束的。这限制了可从中断处理器中调用的服务集。
字节代码检查器禁止中断处理代码调用可能阻塞的服务,同时保持优先级高限锁定。字节代码检查器还能确保只有实现Atomic接口的对象才能将它们的高限优先级设置到对应硬件分派的中断处理范围。
图7所示的方法可以实现将字节数据高效地从硬实时中断处理器流向传统的Java域。该方法将参考返回给DeviceBuffer对象。需要注意的是,DeviceBuffer实现TraditionalJavaBuffer接口。这样,就能利用传统Java域将参考共享给返回的DeviceBuffer。如果共享,传统Java环境就能调用validBytes()和byteAt()方法。
图7:getReadBuffer()方法允许字节流从硬实时中断处理器高效地流向传统的Java域。
中断类并不实现TraditionalJavaDevice接口,因此无法与传统Java环境共享中断实例。图8提供了中断子类ShareDevice的定义,该子类可实现TraditionalJavaDevice接口。ShareDevice实例可以与传统Java域共享。
图8:SharedDevice子类实现TraditionalJavaDevice接口。
值得注意的是,传统Java getBuffer()方法直接通过NoHeapRealtimeThread.transfigure()调用this.getReadBuffer()。transfigure()服务具有将当前运行的传统Java线程转换成硬实时线程的效果。因此,该线程可以执行硬实时同步操作。
如果允许传统Java线程执行硬实时同步,无需先对线程进行transfigure,我们就会给硬实时环境引入许多调度复杂性,进而很难进行分析。虽然transfigure操作非常高效,但线程transfiguration和同步要比简单的方法调用更昂贵。这也是这种共享设备驱动程序处理每个缓存内容无需任何进一步同步的原因。
本文小结
通过适当留意资源管理细节,开发人员就能利用Java语言实现硬实时任务。硬实时Java技术可以比传统Java技术提供更好的性能和确定性以及小得多的存储器体积。硬实时Java组件可以与大型系统高效完全地集成,而大型系统一般都整合有软实时和硬实时功能。使用便携式固定格式Java而不是汇编、C或C++进行低等级软件组件的开发和维护可以显著提高产能,并节省典型关键软件系统整个生命期的成本。
本文来源:Aonix公司 作者:Aonix公司 首席执行官 Kelvin Nilsen
关于 嵌入式软件 的相关解决方案
- 2007-12-27利用Java隔离机制应对软件复杂性
- 2007-12-06外围设备或器件软件仿真的方法及建议
- 2007-12-03嵌入式软件的能耗优化
- 2007-11-29嵌入式软件测试
- 2007-11-20MP3+DAB的应用解决方案
嵌入式软件 相关产品动态
- 2007-12-25XMOS Semiconductor发表第一款SDS
- 2007-12-11EqualLogic推出新款SCSI存储产品PS3700X
- 2007-11-19英特尔推45纳米Penryn处理器 软件合作伙伴成主角
- 2007-11-12研华SUSI v2.0 让您实现节能和数据保护
- 2007-11-08研华SUSI V2.0 让您实现节能和数据保护

