【问题标题】:State Machine with no function pointer没有函数指针的状态机
【发布时间】:2016-04-28 14:54:01
【问题描述】:

我已经为安全 SIL 4 系统实现了一个具有大量状态转换的复杂状态机。 这个实现的主干是使用函数指针完成的。 当一切顺利时,V&V 反对在 SIL 4 系统中使用函数指针。参考 - Rule 9 NASA.Misra C 2004 但是没有说不能使用函数指针。

有没有其他方法可以在没有任何函数指针的情况下实现复杂的状态机?

【问题讨论】:

  • 函数指针与状态机有什么关系?
  • func 指针不断指向基于事件的新函数。程序流程易于追溯且简单。我知道 SM 也可以使用 switch 语句来实现,但是,有许多条件必须检查 switch 中的转换。因此我选择了 func 指针
  • 不要用 cmets 扩展您的问题。如果您想添加信息,请编辑您的问题。如果你想让读者回答你应该给他最好的信息。从评论列表中搜索所有内容不是预期的事情。
  • 分配一个简单的整数而不是函数指针,然后对该 id 进行(大)开关,调用与该 id 匹配的函数。
  • 关于 NASA 的那篇论文,你应该注意到它是关于整体安全关键程序设计的通用文件,但缺乏安全关键软件编码标准所要求的所有必要的专业性。无论规则多么合理,您都不能只是将它们吐在纸上,然后将其称为安全关键系统的规范。您需要考虑整个软件设计过程以及应该如何执行规则。只需选择 MISRA,最好是 2012 年。它在与 NASA 爱好者完全不同的部门中发挥作用......

标签: c embedded state-machine misra safety-critical


【解决方案1】:

首先,NASA 的文件不是正典。首先询问哪些法律/指令/标准/要求/文件强制您遵循 NASA 文件。如果它没有在任何地方强制执行(这似乎很有可能,即使在 NASA 本身也是如此),你没有义务遵循它,你可以忽略整个事情。

如果没有将废话视为废话,您可以在碰壁时使用通常的程序以达到安全标准:解决方案始终是详细记录规定的规则如何没有意义,然后用耳光打他们的脸他们自己的方法。

因此,与其放弃函数指针,不如通过下面描述的方法确保以安全的方式使用它们。


由于所有与安全相关的设计都归结为风险评估,因此您将始终拥有:

错误 -> 原因 -> 危险 -> 安全措施

根据 NASA 文档中给定的(糟糕的)基本原理,您可以通过以下方式证明“避免函数指针”的安全措施是合理的:

  1. 执行了错误的代码 -> 损坏的函数指针 -> 失控代码/非法操作码

  2. 堆栈溢出 -> 函数指针递归 -> 内存损坏

  3. 困惑的程序员 -> 函数指针语法 -> 意外的程序功能

这是一个相当模糊且值得怀疑的风险评估,但这就是 NASA 文件归结为的内容。

我建议使用以下安全措施,而不是针对上述 3 种危险“避免函数指针”:

  1. 防御性编程和断言。
  2. 防御性编程和断言。教育程序员。
  3. 使用 typedef。教育程序员。

防御性编程和断言

  • 对于状态机,确保状态数(枚举中的项)与处理程序数(函数指针数组中的项)相对应。只需针对sizeof(func_pointer_array)/sizeof(*func_pointer_array) 静态断言枚举中的最后一项(称为STATES_N 或类似名称)。
  • 函数指针数组必须在带有错误检测/CRC 的 ROM 中分配。其他安全要求将指向此需求,最简单的解决方法是使用带有“ECC 闪存”的微控制器。回到 ECC 之前的日子,您必须改为在整个 ROM 上计算 CRC,这既复杂又乏味,但也可以完成。您还可以创建表的相同 ROM 镜像,以防止闪存损坏。
  • 唯一允许您的状态机代码调用的函数指针是从这个安全函数指针数组中获得的。鉴于您的状态机调用看起来像STATE_MACHINE[i]();,其中STATE_MACHINE 是函数指针数组,那么只需添加i 的运行时检查以确保它始终有效。
  • 对于程序中的其他函数指针,例如回调函数,请确保将它们初始化为指向 ROM 中的有效函数。尽可能使用const 指针(指针本身是只读的)。如果您需要在运行时重新分配它们,请在调用它们之前确保它们指向一个有效的函数。

上述类型的状态机是惯用的并且非常安全,可能比代码中其他地方的普通函数调用安全得多。当然,您必须确保以安全合理的方式完成状态转换,但这与函数指针无关。

避免递归

这主要是关于教育程序员不要使用它,函数指针或没有函数指针(似乎这可以防止丰田错误)。

递归既不难发现也不难避免,所以半体面的代码审查形式应该足以防止它。没有经验丰富的嵌入式系统程序员,无论对安全至关重要的系统经验如何,都不会认可包含递归的代码。

您可以/应该制定内部设计规则,规定所有与安全相关的代码必须由具有 n 年安全关键程序设计经验的资深 C 程序员审查和批准。

此外,您还应该使用静态分析器工具检查递归(即使它们无法通过函数指针检测递归)。如果您有符合任何 MISRA-C 版本的静态分析器,则包括在内。

关于无意识的递归,上面提到的防御性编程方法可以避免。

Confusig 函数指针语法

C 中的函数指针语法确实很混乱,看看

int (*(*func)[5])(void);

或其他一些荒谬的例子。它可以通过始终对函数指针类型强制执行 typedef 来解决。

(参考:Les Hatton,Safer C,p184“从与安全相关的角度来看,简单的答案是它们永远不应该被允许在 typedef 机制之外。”)

有两种不同的方式可以对它们进行 typedef,我更喜欢这个:

typedef int func_t (void);
func_t* fptr;

因为这不会将指针隐藏在 typedef 后面,这通常是不好的做法。但是,如果您对替代方案感到更自在

typedef int (*func_t) (void);
func_t fptr;

那也可以练习。

【讨论】:

  • 为了证明合规性,您需要编写一份列出风险的文档,然后指出您使用哪些措施来消除风险,指出您实施 MISRA 规则(MISRA 合规矩阵)或在您的质量系统例程中进行代码审查等。这样您就不会明确地说您违反了“避免函数指针”的规则,而是说“是的,我们遵循该规则,这就是方式”。从而避免在第 3 方公告机构的报告中出现特殊言论。
  • 这是我在 SIL3 产品中实现 FSM 的首选方式。作为一个额外的卖点,单元测试是安全关键软件的一项重要要求,基于函数指针的 FSM 比巨大的switch-case 更容易测试。由于 OP 声明我们正在处理 SIL4,您可以提到 FSM 索引 i 应该是一个安全变量,除了应该是传统的运行时限制检查之外,还可以防止软错误(推荐用于 SIL3,SIL4 需要)考虑到外部安全关键环境。
  • 我有机会在 LDRA 会议上与Dr.Michael Hennell 交谈。他说即使在安全关键代码中也可以使用函数指针。我有和他聊天的录音。如果有人需要,请询问。他甚至达到了可以使用甚至递归的程度(不是无限递归,受控递归)
  • void f1(int *ptr); void f2(int *ptr); void (*const fpTest[2])(int *ptr1)={f1,f2}; int b[2][2]={{0,1},{2,3}}; void f1(int *ptr) { int a=0; int b=0; a=ptr[0]; b=ptr[1]; printf("%d\n",a); printf("%d\n",b); } void f2(int *ptr) { int a=0; int b=0; a=ptr[0]; b=ptr[1]; printf("%d\n",a); printf("%d\n",b); } int main() { (*fpTest[0])(b[0]); (*fpTest[1])(b[1]); return 0; } 根据@Lundin 和 Dr.Michael Hennell 的意见,我计划以这种方式实现我的代码
  • 我收到 MISRA 警告:“函数指针与另一种类型之间的转换 [MISRA 2012 规则 11.1,必需]”我需要使用 typedef 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
  • 1970-01-01
  • 2014-03-29
  • 1970-01-01
相关资源
最近更新 更多