<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>

新聞中心

EEPW首頁(yè) > 設計應用 > Linux進(jìn)程的睡眠和喚醒

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

作者: 時(shí)間:2023-12-26 來(lái)源: 收藏

的睡眠和喚醒

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

本文引用地址:http://dyxdggzs.com/article/202312/454280.htm

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

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

Linux 中的進(jìn)程睡眠狀態(tài)有兩種:

· 一種是可中斷的睡眠狀態(tài),其狀態(tài)標志位TASK_INTERRUPTIBLE。

· 另一種是不可中斷 的睡眠狀態(tài),其狀態(tài)標志位為T(mén)ASK_UNINTERRUPTIBLE。

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

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

sleeping_task = current;
set_current_state(TASK_INTERRUPTIBLE);
schedule();
func1();
/* Rest of the code ... */

在第一個(gè)語(yǔ)句中,程序存儲了一份進(jìn)程結構指針 sleeping_task,current 是一個(gè)宏,它指向正在執行的進(jìn)程結構。

set_current_state() 將該進(jìn)程的狀態(tài)從執行狀態(tài) TASK_RUNNING 變成睡眠狀態(tài) TASK_INTERRUPTIBLE。如果 schedule() 是被一個(gè)狀態(tài)為 TASK_RUNNING 的進(jìn)程調度,那么 schedule() 將調度另外一個(gè)進(jìn)程占用CPU。

如果 schedule() 是被一個(gè)狀態(tài)為 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 的進(jìn)程調度,那么還有一個(gè)附加的步驟將被執行:當前執行的進(jìn)程在另外一個(gè)進(jìn)程被調度之前會(huì )被從運行隊列中移出,這將導致正在運行的那個(gè)進(jìn)程進(jìn)入睡眠,因為它已經(jīng)不在運行隊列中了。

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

wake_up_process(sleeping_task);

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

無(wú)效喚醒

幾乎在所有的情況下,進(jìn)程都會(huì )在檢查了某些條件之后,發(fā)現條件不滿(mǎn)足才進(jìn)入睡眠??墒怯械臅r(shí)候進(jìn)程卻會(huì )在判定條件為真后開(kāi)始睡眠,如果這樣的話(huà)進(jìn)程就會(huì )無(wú)限期地休眠下去,這就是所謂的無(wú)效喚醒問(wèn)題。

在操作系統中,當多個(gè)進(jìn)程都企圖對共享數據進(jìn)行某種處理,而 最后的結果又取決于進(jìn)程運行的順序時(shí),就會(huì )發(fā)生競爭條件,這是操作系統中一個(gè)典型的問(wèn)題,無(wú)效喚醒恰恰就是由于競爭條件導致的。

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

A進(jìn)程:
1 spin_lock(&list_lock);
2 if (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ì )出現一個(gè)問(wèn)題,假如當A進(jìn)程執行到第3行后第4行前的時(shí)候,B進(jìn)程被另外一個(gè)處理器調度投入運行。在這個(gè)時(shí)間片內,B進(jìn)程執行完了它所有的指令,因此它試圖喚醒A進(jìn)程,而此時(shí)的A進(jìn)程還沒(méi)有進(jìn)入睡眠,所以喚醒操作無(wú)效。

在這之后,A進(jìn)程繼續執行,它會(huì )錯誤地認為這個(gè)時(shí)候鏈表仍然是空的,于是將自己的狀態(tài)設置為 TASK_INTERRUPTIBLE 然后調用 schedule() 進(jìn)入睡 眠。由于錯過(guò)了B進(jìn)程喚醒,它將會(huì )無(wú)限期的睡眠下去,這就是無(wú)效喚醒問(wèn)題,因為即使鏈表中有數據需要處理,A進(jìn)程也還是睡眠了。

避免無(wú)效喚醒

如何避免無(wú)效喚醒問(wèn)題呢?

我們發(fā)現無(wú)效喚醒主要發(fā)生在檢查條件之后和進(jìn)程狀態(tài)被設置為睡眠狀態(tài)之前,本來(lái)B進(jìn)程的 wake_up_process() 提供了一次將A進(jìn)程狀態(tài)置為 TASK_RUNNING 的機會(huì ),可惜這個(gè)時(shí)候A進(jìn)程的狀態(tài)仍然是 TASK_RUNNING,所以 wake_up_process() 將A進(jìn)程狀態(tài)從睡眠狀態(tài)轉變?yōu)檫\行狀態(tài)的努力 沒(méi)有起到預期的作用。

要解決這個(gè)問(wèn)題,必須使用一種保障機制使得判斷鏈表為空和設置進(jìn)程狀態(tài)為睡眠狀態(tài)成為一個(gè)不可分割的步驟才行,也就是必須消除競爭條 件產(chǎn)生的根源,這樣在這之后出現的 wake_up_process() 就可以起到喚醒狀態(tài)是睡眠狀態(tài)的進(jìn)程的作用了。

找到了原因后,重新設計一下A進(jìn)程的代碼結構,就可以避免上面例子中的無(wú)效喚醒問(wèn)題了。

A進(jìn)程:
1 set_current_state(TASK_INTERRUPTIBLE);
2 spin_lock(&list_lock);
3 if (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);

可以看到,這段代碼在測試條件之前就將當前執行進(jìn)程狀態(tài)轉設置成 TASK_INTERRUPTIBLE 了,并且在鏈表不為空的情況下又將自己置為 TASK_RUNNING 狀態(tài)。

這樣一來(lái)如果B進(jìn)程在A(yíng)進(jìn)程進(jìn)程檢查了鏈表為空以后調用 wake_up_process(),那么A進(jìn)程的狀態(tài)就會(huì )自動(dòng)由原來(lái) TASK_INTERRUPTIBLE 變成 TASK_RUNNING,此后即使進(jìn)程又調用了 schedule(),由于它現在的狀態(tài)是 TASK_RUNNING,所以仍然不會(huì )被從運行隊列中移出,因而不會(huì )錯誤的進(jìn)入睡眠,當然也就避免了無(wú)效喚醒問(wèn)題。

Linux內核的例子

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

/* q 是我們希望睡眠的等待隊列 */
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(q, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/* condition 是等待的條件 */
while (!condition) {
    schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(q, &wait);

上面的操作,使得進(jìn)程通過(guò)下面的一系列步驟安全地將自己加入到一個(gè)等待隊列中進(jìn)行睡眠:首先調用 DECLARE_WAITQUEUE() 創(chuàng )建一個(gè)等待隊列的項,然后調用 add_wait_queue() 把自己加入到等待隊列中,并且將進(jìn)程的狀態(tài)設置為 TASK_INTERRUPTIBLE 或者 TASK_INTERRUPTIBLE。

然后循環(huán)檢查條件是否為真:如果是的話(huà)就沒(méi)有必要睡眠,如果條件不為真,就調用 schedule()。當進(jìn)程檢查的條件滿(mǎn)足后,進(jìn)程又將自己設置為 TASK_RUNNING 并調用 remove_wait_queue() 將自己移出等待隊列。

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

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

/* Wait for kthread_stop */
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
    schedule();
    set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;

上面的這些代碼屬于遷移服務(wù)線(xiàn)程 migration_thread,這個(gè)線(xiàn)程不斷地檢查 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 后才開(kāi)始執行的。因此,如果在條件檢查之后但是在 schedule() 之前有其他進(jìn)程試圖喚醒它,那么該進(jìn)程的喚醒操作不會(huì )失效。

小結

通過(guò)上面的討論,可以發(fā)現在 Linux 中避免進(jìn)程的無(wú)效喚醒的關(guān)鍵是在進(jìn)程檢查條件之前就將進(jìn)程的狀態(tài)置為 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE,并且如果檢查的條件滿(mǎn)足的話(huà)就應該將其狀態(tài)重新設置為 TASK_RUNNING。

這樣無(wú)論進(jìn)程等待的條件是否滿(mǎn)足,進(jìn)程都不會(huì )因為被移出就緒隊列而錯誤地進(jìn)入睡眠狀態(tài),從而避免了無(wú)效喚醒問(wèn)題。



關(guān)鍵詞: Linux 進(jìn)程

評論


相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>