引言
很多嵌入式系统应用于各种航天、军工与医疗等高安全需求的工作环境中,系统出现问题可能带来巨大的经济损失,甚至危及人的生命。嵌入式应用中虽然有很多像Qt/Embedded、MiniGUI之类的图形界面处理软件或工具包可以辅助系统设计,但在很多情况下中却无法使用这些软件或工具包。比如,在恶劣环境下为了保证其电磁兼容性,所使用的设备只能采用高可靠性的特殊单色屏,在该应用下使用这些软件或工具包已不具实际意义。一些基于Linux帧缓冲(Frame Buffer)设备来实现图形界面的嵌入式系统,需要自行设计图形界面显示、输入/输出设备读/写和业务控制流程处理等功能模块的交互方案。一些软件人员没有仔细考虑软件设计,而是随心所欲构建系统。这样的设计行为会导致各模块错综复杂地纠缠,使系统各功能模块具有极高的耦合性,最终使得软件设计思路混乱、层次不清、实现极度僵化,尤其不利于系统的扩展、修改与维护。这些应用的软件架构需要一种实用、简捷的设计模式来解决上述设计问题,从而保证系统的可靠性。
本文结合职责链设计模式与命令设计模式、C++的一些语言特性和Linux系统的消息队列,针对采用Linux帧缓冲设备的嵌入式系统给出了一个统一的软件架构设计模式。该设计模式对系统各功能模块进行有效的解耦合,以简化软件的实现、测试与维护,从而提高系统的可靠性。
1 典型嵌入式系统结构
本文的设计模式是基于图1所示的典型嵌入式系统结构。该系统包括了界面显示、输入/输出设备管理、业务控制流程管理以及数据管理4部分功能模块。其他类型的嵌入式系统都可在此基础上缩减或扩展,其基本的软件设计思路是完全相同的。系统的一致性为研究统一软件架构设计模式提供了广阔的应用空间。
图1 典型嵌入式系统结构
2 设计模式
利用设计模式可以解决一些重复的、相关的问题。在分析嵌入式工程师惯用的糟糕设计的基础上,阐述相关的设计模式基础和本文所提出的软件架构设计模式。
2.1 糟糕的设计
在很多嵌入式系统设计中,工程师随心所欲地构建了系统,设计的系统把界面显示、输入/输出设备管理、业务控制流程管理以及数据管理功能模块纠缠在一起,导致设计的系统具有极高的耦合性。在参考文献[1]中用一种名为大泥球的模式语言贴切地描述了这样的软件系统。
系统数据结构的构造非常随意,甚至几乎不存在。任何事物之间都要进行通信,所有重要的状态数据都可能是全局的。在状态信息被隔开的地方,需要通过错综复杂的后端通道杂乱地传递,以绕开系统的原有结构。变量名和函数名信息量不足,甚至会起误导作用。函数可能使用大量全局变量以及定义模糊的冗长的参数列表。函数本身冗长、费解,完成多项毫无关联的任务。代码重复很多,控制流很难看清,难以找到来龙去脉,程序员的意图几乎无法理解,代码完全不可读。代码中有很多经过多位维护者之手不断修补留下的明显印记,这些维护者几乎没有考虑到自己的修补会造成怎样的后果。
该设计方式带来一系列拙劣的问题。软件设计思路混乱、层次不清,实现极度僵化,尤其不利于可能的扩展与修改。重复代码多,极为繁杂,基本上无代码重用的可能。各代码职责不明、编写随心所欲、基本无法控制代码结构,不利于代码阅读,造成其他人对代码的测试、修改与维护极其困难。随着时间的流逝,连编程者自己都看不懂所写的代码。
总之,这样的设计非但谈不上设计之美,就连最基本的设计要素也不具备,只是勉强能用而已。软件的设计与实现者不清楚这样的软件实践:程序代码被阅读和修改的次数远远多于它被编写的次数,保持代码易读、易修改的关键就是重构。程序代码的读者是谁?面对一个类似大泥球的程序代码,谁愿意为此浪费时间和精力呢?绝大部分情况下,程序维护与修改的人员都采用了这样的策略对待此类程序:如果它还可以运行,就不要动它[23];即使万不得已修改,也只是增加一些更加混乱的代码,实现功能上的修改。
由于很多嵌入式系统应用于各种航天设备、军工设备等高安全需求的环境中,系统出现问题可能带来巨大的经济损失,甚至危及人的生命;而一个设计糟糕的系统很难甚至不可能确保其运行行为,尤其是现在的系统越来越复杂,该问题将越来越严重。嵌入式系统的软件架构需要一种简捷、易于实现的设计模式,从而使程序代码易于读写、修改和维护。只有系统在清晰的逻辑控制下运行,才能保证可靠性与稳定性。
2.2 模式设计的基础
在职责链设计模式和命令设计模式的基础上,提出了本文的软件架构设计模式。
职责链设计模式的思想是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间产生紧密的耦合关系。将这些接收对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。提交请求的对象并不明确地知道哪一个对象将会处理它,当然它也无须知道,但是结果是该请求被某个对象处理了。从第一个对象开始,链中收到请求的对象要么亲自处理请求,要么把请求转发给链中的下一个候选者。
命令设计模式将请求本身封装成一个命令对象,以便向一个应用对象提出请求。该命令对象可被存储,也可像其他对象一样被传递,应用对象知道为该请求执行正确的操作。该模式的关键是采用了一个抽象的命令类,它定义了一个执行操作的接口。命令设计模式通过将请求封装到一个对象中,并将请求的接受者存放到具体的接收对象中,从而对调用操作的对象和知道如何实现该操作的对象进行有效的解耦合[4]。
2.3 具体模式设计
采用职责链设计模式,可以很好地处理嵌入式系统的键盘按键请求。嵌入式系统的图形界面中可以响应按键的对象都可作为接受请求的候选者,可以让系统处于激活链最末端的那个对象处理请求,从而保证图形界面上只有当前激活的界面对象可处理键盘请求。
图形界面中诸如按钮这样的对象,它们响应用户控制请求,但不能显式在按钮类代码中实现该请求对应的操作,只有具体的应用代码(框架代码)才知道按钮所对应的具体操作。采用命令设计模式和控制转置原则可有效地解决,诸如按钮、菜单项、编辑框等控件在响应确认按键执行不同操作的问题[46]。在C++语言中采用函数对象和虚基类机制可很容易地解除图形界面调用操作的对象和知道实现该操作的对象的耦合关系。
图2 系统的数据流图
利用Linux系统的消息队列把输入/输出设备管理、图形界面和业务控制模块进行有效的解耦合,实现原理如图2所示。把输入/输出设备管理模块单独放置在一个进程中,采用I/O复用模型或多线程阻塞方式来处理每个设备的输入输出[7],把从外设获得的数据通过消息队列发送给业务控制流程模块,并从消息队列接收要发送到外设的数据。把业务控制模块放在另一个进程中,根据控制流程处理各种输入/输出数据以及更新图形界面显示,但不直接对外设进行操作(该操作由输入/输出设备的管理进程实现),只是对消息队列进行读写处理。
通过消息队列可以可靠地传递各设备发送或接收的数据。由于消息队列具有缓冲功能,该实现方法可防止输入设备读取数据(如键盘按键信息)的丢失问题[8]。同时,排除了没有解耦合的设计中业务控制模块因为要同时处理外设,外设的阻塞操作导致系统用户界面操作响应迟钝的问题。
3 应用结果
本文提出了一种实用、简捷的设计模式,并给出了一个原型软件,该原型软件完全体现了该设计模式;演示了两个窗口(MainFrameWin和SecondFrameWin),以及这两个框架窗口中一些界面元素的数据交互,同时用键盘和系统时钟模拟了输入设备(输出设备的处理类似);并针对嵌入式系统经常采用的一键控制给出了实现示例。在该原型软件基础上,可以很容易地进行扩展以满足特定应用要求。
由图3的示例可知,该设计模式完全满足常规嵌入式系统的应用要求,针对特殊的应用要求,进行适当的修改即可。该设计模式在一些高安全需求的军工设备上已得到应用,实现效果良好,从根本上解决了糟糕的设计模式带来的问题。
图3 各操作实例界面
4 结论
本文结合职责链设计模式和命令设计模式,利用C++的语言特性和Linux系统提供的消息队列给出了一种适合于经典嵌入式系统的软件设计模式。该模式解决了嵌入式系统设计中一些繁杂的问题,并在实际应用中取得了良好的实现效果。