簡體   English   中英

低延遲基於中斷的傳輸代碼中的隨機行為

[英]Racy behavior in low-latency interrupt-based transmit code

假設您有一些數據傳輸外設,例如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()putch()行中加載的字節。

處理程序將屏蔽該中斷,因為沒有更多數據要發送,但是在handler()返回后, putch錯誤地取消屏蔽該中斷。 因此, 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();

我一直使用雙緩沖來做到這一點,因此在任何時候程序和UART都“擁有”不同的緩沖區。

UART完成發送其緩沖區后,可以進行交換,並屏蔽中斷。 這樣,它不必掩蓋每個字符上的中斷。

一種解決方法是防止中斷處理程序在putch運行:

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

這使我們可以使用原始的傳輸優先中斷處理程序。 這樣做的問題是,總體而言,它增加了每個發送字節執行的操作數。 這也增加了峰值延遲,因為有時即使硬件已經准備好容納更多數據並且要發送數據, handler()根本無法運行。

中斷處理程序確定使發送器再次繁忙的平均等待時間。 最重要的是,峰值延遲由延遲中斷處理程序執行的代碼確定。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM