基于低延迟中断的传输代码中的Racy行为

时间:2015-09-24 16:13:36

标签: c embedded race-condition

假设您有一些数据传输外设,如UART,只要它准备好传输更多数据就会发出中断信号。我们从循环缓冲区发送数据,其中tail是数据从中删除的位置,head是添加数据的位置,而tail == head表示没有更多数据要传输。

我们还假设外围设备没有任何缓冲,并且当它忙于发送当前值时,您无法将下一个值传递给它。如果你需要一个具体的,如果是一个例子,可以考虑一个直接连接到CPU并行I / O端口的移位寄存器。

为了使发送器尽可能忙,您可能希望在输入发送中断处理程序后立即发送。当没有数据要传输时,中断被屏蔽掉,即使中断已经布防,也不会调用处理程序。系统启动时中断屏蔽。

我将使用C来说明事情,尽管问题不是特定于C的。中断处理程序和缓冲区的设置如下:

char buf[...];
char * head = buf;                     ///< write pointer
char * tail = buf;                     ///< read pointer
char * const first = buf;              ///< first byte of the buffer
char * const last = buf+sizeof(buf)-1; ///< last byte of the buffer

/// Sends one byte out. The interrupt handler will be invoked as soon
/// as another byte can be sent.
void transmit(char);     

void handler() {
  transmit(*tail);
  if (tail == last)
    tail = first;
  else
    tail++;
  if (tail == head)
    mask_interrupt();
}

到目前为止,这么好。现在让我们看看如何实现putch()。我们可以比设备能够发送数据更快地以突发方式调用putch()。让我们假设调用者知道不要溢出缓冲区。

void putch(char c) {
  *head = c;
  if (head == last)
    head = first;
  else
    head++;
  /***/
  unmask_interrupt();
}

现在假设发生了这些事情:

  1. 发送器正忙,当调用putch时,正在发送一个字节。
  2. putch位于上方标有/***/的地点时,传输即将结束。 handler()恰好在那里执行。
  3. handler()碰巧发送缓冲区中数据的最后一个字节 - 我们刚刚在putch()中的前一行中加载的字节。
  4. 处理程序屏蔽了中断,因为没有更多数据要发送,但putchhandler()返回后错误地取消屏蔽。因此handler将有另一个通过缓冲区,并将发送缓冲区的过时数据,直到tail再次等于head

    我的问题是:在发送handler之前,唯一的解决方法是增加延迟并检查空缓冲区吗?固定代码如下所示:

    void fixed_handler() {
      if (head == tail) {
        mask_interrupt();
        arm_interrupt(); // so that next time we unmask it, we get invoked
        return;
      }
      transmit(*tail);
      if (tail == last)
        tail = first;
      else
        tail++;
    }
    

    此修补程序增加了一些延迟,并且还添加了一个额外的操作(arm_interrupt),当没有更多数据要发送时,该操作会执行一次。

    对于可能的其他方法,请随意假设至少存在以下操作:

    /// Is the interrupt armed and will the handler fire once unmasked?
    bool is_armed();
    /// Is the interrupt unmasked?
    bool is_unmasked();
    

2 个答案:

答案 0 :(得分:2)

我总是使用双缓冲完成此操作,因此在任何时间点程序和UART都“拥有”不同的缓冲区。

当UART完成发送缓冲区时,可能会发生交换,中断被屏蔽。 这样,它就不必掩盖每个角色的中断。

答案 1 :(得分:0)

一种解决方法是阻止中断处理程序在putch中运行:

void putch(char c) {
  *head = c;
  mask_interrupt();
  if (head == last)
    head = first;
  else
    head++;
  unmask_interrupt();
}

这让我们可以使用原始的发送优先中断处理程序。问题在于它总体上增加了每个发送字节执行的操作数。它也会增加峰值延迟,因为现在有时handler()根本无法运行,即使硬件已准备好接收更多数据也要发送数据。

使发送器再次忙碌的平均延迟由中断处理程序决定。最重要的峰值延迟由延迟执行中断处理程序的代码决定。