4

背景:

我们正在为新的嵌入式系统建模固件。目前固件正在 UML 中建模,但不会使用 UML 建模工具的代码生成功能。

目标语言是 C(具体来说是 C99)。

低功耗(即性能、快速执行)和正确性很重要,但正确性是重中之重,高于一切,包括代码大小和执行速度。

在对系统建模时,我们已经确定了一组定义明确的组件。每个组件都有自己的接口,并且许多组件与许多组件交互。

模型中的大多数组件将是实时操作系统 (RTOS) 下的单个任务(线程),尽管有些组件只不过是库。任务完全通过消息传递/队列发布相互通信。与库的交互将以同步函数调用的形式进行。

因为建议/建议可能取决于规模,所以我将提供一些信息。现在可能有大约 12-15 个组件,可能会增长到大约 20 个?不是 100 多个组件。假设平均而言,每个组件与 25% 的其他组件交互。

组件图中,有端口/连接器用于表示组件之间的接口,即一个组件提供另一个组件所需的东西。到目前为止,一切都很好。

这就是问题所在:在很多情况下,我们不希望“组件 A”能够访问所有“组件 B”的接口,即我们希望将组件 A 限制为组件 B 提供的接口的子集。

问题/问题:

是否有一种系统的、相当直接的方式来执行——最好是在编译时——在组件图上定义的接口契约?

显然,编译时解决方案比运行时解决方案更可取(更早的检测,更好的性能,可能更小的代码)。

例如,假设库组件“B”提供函数 X()、Y() 和 Z(),但我只希望组件“A”能够调用函数 Z(),而不是 X() 和 Y() . 同样,即使组件“A”可能能够通过其消息队列接收和处理大量不同的消息,我们也没有任何组件能够向任何组件发送任何消息。

我能想到的最好办法是为每个组件-组件接口设置不同的头文件,并且只公开(通过头文件)组件允许使用的接口部分。显然这可能会导致大量的头文件。这也意味着组件之间的消息传递不会直接使用 OS API 完成,而是通过函数调用完成,每个函数调用都会构建并发送特定的(允许的)消息。对于同步调用/库,只会公开允许的 API 子集。

对于这个练习,你可以假设人们会表现得很好。 换句话说,不要担心人们直接作弊和剪切和粘贴函数原型,或者包含他们不允许的头文件。如果不允许,他们不会直接从“A”向“B”发布消息,依此类推……

也许有一种方法可以通过编译时断言来强制执行合同。也许有一种更优雅的方法可以在运行时检查/执行它,即使它会产生一些开销。

代码必须干净地编译和 lint,所以“函数原型防火墙”方法是可以的,但似乎可能有一种更惯用的方法来做到这一点。

4

3 回答 3

2
#ifdef FOO_H_

   /* I considered allowing you to include this multiple times (probably indirectly)
      and have a new set of `#define`s switched on each time, but the interaction
      between that and the FOO_H_ got confusing. I don't doubt that there is a good
      way to accomplish that, but I decided not to worry with it right now. */

#warn foo.h included more than one time

#else /* FOO_H_ */

#include <message.h>

#ifdef FOO_COMPONENT_A

int foo_func1(int x);
static inline int foo_func2(message_t * msg) {
    return msg_send(foo, msg);
}
...

#else /* FOO_COMPONENT_A */

  /* Doing this will hopefully cause your compiler to spit out a message with
     an error that will provide a hint as to why using this function name is
     wrong. You might want to play around with your compiler (and maybe a few
     others) to see if there is a better illegal code for the body of the
     macros. */
#define foo_func1(x) ("foo_func1"=NULL)
#define foo_func2(x) ("foo_func2"=NULL)

...
#endif /* FOO_COMPONENT_A */

#ifdef FOO_COMPONENT_B

int foo_func3(int x);

#else /* FOO_COMPONENT_B */

#define foo_func3(x) ("foo_func3"=NULL)

#endif /* FOO_COMPONENT_B */
于 2010-10-26T21:14:38.890 回答
2

带有头文件的想法是合理的,但是,根据组件之间的交错,将每个组件的接口划分为具有自己的头文件的多个子类别,而不是为每个组件提供头文件可能会更清晰-组件连接。

子类别不一定是不相交的,但要确保(通过预处理器指令)您可以混合类别而无需重新定义;这可以通过为每个类型或函数声明创建一个带有自己的包含保护的头文件,然后从这些原子块构建子类别头来以系统的方式实现。

于 2010-10-26T20:27:15.140 回答
1

您应该考虑按照nategoose 在他的回答中提出的建议创建一种迷你语言和一个简单的工具来生成头文件。

要在该答案中生成标题,如下所示(调用它foo.comp):

[COMPONENT_A]
int foo_func1(int x);
static inline int foo_func2(message_t * msg) {
    return msg_send(foo, msg);
}

[COMPONENT_B]
int foo_func3(int x);

(并扩展示例以提供可供多个组件使用的接口):

[COMPONENT_B, COMPONENT_C]
int foo_func4(void);

这将很容易解析和生成头文件。如果您的接口(我特别怀疑消息传递可能是)比我上面假设的更多样板,您可以稍微简化语言。

这里的优点是:

  1. 一些语法糖,使维护更容易。
  2. 如果您以后发现更好的方法,您可以通过更改工具来更改保护方案。改变的地方会更少,这意味着你更有可能做出改变。(例如,您以后可能会找到 nategoose 建议的“非法宏代码”的替代方案。)
于 2010-10-26T21:37:43.780 回答