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