【问题标题】:Any systematic way to avoid "reentry" problem? (embedded system) [closed]有什么系统的方法可以避免“再入”问题吗? (嵌入式系统)[关闭]
【发布时间】:2010-01-25 12:42:44
【问题描述】:

我们正在使用 C 在 ARM 内核上构建系统(即嵌入式系统)。问题是:我们如何才能以正式的方式避免重入问题,以便我们确信所有重入错误都已消除。我猜这可能不是一个实际的愿望,但对于任何系统来说肯定很重要。

只是为了讨论,我想画UML图或拥有一个完整的状态机将是一个好的开始(但是在整个系统开发之后如何生成它呢?)。关于如何使用状态机/UML图进行分析有什么建议吗?

【问题讨论】:

    标签: c uml embedded-language regression-testing


    【解决方案1】:

    我不太确定您要解决的问题,但让我做一个有根据的猜测。

    第一点是确定可能有问题的功能。重入要么通过递归调用发生,递归调用可能会遍历多个嵌套调用,甚至被回调/依赖注入隐藏,或者被多个线程中使用的函数所隐藏。

    您可以绘制有向调用图。假设函数 A 调用 B 和 C,函数 B 调用 D、E 和 F,函数 C 什么都不调用,等等。多线程时为每个线程绘制这个。如果图中存在循环,则构成该循环的所有函数都需要是可重入安全的。在这种情况下,您可以忽略子分支。在多个线程中使用的函数也需要是安全的,但现在包括所有子分支,因为您不确切知道每个线程当前在哪里。使用锁时事情会变得越来越复杂,所以我们暂时忽略这一点。

    这一步肯定可以通过代码分析工具自动化。

    现在功能已经确定,

    • 仅依赖于其函数本地数据的函数通常是安全的。这是函数式编程的优点之一。
    • 应仔细检查依赖于外部数据的函数(不在函数范围内),了解外部数据何时何地发生更改尤为重要。
    • 更改外部数据的函数应发出危险信号并激活响亮的警报器,尤其是在多线程时。

    【讨论】:

    • 当我进行这种代码检查时,我首先要看的是我的全局变量。直接访问/操作全局变量的函数通常是重入问题最多的函数。正如 Secure 所提到的,只修改函数参数或内部数据的函数通常是可以的。不要忘记验证您调用的任何库函数(包括标准库函数)是否是线程安全的!
    • 知道了。感谢 Secure & bta 的建议。
    • “被回调/依赖注入隐藏”或信号处理程序,只是为了完整性。
    【解决方案2】:

    快速修复,如果您怀疑某事:

    int some_func(int x, int y)
    {
        static volatile int do_not_enter_twice = 0;
        assert(!(do_not_enter_twice++));
    
        /* some_func continued */
    
        do_not_enter_twice--;
        return whatever;
    }
    

    更长的答案:
    使用一些工具创建一个call graph 并从那里手动继续。

    【讨论】:

    • 哦!我懂了。我们已经制作了一个调用图,但没有设法将其用于解决重入问题。是的,我们做了重新进入检查,只是不确定哪个函数应该通过这个检查(现在我知道了)。顺便说一句,有没有工具可以帮助分析调用图,因为我们的调用图很大......
    • 查看 DMS 答案以计算巨大的调用图。
    【解决方案3】:

    一个可以计算巨大调用图的工具是DMS Software Reengineering Toolkit 及其C 前端。 C前端用于解析C代码; DMS 内置了计算控制和数据流分析、指向分析和提取调用直接和间接调用通过指针事实的机制

    DMS 已被用于构建 C 源代码系统的调用图,该系统包含 3500 万 行 C 代码(= 250,000 个函数),然后从该调用图中提取信息。构建像这样的大型图时的一个关键问题是尽可能准确地计算指向信息(完美地做到这一点存在理论上的硬性限制),以便间接函数调用保守地针对最少数量的误报目标。

    就您而言,正如其他作者所指出的那样,要提取的信息是“是否存在循环?”在这个调用图中。

    在这种规模下,您不想手动执行此操作,并且每次为生产构建做好准备时都需要重新执行此操作。所以机械化检查会很有意义。

    【讨论】:

      【解决方案4】:

      我会做两件事来检查您的代码。彻底的小组代码审查(目的是只发现重入错误,而不是样式或其他错误)。其次,对问题的实际攻击。

      例如:

      int myfunction(int x, int y) {
          REENTRANCE_CHECK;
          ... body of function
      }
      

      现在您可以将#define REENTRANCE_CHECK 设为空(用于生产)或某些检查函数的代码永远不会重新输入。在启用这些检查的情况下运行您的测试(如果您没有测试,则在连接了调试器的设备上运行它),看看是否有任何问题。

      同样,您可以添加调试逻辑来检测对全局状态的错误更新。编写使用锁的代码(如果它们在已经持有时被获取,则断言它们。

      类似这样的:

      int my_global;
      DEFINE_GLOBAL_LOCK(my_global);
      
      void my_dangerous_function() {
          ...
          LOCK_GLOBAL(my_global);
          .. some critical section of code that uses my_global.
          UNLOCK_GLOBAL(my_global);
          ...
      }
      

      同样,DECLARE_GLOBAL_LOCK、LOCK_GLOBAL 和 UNLOCK_GLOBAL 可以被 #defined 为真正的锁定代码(当然你必须编写)用于测试,而对于生产它们可以被 #defined 为空。

      此方法仅在您找到并包装对全局状态的所有访问时才有效,但通过搜索很容易。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-07-10
        • 1970-01-01
        • 1970-01-01
        • 2010-10-08
        • 1970-01-01
        • 1970-01-01
        • 2011-05-27
        相关资源
        最近更新 更多