前言
信號 signal,并不是線程間同步的信號量 semaphore。后者是線程間同步機制的一種,而前者是線程間異步通信的一種。
官方文檔里對其解釋是:“信號(又稱為軟中斷信號),在軟件層次上是對中斷機制的一種模擬,在原理上,一個線程收到一個信號與處理器收到一個中斷請求可以說是類似的。”
信號本質是**軟中斷**,用來通知線程發生了異步事件,**用做線程之間的異常通知、應急處理**。一個**線程不必通過任何操作來等待信號的到達**,事實上,**線程也不知道信號到底什么時候到達**。線程之間可以互相通過調用 `rt_thread_kill` 發送信號。
以上畫線部分是我特意要大家注意的,我們要看待中斷回調函數那樣,看待信號回調函數**被執行的實機**,但不需要過分擔憂的是回調函數**執行時間**,因為**終究信號回調函數還是在線程上下文被執行的**。
從官方文檔可以清楚了解到,使用信號很簡單,安裝信號、解除信號掩碼、發送信號、處理信號等幾個過程。
更多關于信號的原理詳見官方文檔 [信號]( https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2?id=%e4%bf%a1%e5%8f%b7 )
一個示例引起的血案
官方原版示例筆者就不貼出來了,直接拷貝到自己的項目完美運行。但是,筆者經過如下修改,發現一點兒疑問。
/* 線程 1 的入口函數 */
static void thread1_entry(void *parameter)
{
...
while (cnt < 10)
{
...
tick = rt_tick_get();
rt_thread_mdelay(1000);
tick = rt_tick_get();
}
...
}
把延時時間增長,前后添加測時。多次運行發現 tick 值改變只有 300 (`rt_thread_mdelay(300)`)。這說明了線程響應 signal 后,處理了信號回調函數之后放棄了之前的延時!那么問題來了,應用層想要的延時時間不足,應用層知道嗎?答案是,*不知道!*
rt-thread 中阻塞函數列表
前一段時間在文章 rt-thread 那些你必須知道的幾類 api 里總結了 *禁止在中斷中調用*、*必須在任務調度器運行以后才能使用*、*不能用在線程自己身上*的幾類 api。
可能還缺一種:哪些 api 會引起線程調度,使得當前線程放棄 cpu 使用權——所有調用 `rt_schedule` 的函數都屬于這類。這里邊又分三種情況,一種是時間片耗盡讓出 cpu 使用權;一種是釋放資源或者信號讓出 cpu 使用權;還有一種是等待資源而被動放棄 cpu。最后這種情況,是有目地的,往往希望有資源可用了之后從阻塞中恢復繼續運行,如果線程從阻塞中恢復運行但同時沒有資源可用是不是就烏龍了?以下的關注重點也是這類函數。
所有第三類引起線程調度的函數和上面的 `rt_thread_mdelay` 一樣,在 signal 面前可能遇到一樣的遭遇。大體上,分這么幾類:
- 延時函數
- 線程間同步機制函數
- 線程間通信機制部分函數(signal除外)
- posix 下的 select poll 等接口(可能使用了線程間同步和通信機制)
這幾類在遇到 signal 之后行為分別是什么樣的?
被阻塞函數遇到 signal 后什么反應?
延時函數遇到 signal
這個前面已經經過測試的了,它會退出阻塞提前結束延時,但是應用層并不知道是達到延時時間還是有信號。
線程間同步通信機制函數遇到 signal
- `rt_sem_take` 線程 error 非 RT_EOK (包括 RT_EINTR)直接返回線程錯誤狀態
/* do schedule */
rt_schedule();
if (thread->error != RT_EOK)
{
return thread->error;
}
- `rt_mutex_take` 考慮到了 signal 的影響,返回繼續阻塞等待 `time` 時間。這是 ipc 里唯一例外的一個。
/* do schedule */
rt_schedule();
if (thread->error != RT_EOK)
{
#ifdef RT_USING_SIGNALS
/* interrupt by signal, try it again */
if (thread->error == -RT_EINTR) goto __again;
#endif /* RT_USING_SIGNALS */
其它,其余的 ipc 都和 `rt_sem_take` 一樣。
完成量遇到 signal
`rt_completion_wait` 返回線程錯誤狀態。
/* do schedule */
rt_schedule();
/* thread is waked up */
result = thread->error;
level = rt_hw_interrupt_disable();
}
}
...
return result;
select poll 等接口與 signal
因為文件描述符對應的設備不盡相同,設備底層實現 `poll` 的方式可能也千差萬別,但是他們大概率是使用上面的線程間同步和通信機制了。
`poll` 實現過程調用個超時等待函數 `poll_wait_timeout` ,它也沒有區分超時和信號兩種情況。
rt_schedule();
level = rt_hw_interrupt_disable();
}
ret = !pt->triggered;
rt_hw_interrupt_enable(level);
return ret;
我們發現,`rt_sem_take` 結束了阻塞,并可能返回了 `RT_EINTR` ,而 `rt_mutex_take` 繼續了循環阻塞。
等待資源而被動放棄 cpu 時怎么應對 signal 才合適?
現做以下約定,等待資源而被動放棄 cpu 的線程在此約定下,當有 signal 的時候會提前結束阻塞,返回應用層,應用層可以根據線程錯誤狀態區別處理。
1. 復位線程錯誤狀態為 `RT_EOK` 。
2. 調用 `rt_schedule` 進行線程調度,線程被阻塞掛起。
3. 從 `rt_schedule` 恢復喚醒,有一定手段通知到應用層(返回線程錯誤狀態),應用層可以區分出是因為資源可用還是因為信號。
哪些 api 做到了以上這幾點呢?
```
rt_completion_wait
rt_sem_take
rt_event_recv
rt_mb_send_wait
rt_mb_recv
rt_mq_send_wait
rt_mq_recv
rt_data_queue_push
rt_data_queue_pop
rt_mp_alloc
哪些 api 沒有做到以上幾點?
```
rt_mutex_take
rt_thread_sleep
rt_thread_delay
rt_thread_delay_until
rt_thread_mdelay
rt_wqueue_wait
筆者曾經在 gitee 上提交過一個 [issue]( https://gitee.com/rtthread/rt-thread/issues/I44JNS ) ,當時筆者隱隱中認為 ipc 中的不一致行為總有些隱患,感覺所有的阻塞等待都應該處理一下意外喚醒后的超時等待。卻沒意識到有什么意外情況可以讓這些函數從阻塞等待中提前退出。通過研究 signal 實現原理的過程中發現,這種意外情況還有存在的,只是擔憂的問題重點變了,不是處理阻塞等待剩余時間,而是在 signal 的影響下通知應用層的問題。
解決方案
有了上面的梳理,下面的修改方向就有了,改動范圍也確定了。
- 幾個延時函數返回 `thread->error` 代替目前的 `RT_EOK` ;
- `rt_mutex_take` 去掉 `goto __again` 也返回 `thread->error` ;
- `rt_wqueue_wait` 返回 `thread->error` 代替目前的 `RT_EOK` 。
- `poll` 目前返回值是 >= 0 的,返回 0 可能是超時,也可能是被信號中斷了。暫時不發表修改意見。
結束語
以上搜索不一定完整完全,但應該包括了大部分受到影響的函數。如果看客有發現其它的 api 有不符合上述約定行為的,請留言告知,謝謝!
本人能力有限,文中難免有錯誤。望各位同仁不吝賜教。
相關文章
rt-thread 優化系列(0) SysTick 優化分析
rt-thread 優化系列(一) 之 過多關中斷
rt-thread 優化系列(二) 之 同步和消息關中斷分析
rt-thread優化系列(三)軟定時器的定時漂移問題分析
審核編輯:湯梓紅
-
信號
+關注
關注
11文章
2790瀏覽量
76741 -
IPC
+關注
關注
3文章
347瀏覽量
51905 -
signal
+關注
關注
0文章
110瀏覽量
24916 -
RT-Thread
+關注
關注
31文章
1286瀏覽量
40102
發布評論請先 登錄
相關推薦
評論