[英]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();
}
現在假設發生了這些事情:
putch
時,正在發送一個字節。 putch
位於上面標記為/***/
的位置時,傳輸恰好完成。 handler()
恰好在那里執行。 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.