【问题标题】:Prevent nested calls防止嵌套调用
【发布时间】:2013-01-12 23:44:36
【问题描述】:

我有一个禁用中断的函数,但问题是如果我禁用它们并调用一个也禁用/启用它们的函数,它们会过早重新启用。以下逻辑是否足以防止这种情况发生?

static volatile int IrqCounter = 0;

void EnableIRQ()
{
    if(IrqCounter > 0)
    {
        IrqCounter--;
    }

    if(IrqCounter == 0)
    {
        __enable_irq();
    }
}

void DisableIRQ()
{
    if(IrqCounter == 0)
    {
        __disable_irq();
    }

    IrqCounter++;
}

【问题讨论】:

  • 不应该反过来吗?当你启用时,你真的在​​ 0 处启用并增加所有其他调用的计数,当你禁用时,你递减,当它变为零时,你调用真正的禁用?
  • @Jay MCU 启动时,默认情况下它们已经启用,所以这就是为什么我的逻辑看起来有点奇怪

标签: c embedded interrupt c99


【解决方案1】:
static volatile int IrqCounter = 0;

void EnableIRQ(void)
{
    ASSERT(IrqCounter != 0)  //should never be 0, or we'd have an unmatched enable/disable pair

    if (IrqCounter > 0)
    {
        IrqCounter--;
    }

    if (IrqCounter == 0)
    {
        __enable_irq();
    }
}

void DisableIRQ(void)
{
    __disable_irq(); // Fix TOCTOU issues. In CMSIS there is no harm in extra disables, so always disable.
    IrqCounter++;
}

【讨论】:

    【解决方案2】:

    假设您的系统在中断被禁用时无法更改上下文,那么您所拥有的一切都很好,假设您仔细跟踪调用 enable() 的时间。

    在您在下面的 cmets 中描述的用法中,您计划在中断服务例程中使用这些部分。您的主要用途是阻止更高优先级的中断在 ISR 的某个部分运行。

    请注意,您必须考虑这些嵌套 ISR 的堆栈深度,因为当您在从中断返回之前启用中断时,您将在 ISR 中启用中断。

    关于其他答案: enable() 缺乏线程安全性(由于if(IrqCounter > 0))并不重要,因为任何时候你在 enable() 上下文切换已经被禁用由于中断被离开。 (除非由于某种原因您有无与伦比的禁用/启用对,并且在这种情况下您还有其他问题。)

    我唯一的建议是将 ASSERT 添加到启用而不是运行时检查,因为您永远不应该启用您没有禁用的中断。

    void EnableIRQ()
    {
      ASSERT(IrqCounter != 0)  //should never be 0, or we'd have an unmatched enable/disable pair
    
      IrqCounter--;  //doesn't matter that this isn't thread safe, as the enable is always called with interrupts disabled.
    
      if(IrqCounter == 0)
      {
          __enable_irq();
      }
    }
    

    比起save(); disable(); restore(); 技术,我更喜欢您列出的技术,因为我不喜欢每次处理中断时都必须跟踪操作系统的一段数据。但是,您必须注意何时(直接或间接)从 ISR 调用 enable()。

    【讨论】:

    • 你写了“你不能从 ISR 调用这些”,但我实际上计划这样做。我想从 ISR 中调用此函数,以防止任何其他(较高优先级)ISR 抢占此(较低优先级)ISR。这样会有问题吗?
    • 只要您将 enable() 作为 ISR 中的绝对最后一次调用,调用本身就可以了。但是,您最终可能会得到比预期更深的调用堆栈,因为您的 ISR 可能不会在您为现在启用的高优先级中断提供服务之前展开。
    • 我试图理解为什么它应该是最后一个电话。我知道当启用调用导致挂起(更高优先级)中断执行时,我需要两倍的堆栈空间。但这不是一直如此吗?即使没有禁用/启用中断,我是否总是有机会被该函数中的另一个中断抢占?
    • 我认为禁用的原因是为了确保在 ISR 期间不会执行任何其他操作。但看起来您的用途是确保在您的 ISR 的 部分 期间没有执行任何其他操作。我会更新我的答案以更多地反映您的使用情况...
    • 没错!我的中断是线程安全的,但很小的部分不是。所以我暂时禁用中断,执行不安全的代码,并尽快重新启用它们。例如,当我从 ISR 调用 malloc() 时,我需要用禁用/启用包围它,因为 malloc() 在我的环境中不是线程安全的。
    【解决方案3】:

    我所知道的每个操作系统的做法是将 IRQ 状态保存到一个局部变量中,然后再恢复它。

    显然,您的代码存在TOCTOU 问题 - 如果两个线程同时运行,检查 IrqCounter > 0,如果 IrqCounter == 1,则第一个线程将其视为 1,第二个线程将其视为1,并且两者都递减计数器。

    我肯定会尝试这样安排:

    int irq_state = irq_save();
    
    irq_disable();
    
    ... do stuff with IRQ's turned off ... 
    
    irq_restore(irq_state);
    

    现在,您不必担心计数器会不同步等问题。

    【讨论】:

    • 但是,当代码在 EnableIRQ() 中运行并检查 > 0 时,不可能有 2 个线程同时运行。 (因为中断被禁用,因此上下文切换也是如此......)除非由于某种原因你重新启用了一些你没有禁用的东西,但是你还有其他问题。
    • @Mats 首先创建这些包装器的全部原因是我不知道如何读出当前状态。这是一个 Cortex M0 CPU,我知道禁用 IRQ(“CPSID”)的汇编指令,但我不知道如何检查当前状态。
    • 这是一个 ARM 处理器,是吗?如果是这样,您可以使用 MSR 和 MRS 来读取/写入 PRIMASK 寄存器。
    【解决方案4】:

    看起来不错,只是它不是线程安全的。

    另一个常见的选项是查询中断启用/禁用状态并将其保存到局部变量中,然后禁用中断,然后在禁用中断时执行您想做的任何事情,然后从局部变量中恢复状态。

    【讨论】:

      猜你喜欢
      • 2014-04-07
      • 1970-01-01
      • 2015-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-14
      • 1970-01-01
      相关资源
      最近更新 更多