0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux進(jìn)程的睡眠和喚醒

馬哥Linux運(yùn)維 ? 來(lái)源:未知 ? 作者:李倩 ? 2018-03-20 14:34 ? 次閱讀

1 Linux 進(jìn)程的睡眠和喚醒

在 Linux 中,僅等待 CPU 時(shí)間的進(jìn)程稱為就緒進(jìn)程,它們被放置在一個(gè)運(yùn)行隊(duì)列中,一個(gè)就緒進(jìn)程的狀 態(tài)標(biāo)志位為 TASK_RUNNING。一旦一個(gè)運(yùn)行中的進(jìn)程時(shí)間片用完, Linux 內(nèi)核的調(diào)度器會(huì)剝奪這個(gè)進(jìn)程對(duì) CPU 的控制權(quán),并且從運(yùn)行隊(duì)列中選擇一個(gè)合適的進(jìn)程投入運(yùn)行。

當(dāng)然,一個(gè)進(jìn)程也可以主動(dòng)釋放 CPU 的控制權(quán)。函數(shù) schedule() 是一個(gè)調(diào)度函數(shù),它可以被一個(gè)進(jìn)程主動(dòng)調(diào)用,從而調(diào)度其它進(jìn)程占用 CPU。一旦這個(gè)主動(dòng)放棄 CPU 的進(jìn)程被重新調(diào)度占用 CPU,那么它將從上次停止執(zhí)行的位置開始執(zhí)行,也就是說(shuō)它將從調(diào)用 schedule() 的下一行代碼處開始執(zhí)行。

有時(shí)候,進(jìn)程需要等待直到某個(gè)特定的事件發(fā)生,例如設(shè)備初始化完成、I/O 操作完成或定時(shí)器到時(shí)等。在這種情況下,進(jìn)程則必須從運(yùn)行隊(duì)列移出,加入到一個(gè)等待隊(duì)列中,這個(gè)時(shí)候進(jìn)程就進(jìn)入了睡眠狀態(tài)。

Linux 中的進(jìn)程睡眠狀態(tài)有兩種:一種是可中斷的睡眠狀態(tài),其狀態(tài)標(biāo)志位

TASK_INTERRUPTIBLE;

另一種是不可中斷 的睡眠狀態(tài),其狀態(tài)標(biāo)志位為 TASK_UNINTERRUPTIBLE。可中斷的睡眠狀態(tài)的進(jìn)程會(huì)睡眠直到某個(gè)條件變?yōu)檎?,比如說(shuō)產(chǎn)生一個(gè)硬件中斷、釋放 進(jìn)程正在等待的系統(tǒng)資源或是傳遞一個(gè)信號(hào)都可以是喚醒進(jìn)程的條件。不可中斷睡眠狀態(tài)與可中斷睡眠狀態(tài)類似,但是它有一個(gè)例外,那就是把信號(hào)傳遞到這種睡眠 狀態(tài)的進(jìn)程不能改變它的狀態(tài),也就是說(shuō)它不響應(yīng)信號(hào)的喚醒。不可中斷睡眠狀態(tài)一般較少用到,但在一些特定情況下這種狀態(tài)還是很有用的,比如說(shuō):進(jìn)程必須等 待,不能被中斷,直到某個(gè)特定的事件發(fā)生。

在現(xiàn)代的 Linux 操作系統(tǒng)中,進(jìn)程一般都是用調(diào)用 schedule() 的方法進(jìn)入睡眠狀態(tài)的,下面的代碼演示了如何讓正在運(yùn)行的進(jìn)程進(jìn)入睡眠狀態(tài)。

sleeping_task = current;

set_current_state(TASK_INTERRUPTIBLE);

schedule();

func1();

/* Rest of the code ... */

在第一個(gè)語(yǔ)句中,程序存儲(chǔ)了一份進(jìn)程結(jié)構(gòu)指針 sleeping_task,current 是一個(gè)宏,它指向正在執(zhí)行的進(jìn)程結(jié)構(gòu)。set_current_state() 將該進(jìn)程的狀態(tài)從執(zhí)行狀態(tài) TASK_RUNNING 變成睡眠狀態(tài)TASK_INTERRUPTIBLE。 如果 schedule() 是被一個(gè)狀態(tài)為TASK_RUNNING 的進(jìn)程調(diào)度,那么 schedule() 將調(diào)度另外一個(gè)進(jìn)程占用 CPU;如果 schedule() 是被一個(gè)狀態(tài)為 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 的進(jìn)程調(diào)度,那么還有一個(gè)附加的步驟將被執(zhí)行:當(dāng)前執(zhí)行的進(jìn)程在另外一個(gè)進(jìn)程被調(diào)度之前會(huì)被從運(yùn)行隊(duì)列中移出,這將導(dǎo)致正在運(yùn)行的那個(gè)進(jìn)程進(jìn)入睡眠,因?yàn)?它已經(jīng)不在運(yùn)行隊(duì)列中了。

我們可以使用下面的這個(gè)函數(shù)將剛才那個(gè)進(jìn)入睡眠的進(jìn)程喚醒。

wake_up_process(sleeping_task);

在調(diào)用了 wake_up_process() 以后,這個(gè)睡眠進(jìn)程的狀態(tài)會(huì)被設(shè)置為 TASK_RUNNING,而且調(diào)度器會(huì)把它加入到運(yùn)行隊(duì)列中去。當(dāng)然,這個(gè)進(jìn)程只有在下次被調(diào)度器調(diào)度到的時(shí)候才能真正地投入運(yùn)行。

2 無(wú)效喚醒

幾乎在所有的情況下,進(jìn)程都會(huì)在檢查了某些條件之后,發(fā)現(xiàn)條件不滿足才進(jìn)入睡眠。可是有的時(shí)候進(jìn)程卻會(huì)在 判定條件為真后開始睡眠,如果這樣的話進(jìn)程就會(huì)無(wú)限期地休眠下去,這就是所謂的無(wú)效喚醒問(wèn)題。在操作系統(tǒng)中,當(dāng)多個(gè)進(jìn)程都企圖對(duì)共享數(shù)據(jù)進(jìn)行某種處理,而 最后的結(jié)果又取決于進(jìn)程運(yùn)行的順序時(shí),就會(huì)發(fā)生競(jìng)爭(zhēng)條件,這是操作系統(tǒng)中一個(gè)典型的問(wèn)題,無(wú)效喚醒恰恰就是由于競(jìng)爭(zhēng)條件導(dǎo)致的。

設(shè)想有兩個(gè)進(jìn)程 A 和 B,A 進(jìn)程正在處理一個(gè)鏈表,它需要檢查這個(gè)鏈表是否為空,如果不空就對(duì)鏈表里面的數(shù)據(jù)進(jìn)行一些操作,同時(shí) B 進(jìn)程也在往這個(gè)鏈表添加節(jié)點(diǎn)。當(dāng)這個(gè)鏈表是空的時(shí)候,由于無(wú)數(shù)據(jù)可操作,這時(shí) A 進(jìn)程就進(jìn)入睡眠,當(dāng) B 進(jìn)程向鏈表里面添加了節(jié)點(diǎn)之后它就喚醒 A 進(jìn)程,其代碼如下:

A 進(jìn)程:

1 spin_lock(&list_lock);

2if(list_empty(&list_head)) {

3 spin_unlock(&list_lock);

4 set_current_state(TASK_INTERRUPTIBLE);

5 schedule();

6 spin_lock(&list_lock);

7 }

8

9/* Rest of the code ... */

10 spin_unlock(&list_lock);

B 進(jìn)程:

100 spin_lock(&list_lock);

101 list_add_tail(&list_head, new_node);

102 spin_unlock(&list_lock);

103 wake_up_process(processa_task);

這里會(huì)出現(xiàn)一個(gè)問(wèn)題,假如當(dāng) A 進(jìn)程執(zhí)行到第 3 行后第 4 行前的時(shí)候,B 進(jìn)程被另外一個(gè)處理器調(diào)度投入運(yùn)行。在這個(gè)時(shí)間片內(nèi),B 進(jìn)程執(zhí)行完了它所有的指令,因此它試圖喚醒 A 進(jìn)程,而此時(shí)的 A 進(jìn)程還沒(méi)有進(jìn)入睡眠,所以喚醒操作無(wú)效。在這之后,A 進(jìn)程繼續(xù)執(zhí)行,它會(huì)錯(cuò)誤地認(rèn)為這個(gè)時(shí)候鏈表仍然是空的,于是將自己的狀態(tài)設(shè)置為 TASK_INTERRUPTIBLE 然后調(diào)用 schedule() 進(jìn)入睡 眠。由于錯(cuò)過(guò)了 B 進(jìn)程喚醒,它將會(huì)無(wú)限期的睡眠下去,這就是無(wú)效喚醒問(wèn)題,因?yàn)榧词规湵碇杏袛?shù)據(jù)需要處理,A 進(jìn)程也還是睡眠了。

3 避免無(wú)效喚醒

如何避免無(wú)效喚醒問(wèn)題呢?我們發(fā)現(xiàn)無(wú)效喚醒主要發(fā)生在檢查條件之后和進(jìn)程狀態(tài)被設(shè)置為睡眠狀態(tài)之前, 本來(lái) B 進(jìn)程的 wake_up_process() 提供了一次將 A 進(jìn)程狀態(tài)置為 TASK_RUNNING 的機(jī)會(huì),可惜這個(gè)時(shí)候 A 進(jìn)程的狀態(tài)仍然是 TASK_RUNNING,所以 wake_up_process() 將 A 進(jìn)程狀態(tài)從睡眠狀態(tài)轉(zhuǎn)變?yōu)檫\(yùn)行狀態(tài)的努力 沒(méi)有起到預(yù)期的作用。要解決這個(gè)問(wèn)題,必須使用一種保障機(jī)制使得判斷鏈表為空和設(shè)置進(jìn)程狀態(tài)為睡眠狀態(tài)成為一個(gè)不可分割的步驟才行,也就是必須消除競(jìng)爭(zhēng)條 件產(chǎn)生的根源,這樣在這之后出現(xiàn)的 wake_up_process () 就可以起到喚醒狀態(tài)是睡眠狀態(tài)的進(jìn)程的作用了。找到了原因后,重新設(shè)計(jì)一下 A 進(jìn)程的代碼結(jié)構(gòu),就可以避免上面例子中的無(wú)效喚醒問(wèn)題了。

A 進(jìn)程:

1 set_current_state(TASK_INTERRUPTIBLE);

2 spin_lock(&list_lock);

3if(list_empty(&list_head)) {

4 spin_unlock(&list_lock);

5 schedule();

6 spin_lock(&list_lock);

7 }

8 set_current_state(TASK_RUNNING);

9

10/* Rest of the code ... */

11 spin_unlock(&list_lock);

可以看到,這段代碼在測(cè)試條件之前就將當(dāng)前執(zhí)行進(jìn)程狀態(tài)轉(zhuǎn)設(shè)置成 TASK_INTERRUPTIBLE 了,并且在鏈表不為空的情況下又將自己置為 TASK_RUNNING 狀態(tài)。這樣一來(lái)如果 B 進(jìn)程在 A 進(jìn)程進(jìn)程檢查了鏈表為空以后調(diào)用 wake_up_process(),那么 A 進(jìn)程的狀態(tài)就會(huì)自動(dòng)由原來(lái) TASK_INTERRUPTIBLE變成 TASK_RUNNING,此后即使進(jìn)程又調(diào)用了 schedule(),由于它現(xiàn)在的狀態(tài)是 TASK_RUNNING,所以仍然不會(huì)被從運(yùn)行隊(duì)列中移出,因而不會(huì)錯(cuò)誤的進(jìn)入睡眠,當(dāng)然也就避免了無(wú)效喚醒問(wèn)題。

4 Linux 內(nèi)核的例子

在 Linux 操作系統(tǒng)中,內(nèi)核的穩(wěn)定性至關(guān)重要,為了避免在 Linux 操作系統(tǒng)內(nèi)核中出現(xiàn)無(wú)效喚醒問(wèn)題,Linux 內(nèi)核在需要進(jìn)程睡眠的時(shí)候應(yīng)該使用類似如下的操作:

/* ‘q’是我們希望睡眠的等待隊(duì)列 */

DECLARE_WAITQUEUE(wait,current);

add_wait_queue(q, &wait);

set_current_state(TASK_INTERRUPTIBLE);

/* 或 TASK_INTERRUPTIBLE */

while(!condition) /* ‘condition’ 是等待的條件 */

schedule();

set_current_state(TASK_RUNNING);

remove_wait_queue(q, &wait);

上面的操作,使得進(jìn)程通過(guò)下面的一系列步驟安全地將自己加入到一個(gè)等待隊(duì)列中進(jìn)行睡眠:首先調(diào)用 DECLARE_WAITQUEUE () 創(chuàng)建一個(gè)等待隊(duì)列的項(xiàng),然后調(diào)用 add_wait_queue() 把自己加入到等待隊(duì)列中,并且將進(jìn)程的狀態(tài)設(shè)置為TASK_INTERRUPTIBLE 或者 TASK_INTERRUPTIBLE。然后循環(huán)檢查條件是否為真:如果是的話就沒(méi)有必要睡眠,如果條件不為真,就調(diào)用 schedule()。當(dāng)進(jìn)程 檢查的條件滿足后,進(jìn)程又將自己設(shè)置為 TASK_RUNNING 并調(diào)用 remove_wait_queue() 將自己移出等待隊(duì)列。

從上面可以看到,Linux 的內(nèi)核代碼維護(hù)者也是在進(jìn)程檢查條件之前就設(shè)置進(jìn)程的狀態(tài)為睡眠狀態(tài),然后才循環(huán)檢查條件。如果在進(jìn)程開始睡眠之前條件就已經(jīng)達(dá)成了,那么循環(huán)會(huì)退出并用 set_current_state() 將自己的狀態(tài)設(shè)置為就緒,這樣同樣保證了進(jìn)程不會(huì)存在錯(cuò)誤的進(jìn)入睡眠的傾向,當(dāng)然也就不會(huì)導(dǎo)致出現(xiàn)無(wú)效喚醒問(wèn)題。

下面讓我們用 linux 內(nèi)核中的實(shí)例來(lái)看看 Linux 內(nèi)核是如何避免無(wú)效睡眠的,這段代碼出自 Linux2.6 的內(nèi)核 (linux-2.6.11/kernel/sched.c: 4254):

4253/* Wait for kthread_stop */

4254 set_current_state(TASK_INTERRUPTIBLE);

4255while (!kthread_should_stop()) {

4256 schedule();

4257 set_current_state(TASK_INTERRUPTIBLE);

4258 }

4259 __set_current_state(TASK_RUNNING);

4260return0;

上面的這些代碼屬于遷移服務(wù)線程 migration_thread,這個(gè)線程不斷地檢查 kthread_should_stop(),

直 到 kthread_should_stop() 返回 1 它才可以退出循環(huán),也就是說(shuō)只要 kthread_should_stop() 返回 0 該進(jìn)程就會(huì)一直睡 眠。從代碼中我們可以看出,檢查 kthread_should_stop() 確實(shí)是在進(jìn)程的狀態(tài)被置為 TASK_INTERRUPTIBLE 后才開始執(zhí)行 的。因此,如果在條件檢查之后但是在 schedule() 之前有其他進(jìn)程試圖喚醒它,那么該進(jìn)程的喚醒操作不會(huì)失效。

小結(jié)

通過(guò)上面的討論,可以發(fā)現(xiàn)在 Linux 中避免進(jìn)程的無(wú)效喚醒的關(guān)鍵是在進(jìn)程檢查條件之前就將進(jìn)程的狀態(tài)置為 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE,并且如果檢查的條件滿足的話就應(yīng)該將其狀態(tài)重新設(shè)置為 TASK_RUNNING。這樣無(wú)論進(jìn)程等待的條件是否滿足, 進(jìn)程都不會(huì)因?yàn)楸灰瞥鼍途w隊(duì)列而錯(cuò)誤地進(jìn)入睡眠狀態(tài),從而避免了無(wú)效喚醒問(wèn)題。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11123

    瀏覽量

    207900

原文標(biāo)題:關(guān)于 Linux 進(jìn)程的睡眠和喚醒 ,來(lái)看這篇就夠了~

文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux進(jìn)程睡眠喚醒

    Linux中,僅等待CPU時(shí)間的進(jìn)程稱為就緒進(jìn)程,它們被放置在一個(gè)運(yùn)行隊(duì)列中,一個(gè)就緒進(jìn)程的狀 態(tài)標(biāo)志位為 TASK_RUNNING。一旦一個(gè)運(yùn)行中的
    發(fā)表于 06-07 12:26 ?391次閱讀

    Linux學(xué)習(xí)雜談】之進(jìn)程狀態(tài)

    等待態(tài)的進(jìn)程就是進(jìn)程在等待某些條件,當(dāng)條件成熟之后可以進(jìn)入就緒態(tài)等待CPU的調(diào)度執(zhí)行。進(jìn)程位于等待態(tài)的情況下如果給了它調(diào)度的權(quán)限,CPU也是無(wú)法執(zhí)行的。 淺度睡眠等待的時(shí)候是可以被信
    發(fā)表于 09-27 00:36

    Linux下的進(jìn)程結(jié)構(gòu)

    進(jìn)程不但包括程序的指令和數(shù)據(jù),而且包括程序計(jì)數(shù)器和處理器的所有寄存器及存儲(chǔ)臨時(shí)數(shù)據(jù)的進(jìn)程堆棧,因此正在執(zhí)行的進(jìn)程包括處理器當(dāng)前的一切活動(dòng)。 因?yàn)?b class='flag-5'>Linux是一個(gè)多
    發(fā)表于 05-27 09:24

    如何設(shè)置以及喚醒stm32 ucos下睡眠

    請(qǐng)教一下ucos進(jìn)入睡眠模式,是不是應(yīng)該先掛起所有任務(wù)然后進(jìn)入睡眠,還是直接進(jìn)入睡眠等待喚醒(等待喚醒任務(wù)是否還在執(zhí)行調(diào)度)?
    發(fā)表于 07-29 00:42

    睡眠時(shí)的BOR/LPBOR無(wú)法喚醒

    。但我的意思是,如果棕色的持續(xù)時(shí)間很長(zhǎng),那么PIC通常會(huì)醒來(lái)嗎?棕色是否會(huì)導(dǎo)致PIC無(wú)法正常喚醒或根本無(wú)法喚醒的情況?如果可能的話,那么我會(huì)在睡眠時(shí)也啟用BOR。我可以在沒(méi)有睡眠時(shí)啟用
    發(fā)表于 10-12 14:46

    求助!關(guān)于arduino睡眠喚醒

    我用arduino讀取數(shù)據(jù)寫了一個(gè)睡眠 用看門狗定時(shí)喚醒讀取數(shù)據(jù)但是喚醒后arduino的AI針腳不能讀取求大佬答疑!!
    發(fā)表于 07-02 19:06

    Linux進(jìn)程管理

    Linux進(jìn)程管理 本章主要介紹進(jìn)程的概念、狀態(tài)、構(gòu)成以及Linux進(jìn)程的相關(guān)知識(shí)。 掌握進(jìn)程
    發(fā)表于 04-28 14:57 ?0次下載

    漸響式睡眠喚醒器電路圖

    漸響式睡眠喚醒器電路圖
    發(fā)表于 05-25 13:43 ?1463次閱讀
    漸響式<b class='flag-5'>睡眠</b><b class='flag-5'>喚醒</b>器電路圖

    Linux守護(hù)進(jìn)程詳解

    分享到:標(biāo)簽:進(jìn)程控制 Linux 守護(hù)進(jìn)程進(jìn)程 7.3 Linux守護(hù)進(jìn)程 7.3.1 守
    發(fā)表于 10-18 14:24 ?0次下載
    <b class='flag-5'>Linux</b>守護(hù)<b class='flag-5'>進(jìn)程</b>詳解

    如何設(shè)置Linux進(jìn)程睡眠喚醒

    Linux中,僅等待CPU時(shí)間的進(jìn)程稱為就緒進(jìn)程,它們被放置在一個(gè)運(yùn)行隊(duì)列中,一個(gè)就緒進(jìn)程的狀 態(tài)標(biāo)志位為TASK_RUNNING。
    發(fā)表于 04-23 14:29 ?892次閱讀

    你知道Linux進(jìn)程睡眠喚醒操作?

    Linux 中的進(jìn)程睡眠狀態(tài)有兩種:一種是可中斷的睡眠狀態(tài),其狀態(tài)標(biāo)志位TASK_INTERRUPTIBLE;
    發(fā)表于 04-23 14:56 ?910次閱讀
    你知道<b class='flag-5'>Linux</b><b class='flag-5'>進(jìn)程</b>的<b class='flag-5'>睡眠</b>和<b class='flag-5'>喚醒</b>操作?

    GD32低功耗:深度睡眠喚醒系統(tǒng)時(shí)鐘變慢問(wèn)題

    一、問(wèn)題1、進(jìn)入深度睡眠后,通過(guò)外部中斷喚醒,發(fā)現(xiàn)系統(tǒng)時(shí)鐘變慢。2、進(jìn)入休眠模式,通過(guò)任何中斷喚醒,系統(tǒng)時(shí)鐘正常。二、原因1、從電源管理章節(jié)可知,睡眠模式下使用沒(méi)有什么需要需要注意的,
    發(fā)表于 12-02 15:06 ?21次下載
    GD32低功耗:深度<b class='flag-5'>睡眠</b><b class='flag-5'>喚醒</b>系統(tǒng)時(shí)鐘變慢問(wèn)題

    STM32 低功耗睡眠模式(SLEEP)事件(EVENT)喚醒實(shí)現(xiàn)及優(yōu)化

    STM32 低功耗睡眠模式(SLEEP)事件(EVENT)喚醒實(shí)現(xiàn)及優(yōu)化1. 介紹STM32具有多種低功耗模式,當(dāng)前以STM32L4系列的低功耗模式最為豐富,此處基于STM32L476
    發(fā)表于 12-31 19:08 ?34次下載
    STM32 低功耗<b class='flag-5'>睡眠</b>模式(SLEEP)事件(EVENT)<b class='flag-5'>喚醒</b>實(shí)現(xiàn)及優(yōu)化

    AN010 從深度睡眠模式2喚醒并恢復(fù)

    AN010 從深度睡眠模式2喚醒并恢復(fù)
    發(fā)表于 02-27 18:18 ?1次下載
    AN010 從深度<b class='flag-5'>睡眠</b>模式2<b class='flag-5'>喚醒</b>并恢復(fù)

    Linux電源管理的組成與睡眠喚醒

    (Clock)、頻率(Frequency)、電壓(Voltage)、睡眠/喚醒(Suspend/Resume)等方方面面。 Generic PM 軟件架構(gòu) Generic PM 主要處理關(guān)機(jī)、重啟、冬眠
    的頭像 發(fā)表于 09-11 15:54 ?446次閱讀
    <b class='flag-5'>Linux</b>電源管理的組成與<b class='flag-5'>睡眠</b><b class='flag-5'>喚醒</b>