
嵌入式系统中的微模块化程序设计,通过实用状态图,采用C/C++语言实现。
5星
- 浏览量: 0
- 大小:None
- 文件类型:None
简介:
状态模式在 GoF 的 23 种设计模式中被广泛采用。本文旨在探讨状态模式的有效运用,并分享一些个人体会,并非详尽地涵盖所有方面。
状态模式主要用于设计状态机,因此在后续的描述中,我们将等同理解它们。如果您对状态机设计有深入的研究需求,建议参考一本权威著作:《Practical Statecharts in C/C++ Quantum Programming for Embedded Systems》,中文名是《嵌入式系统的微模块化程序设计-实用状态图C/C++实现》,由北航出版社出版,作者是 Miro Samek 博士,他在嵌入式实时系统开发领域拥有丰富的经验。这本书对于深入理解状态机领域无疑是一本不可多得的参考资料。
让我们先观察一种传统的“古老”的状态机实现方式,以 C 语言为例。通常情况下,我们可以将能够接收事件(消息)的系统称为事件驱动系统。系统会处于某个特定状态,等待外部的激励信号。这些激励可能来自外部事件或定时器超时等触发条件。当系统收到这些事件后,会执行相应的处理逻辑并过渡到新的状态(或者保持当前状态不变),然后继续等待下一个激励的到来,直到事务处理完成为止。
典型的状态机实现需要考虑几个关键要素:包括明确的状态定义、消息(及其内容)、消息处理函数以及系统上下文数据等信息。当系统处于某个特定状态时,收到某个消息后会解析出消息内容并调用相应的消息处理函数进行处理。而消息处理函数往往会利用状态机的上下文数据来完成任务;完成消息处理后,系统会根据情况跃迁到新的状态。
典型的代码示例大致如下:
```c
switch (state) {
case STATE1:
switch (msg) {
case MSG1:
HandleMsg1(msgpara, context);
nextstate(STATE2);
break;
case MSG2:
HandleMsg2(msgpara, context);
nextstate(STATE3);
break;
/*...*/
}
break;
case STATE2:
switch (msg) {
case MSG3:
HandleMsg3(msgpara, context);
nextstate(STATE3);
break;
/*...*/
}
break; /*...*/
}
```
可以看到这就是所谓的平面状态机,其特点在于先列出所有可能的状态,然后再列出所有可接收的消息;如果找不到匹配的消息则直接丢弃该消息。
为了提升状态机的运行效率,这里提供一些小技巧总结如下:
(1)优先处理概率较高的消息:在同一个状态下,应该优先将最有可能接收到的消息放在前面进行处理。一个状态下可能需要处理多种不同的消息类型,而每种消息被接收的概率是不一样的;因此将接收概率较大的消息放在前面可以减少 `switch` 语句中的比较次数从而提高执行效率. 对于单个的状态机而言这种优化可能微乎其微, 但如果你的系统中同时运行成千上万个这样的状态机, 那么就值得考虑这种优化策略了.
(2)查表法:通过查表法可以消除枚举所有可能的 state 和 message 的开销. 我们可以将每个 message 处理函数的指针存储在一个二维数组中, 其中一维代表 state, 另一维代表 message 的序号. 通过 p[state][msg] 可以直接定位到当前 state 下当前 message 的处理函数. 对于简单的应用场景, 甚至可以将新 state 也存储在二维表中, 这样用户无需显式地调用 state 跃迁函数. 然而对于存在不同执行路径的状态迁移的情况, state 的跃迁可能需要在 message 处理函数内部进行操作.
(3)消息分段再查表:一般来说, 一个 state 机的状态数量不会很多, 接收的消息数量也有限. 但通常情况下, message 是不连续的. 因此使用查表法可能会导致内存开销过大, 特别是在 message 序号比较稀疏的情况下. 为了解决这个问题, 可以将 message 进行归类分段. 例如可以将接口的消息定义成一段. 通过分段后可以将message存储在一个相对紧凑的内存空间中, 然后再运用查表法来提高效率和降低内存占用之间的平衡关系. 需要注意的是这并不是绝对有效的策略 , 这取决于具体的应用场景 。 系统收到一条信息后首先判断该信息属于哪个分段区域然后调用 p[state][msg - offset] 来进行相应的处理 。
全部评论 (0)


