基于VxWorks的消息控制软件设计方法
2022-04-29杨帆
杨帆
关键词 消息控制软件 软件架构设计
VxWorks 操作系统以其高实时性和友好的用户开发环境被广泛应用于各种消息控制软件中[1] 。但是,在设计消息控制软件时,经常会出现各模块之间互相调用复杂、软件结构设计过于随意的情况。这会使软件的可读性、可靠性较差,并且造成了软件在维护阶段成本太大、无法快速复用、复用时具有隐患的问题。本文针对这类软件,结合VxWorks 操作系统的互斥资源访问和实时性的特点,分析了软件设计中遇到的种种缺点和不足,提出了链路信息控制软件的新的设计方法。
1设计消息控制软件存在的问题
对于消息控制软件来说,基本都应该包含以下模块:RS422 串口、RS232 串口、以太网口等硬件接口的收发模块,以及消息编码解码模块、链路消息监控过滤模块、链路消息分析的算法模块、硬件自检和配置文件读取模块等。这些模块可以支撑一个比较基础的消息控制功能的软件。
在设计消息控制软件时需要考虑模块之间的通信和相互调用的问题[2] 。在传统C 语言程序中,如果一个模块的功能函数要使用其他模块的函数,最简单的方式是直接调用。但是,这会使得模块之间过分相互依赖,导致紧耦合,不利于软件的模块化设计。由于模块间的相互调用,使得这些模块无法复用到新的程序中,造成有新需求时需要对这些模块进行重新开发,无法做到在原来基础上进行扩展。
在消息控制软件中经常会处理一些任务,如在固定周期时间内重复处理多次业务;延迟n 秒后,若未收到回执应答,则周期时间内处理重复n 次的业务模型。一方面,在VxWorks 操作系统中,我们可以使用看门狗或者全局信号量的方式来处理定时触发的任务。但是,在看门狗中执行的函数本质是在中断上进行执行的,存在无法嵌套等约束条件,并且在部分硬件系统中无法完整支持。另一方面,利用全局信号量的方式则存在代码不统一和可读性差的问题,无法满足高内聚低耦合的设计需求。通常在一些成熟的面向对象语言的框架中,会提供setTimer()、onTimer()相关接口,使用接口回调函数的方式去重复处理业务。VxWorks 操作系统一般使用C 语言进行开发,支持延迟若干秒后周期处理业务的模块[3] 。另外,还需要考虑线程之间通信使用全局变量的问题。尽管VxWorks 操作系统推荐了一些通信手段,但在一些C语言程序中经常会出现没有保护的全局变量直接进行线程通信的情况,十分不利于线程的安全,造成大量的隐患,本文设计了一个软件模块来定制全局变量的访问改变。
2消息控制软件设计
2.1消息控制软件的模块架构
在以往的消息控制软件中虽然也有链路监控、消息编解码、消息过滤等模块,但是模块和模块之间是相互调用的。我们需要设计一个中介模块来统一调度各个模块的对外接口。此外,我们还需要在每个模块中增加一个接口层,在分析每一个模块对外有哪些接口后,将这些接口以句柄的方式统一起来,作为对外的接口层。
以消息过滤模块为例,过滤模块的接口层声明的示意代码如下:
消息过滤接口是在原来消息过滤的代码中添加一个新的接口层。在消息过滤接口模块的声明中,首先需要声明消息过滤模块中函数的id———可以以宏定义的形式声明,也可以用枚举量的形式声明。此外,还需要声明一个send_msg_to_filter_function 的句柄,其作用是接收所有从中介模块发过来的消息,并根据function_id 选择过滤模块中正确的执行函数。将原有对外传送消息的函数统一在send_msg_to_filter_function 句柄中,好处是可以不用过多暴露本模块的接口,做到统一管理。另外,还需要将send_msg_to_filter_function 句柄放在中介模块存储中,具体存储在中介模块的句柄集合中,据此中介模块的句柄集合包含了所有模块的对外句柄,使得其他模块调用消息过滤模块时,都需要通过中介模块。
在中介模块中,需要声明一个mediator _recv _handler_list 的数组。这是一个存放其他所有模块接口句柄的集合。中介模块还需要声明一个关于包含相关模块的枚举量,其可以定义每一个模块值。send_msg_to_function 是中介模块对外的接口。当一个模块需要调用过滤模块中的函数时,只需要调用中介模块的send_msg_to_function 并填写好Msg 结构体。send_msg_to_function 函数检查mediator_recv_handler_list是否存在过滤接口模块接口层的句柄,如果存在,则调用消息过滤模块接口层的句柄,否则返回调用失败的错误值。基于此,在其他模块调用消息过滤模块中的函数时,都需要通过中介模块。调用模块可以不关心过滤模块是否有相关函数(如果没有也不会编译报错),消息过滤模块不需要关注是哪个模块调用了自己。做到了模块之间的松耦合,使得模块之间独立存在、自由组合。通过在消息过滤模块中分出接口层,做到模块内部的高内聚。
2.2定时器的封装
封装的定时器主要是由节拍器、订阅器、订阅者三个部分组成。节拍器根据CPU 指令周期,每秒钟向订阅器发送信号量。订阅器收到信号量后,每秒循环遍历订阅器中定义的结构体鏈表,检查链表中注册的订阅者是否符合触发条件。如果符合触发条件,则开启线程执行订阅者所注册的update 事件,并在订阅器结构体链表中注销订阅者。订阅者需要定义具体的update 事件,其指的是满足具体条件后需要执行的函数。订阅者需要在订阅器中注册,注册在订阅器的链表内。
订阅者的结构体中声明了需要延迟多少秒触发,触发几次,周期为多少秒,并且包含一个函数指针update,指向需要触发的事件函数。
2.3全局变量的使用
重构老代码时通常会遇到全局变量的使用问题。在无保护的情况下,频繁在多线程任务下读写全局变量是存在隐患的。此时可以将全局变量的读写单独放在一个.c 文件中,其中的每一个全局变量的读取都要在保护之中。方法是在读取全局变量时使用get(),读取全局变量时使用set(),使用set 和get 函数时加互斥信号量。
VxWorks 操作系统提供了互斥信号量来解决任务之间存在的同步问题。使用互斥信号量可以在访问修改全局变量时具有排他性,在初始化互斥信号量时,将属性设置为SEM_Q_FIFO,据此访问全局变量的请求就可以符合先来先得的顺序。这可以避免线程之间调用时可能发生的问题。
3结束语
好的软件是可维护的、可扩展的、可复用的,且灵活性强。可维护是指当遇到新需求时,只需要更改需要更改的部分,改动量越小越好;可扩展性是指新增功能时,事先预留了接口,之前设计的接口是通用的;可复用是指一个新的模块是可以一直使用的,不需要重新编制;灵活性是指模块间可以任意调配使用,不需要打破原有软件架构去实现新的功能。但是,我们编制代码时不一定完全符合以上特性,往往在开发阶段为了快速开发新功能,不注意这些开发原则,为代码的二次开发埋下了隐患。本文针对消息控制软件,参考以前的代码的一些编写弊端,提出了一种软件设计方法,以便重构代码和设计新代码,充分满足高内聚低耦合的特性。