【问题标题】:Portable ISR-safe data-passing便携式 ISR 安全数据传递
【发布时间】:2017-11-21 15:52:09
【问题描述】:

在嵌入式 C 中,我正在尝试创建一种通用方法来安全地将值从 ISR(前台)传递到我的主循环(后台)。通过“通用”,我的意思是我不想暂停中断(因为这是编译器/CPU 特定的并且可能有副作用),只想用繁忙的标志等来做。对于这个特殊的机制,我不需要一个队列,我只想检索 ISR 报告的最新值。

所以我使用的模式是一个结构和一些对该结构进行操作的函数。问题当然是“写入”功能是基于 ISR 的,可以随时中断“读取”功能,我想消除数据损坏的可能性。该方法是一个双槽系统和几个繁忙标志。

它会起作用吗?和/或有更简单的方法吗? (请记住,这是嵌入式 C,我正在尝试通用/便携。)谢谢!

typedef struct 
{
    uint8_t busy;
    int32_t valueA; 
    int32_t valueB;     
    uint8_t reading_from_A;
    uint8_t last_wrote_to_B;
} sSafeI32_Fore2Back;

void SafeI32_InitFore2Back(sSafeI32_Fore2Back * si)
{
    si->busy = 0;    
    si->valueA = 0;
    si->valueB = 0;
    si->reading_from_A = 0;
    si->last_wrote_to_B = 1;
}

int32_t SafeI32_ReadFromBack(sSafeI32_Fore2Back * si)
{
    int32_t rtn;

    si->busy = 1;
    if (si->last_wrote_to_B)
    {
        rtn = si->valueB;
    }
    else
    {
        si->reading_from_A = 1;
        rtn = si->valueA;
        si->reading_from_A = 0;
    }
    si->busy = 0;

    return rtn;
}

void SafeI32_WriteFromFore(sSafeI32_Fore2Back * si, int32_t v)
{
    if (si->busy == 0)
    {
        si->valueA = v;
        si->last_wrote_to_B = 0;      
    }
    else
    {
        if (si->reading_from_A)
        {
            si->valueB = v;
            si->last_wrote_to_B = 1;
        }
        else
        {
            si->valueA = v;
            si->last_wrote_to_B = 0;            
        }
    }    
}

【问题讨论】:

  • 使用循环队列是很常见的。 ISR 在“保存”索引处加载数据,当“保存”索引不同时,主循环从“加载”索引中提取。ISR 读取“保存”索引,将其移动一轮,然后加载新数据索引,并且在安全完成所有操作后,将索引的新值存储回“保存”索引中。然后,在新数据完全可用之前,主循环永远不会看到“保存”索引的任何新值。指数应该是易变的。如果您有 RTOS,您可能会发出信号量,处理程序线程在该信号量上等待而不是轮询。
  • 两件事:我认为包装队列本质上不是线程安全的,例如,如果它比 256 个插槽宽,那么您的索引大于一个字节,并且需要两次组装操作来读取或写入对他们来说(即它的非原子性的)。因此,您可能会在 中途 读取索引后对其进行修改,从而产生随机结果和不良行为。
  • 其次,队列会产生与我在上面所做的稍有不同的行为。我想要来自 ISR 的 最新 值,不需要历史记录——如果还没有读取前一个值,我希望它被覆盖。
  • 也感谢您的回复:)
  • 只要遵循我上面描述的协议,在一个 ISR 和一个线程/主循环之间使用包装队列是安全的。索引会递增,并且可能会被 ISR 包装在一个临时的、自动的 var 中,并且在设置 volatile 'save' var 之前在这个新索引处加载数据,因此线程/main 在递增/测试/设置发生在自动变量中。没关系——我已经多次使用过这样的方案,只要最后完成“保存”的设置,就可以了。可能会因 a) 嵌套 ISR b) 多个线程等待而失败。

标签: c thread-safety embedded interrupt isr


【解决方案1】:

执行此操作的一种简单、常见的方法是让读取函数确保它在读取期间没有被抢占。 ISR 每次执行时都会更新一个计数器,读取功能确保计数器在访问期间不会更改。如果计数器确实发生了变化,则再次执行读取。

typedef struct
{
    int32_t value;
    uint32_t counter;
} sSafeI32_Fore2Back;

void SafeI32_InitFore2Back(volatile sSafeI32_Fore2Back * si)
{
    si->value = 0;
    si->counter = 0;
}

int32_t SafeI32_ReadFromBack(volatile sSafeI32_Fore2Back * si)
{
    int32_t rtn;
    uint32_t ctr;

    do {
        ctr = si->counter;
        rtn = si->value;
    } while(ctr != si->counter);

    return rtn;
}

void SafeI32_WriteFromFore(sSafeI32_Fore2Back * si, int32_t v)
{
    si->value = v;
    si->counter++;
}

【讨论】:

  • 为什么要使用计数器?似乎不必要的复杂,因为您可以使用任何 volatile bool 变量。读者:changed = false; x = read; if(changed)....
  • @Lundin 我同意您的解决方案在只有一个后台任务时更简洁。但是将状态更改限制为 ISR 执行会使读取线程安全。
  • 如果你有多个线程直接与同一个 ISR 通信,那么设计很糟糕。应该是 ISR driver driver setter/getter 多线程。
  • @Lundin read 函数不就是一个驱动 getter 的实现吗?
【解决方案2】:

我执行了一次代码遍历,但找不到任何明显的实现问题。我相信它会安全地工作。但是,使用这两个标志(忙和阅读_from_A)可能过于复杂,这使得复习更具挑战性。重要的部分是只有后台函数写入标志(reading_from_A 和忙)而不是前台函数。

下面显示了一种可能的简化,我相信它也可以安全地工作。我在 8051 处理器上使用了类似的实现。

typedef struct
{
    uint8_t pendingData;
    uint8_t index;
    int32_t value[2];
} sSafeI32_Fore2Back;

void SafeI32_InitFore2Back(sSafeI32_Fore2Back * si)
{
    si->pendingData = 0;
    si->index = 0;
    si->value[0] = 0;
    si->value[1] = 0;
}

int32_t SafeI32_ReadFromBack(sSafeI32_Fore2Back * si)
{
    if (si->pendingData) {
        if (si->index == 0) {
            si->index = 1;
        } else {
            si->index = 0;
        }
        si->pendingData = 0;
    }
    return si->value[index];


    // Note 1: you could change the above if;then;else to
    //  index = (index+1) % 2;
    // but this is not an atomic operation and the ISR could
    // fire after the sum and before the modulo 2.
    // Perhaps index would take on an intermediate value of 2 before
    // the modulo and lead to a memory corruption by indexing out of
    // range when the ISR fires.  The ISR could mask the upper bits out
    // to ensure an out of range condition does not occur, but I think
    // the if;then;else is inherently safer.

    // Note 2: You need to assume that the ISR has fired at least once before this
    // function is called. Otherwise the function will return the initialized
    // value of 0. I believe the original post would do the same....

    // Note 3: I believe the original post is correct (at least I didn't find any
    // glaring issue with it.)  The important part is that only the background
    // function writes to the flags (reading_from_A and the busy) and not the
    // foreground function. Likewise, in this implemention, only the background
    // write to index and the forground function only reads it.However, the
// pendingData flag is read and written by both functions. Note that it is
// only written if its in a particular state which is mutually exclusive
}

void SafeI32_WriteFromFore(sSafeI32_Fore2Back * si, int32_t v)
{
    si->value[(si->index + 1) % 2] = v;  // write to the other location than what is
                                         // is being read from.
    if (!si->pendingData) {
        si->pendingData = 1;
    }
}

【讨论】:

  • 太棒了!看起来不错,谢谢比较/对比。
  • 问题,假设 ISR 向它写入一个“3”,然后我从中读取它会得到一个“3”。然后我再读一遍,我会得到一个“0”,对吧?这与总是获取最新值的意图(我没有很好地解释,抱歉)有点不同。 (换句话说,我们想要检索 '3' 直到它被覆盖,就像一个简单的变量。)
  • 是的。我认为如果 ISR 没有在两者之间触发,那么您在下一次读取时会得到 0。
  • 可能需要像其他建议一样添加一个计数器,以了解是否已写入新值。
  • 我修复了您指出的关于多次读取的错误。但是我添加了另一个标志,它基本上回到了你实现的内容。它有点不同,也许更简单一些。
【解决方案3】:

既然你知道一个中断不能被轮流打断(除非你有一些特殊的奇特场景),这很简单:

static volatile bool changed = false;
static uint32_t value;

void isr (void) // it is an interrupt; it can't be interrupted by the caller
{
  changed = true;
  value = something;
}

void caller (void)
{
  ...
  changed = false;
  uint32_t val = value;
  if(changed)
  {
    // handle error, discard val
  }
}

changed 变量甚至不需要原子访问。

但是请注意,在 ISR 和调用者之间共享的所有变量都必须是 volatile,否则可能会出现各种奇怪且非常微妙的优化错误。

【讨论】:

    猜你喜欢
    • 2015-06-12
    • 2011-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-25
    相关资源
    最近更新 更多