這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)Linux中進程怎么設(shè)置睡眠和喚醒,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)是專業(yè)的肥鄉(xiāng)網(wǎng)站建設(shè)公司,肥鄉(xiāng)接單;提供成都網(wǎng)站設(shè)計、網(wǎng)站制作,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行肥鄉(xiāng)網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
Linux 中的進程睡眠狀態(tài)分類
一種是可中斷的睡眠狀態(tài),其狀態(tài)標(biāo)志位TASK_INTERRUPTIBLE;
另一種是不可中斷 的睡眠狀態(tài),其狀態(tài)標(biāo)志位為TASK_UNINTERRUPTIBLE??芍袛嗟乃郀顟B(tài)的進程會睡眠直到某個條件變?yōu)檎?,比如說產(chǎn)生一個硬件中斷、釋放 進程正在等待的系統(tǒng)資源或是傳遞一個信號都可以是喚醒進程的條件。不可中斷睡眠狀態(tài)與可中斷睡眠狀態(tài)類似,但是它有一個例外,那就是把信號傳遞到這種睡眠 狀態(tài)的進程不能改變它的狀態(tài),也就是說它不響應(yīng)信號的喚醒。不可中斷睡眠狀態(tài)一般較少用到,但在一些特定情況下這種狀態(tài)還是很有用的,比如說:進程必須等 待,不能被中斷,直到某個特定的事件發(fā)生。
在現(xiàn)代的Linux操作系統(tǒng)中,進程一般都是用調(diào)用schedule()的方法進入睡眠狀態(tài)的,下面的代碼演
示了如何讓正在運行的進程進入睡眠狀態(tài)。
sleeping_task = current; set_current_state(TASK_INTERRUPTIBLE); schedule(); func1(); /* Rest of the code ... */
在***個語句中,程序存儲了一份進程結(jié)構(gòu)指針sleeping_task,current 是一個宏,它指向正在執(zhí)行
的進程結(jié)構(gòu)。set_current_state()將該進程的狀態(tài)從執(zhí)行狀態(tài)TASK_RUNNING 變成睡眠狀態(tài)
TASK_INTERRUPTIBLE。 如果schedule()是被一個狀態(tài)為TASK_RUNNING 的進程調(diào)度,那么schedule()將調(diào)度另外一個進程占用CPU;如果schedule()是被一個狀態(tài)為TASK_INTERRUPTIBLE 或TASK_UNINTERRUPTIBLE 的進程調(diào)度,那么還有一個附加的步驟將被執(zhí)行:當(dāng)前執(zhí)行的進程在另外一個進程被調(diào)度之前會被從運行隊列中移出,這將導(dǎo)致正在運行的那個進程進入睡眠,因為 它已經(jīng)不在運行隊列中了。
我們可以使用下面的這個函數(shù)將剛才那個進入睡眠的進程喚醒。
wake_up_process(sleeping_task);
在調(diào)用了wake_up_process()以后,這個睡眠進程的狀態(tài)會被設(shè)置為TASK_RUNNING,而且調(diào)度器
會把它加入到運行隊列中去。當(dāng)然,這個進程只有在下次被調(diào)度器調(diào)度到的時候才能真正地投入運行。
無效喚醒
幾乎在所有的情況下,進程都會在檢查了某些條件之后,發(fā)現(xiàn)條件不滿足才進入睡眠。可是有的時候
進程卻會在 判定條件為真后開始睡眠,如果這樣的話進程就會***期地休眠下去,這就是所謂的無效喚醒問題。在操作系統(tǒng)中,當(dāng)多個進程都企圖對共享數(shù)據(jù)進行某種處理,而 ***的結(jié)果又取決于進程運行的順序時,就會發(fā)生競爭條件,這是操作系統(tǒng)中一個典型的問題,無效喚醒恰恰就是由于競爭條件導(dǎo)致的。
設(shè)想有兩個進程A 和B,A 進程正在處理一個鏈表,它需要檢查這個鏈表是否為空,如果不空就對鏈
表里面的數(shù)據(jù)進行一些操作,同時B進程也在往這個鏈表添加節(jié)點。當(dāng)這個鏈表是空的時候,由于無數(shù)據(jù)可操作,這時A進程就進入睡眠,當(dāng)B進程向鏈表里面添加了節(jié)點之后它就喚醒A 進程,其代碼如下:
A進程:
spin_lock(&list_lock); if(list_empty(&list_head)) { spin_unlock(&list_lock); set_current_state(TASK_INTERRUPTIBLE); schedule(); spin_lock(&list_lock); } /* Rest of the code ... */ spin_unlock(&list_lock);
B進程:
spin_lock(&list_lock); list_add_tail(&list_head, new_node); spin_unlock(&list_lock); wake_up_process(processa_task);
這里會出現(xiàn)一個問題,假如當(dāng)A進程執(zhí)行到第3行后第4行前的時候,B進程被另外一個處理器調(diào)度
投 入運行。在這個時間片內(nèi),B進程執(zhí)行完了它所有的指令,因此它試圖喚醒A進程,而此時的A進程還沒有進入睡眠,所以喚醒操作無效。在這之后,A 進程繼續(xù)執(zhí)行,它會錯誤地認(rèn)為這個時候鏈表仍然是空的,于是將自己的狀態(tài)設(shè)置為TASK_INTERRUPTIBLE然后調(diào)用schedule()進入睡 眠。由于錯過了B進程喚醒,它將會***期的睡眠下去,這就是無效喚醒問題,因為即使鏈表中有數(shù)據(jù)需要處理,A 進程也還是睡眠了。
避免無效喚醒
如何避免無效喚醒問題呢?我們發(fā)現(xiàn)無效喚醒主要發(fā)生在檢查條件之后和進程狀態(tài)被設(shè)置為睡眠狀
態(tài)之前, 本來B進程的wake_up_process()提供了一次將A進程狀態(tài)置為TASK_RUNNING 的機會,可惜這個時候A進程的狀態(tài)仍然是TASK_RUNNING,所以wake_up_process()將A進程狀態(tài)從睡眠狀態(tài)轉(zhuǎn)變?yōu)檫\行狀態(tài)的努力 沒有起到預(yù)期的作用。要解決這個問題,必須使用一種保障機制使得判斷鏈表為空和設(shè)置進程狀態(tài)為睡眠狀態(tài)成為一個不可分割的步驟才行,也就是必須消除競爭條 件產(chǎn)生的根源,這樣在這之后出現(xiàn)的wake_up_process ()就可以起到喚醒狀態(tài)是睡眠狀態(tài)的進程的作用了。
找到了原因后,重新設(shè)計一下A進程的代碼結(jié)構(gòu),就可以避免上面例子中的無效喚醒問題了。
A進程:
set_current_state(TASK_INTERRUPTIBLE); spin_lock(&list_lock); if(list_empty(&list_head)) { spin_unlock(&list_lock); schedule(); spin_lock(&list_lock); } set_current_state(TASK_RUNNING); /* Rest of the code ... */ spin_unlock(&list_lock);
可以看到,這段代碼在測試條件之前就將當(dāng)前執(zhí)行進程狀態(tài)轉(zhuǎn)設(shè)置成TASK_INTERRUPTIBLE了,并且在鏈表不為空的情況下又將自己置為TASK_RUNNING狀態(tài)。這樣一來如果B進程在A進程進程檢查
了鏈表為空以后調(diào)用wake_up_process(),那么A進程的狀態(tài)就會自動由原來TASK_INTERRUPTIBLE
變成TASK_RUNNING,此后即使進程又調(diào)用了schedule(),由于它現(xiàn)在的狀態(tài)是TASK_RUNNING,所以仍然不會被從運行隊列中移出,因而不會錯誤的進入睡眠,當(dāng)然也就避免了無效喚醒問題。
Linux內(nèi)核的例子
在Linux操作系統(tǒng)中,內(nèi)核的穩(wěn)定性至關(guān)重要,為了避免在Linux操作系統(tǒng)內(nèi)核中出現(xiàn)無效喚醒問題,
Linux內(nèi)核在需要進程睡眠的時候應(yīng)該使用類似如下的操作:
/* ‘q’是我們希望睡眠的等待隊列 */ 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);
上面的操作,使得進程通過下面的一系列步驟安全地將自己加入到一個等待隊列中進行睡眠:首先調(diào)
用DECLARE_WAITQUEUE ()創(chuàng)建一個等待隊列的項,然后調(diào)用add_wait_queue()把自己加入到等待隊列中,并且將進程的狀態(tài)設(shè)置為 TASK_INTERRUPTIBLE 或者TASK_INTERRUPTIBLE。然后循環(huán)檢查條件是否為真:如果是的話就沒有必要睡眠,如果條件不為真,就調(diào)用schedule()。當(dāng)進程 檢查的條件滿足后,進程又將自己設(shè)置為TASK_RUNNING 并調(diào)用remove_wait_queue()將自己移出等待隊列。
從上面可以看到,Linux的內(nèi)核代碼維護者也是在進程檢查條件之前就設(shè)置進程的狀態(tài)為睡眠狀態(tài),
然后才循環(huán)檢查條件。如果在進程開始睡眠之前條件就已經(jīng)達成了,那么循環(huán)會退出并用set_current_state()將自己的狀態(tài)設(shè)置為就緒,這樣同樣保證了進程不會存在錯誤的進入睡眠的傾向,當(dāng)然也就不會導(dǎo)致出現(xiàn)無效喚醒問題。
下面讓我們用linux 內(nèi)核中的實例來看看Linux 內(nèi)核是如何避免無效睡眠的,這段代碼出自Linux2.6的內(nèi)核(linux-2.6.11/kernel/sched.c: 4254):
/* 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ù)線程migration_thread,這個線程不斷地檢查kthread_should_stop(),
直 到kthread_should_stop()返回1它才可以退出循環(huán),也就是說只要kthread_should_stop()返回0該進程就會一直睡 眠。從代碼中我們可以看出,檢查kthread_should_stop()確實是在進程的狀態(tài)被置為TASK_INTERRUPTIBLE后才開始執(zhí)行 的。因此,如果在條件檢查之后但是在schedule()之前有其他進程試圖喚醒它,那么該進程的喚醒操作不會失效。
上述就是小編為大家分享的Linux中進程怎么設(shè)置睡眠和喚醒了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。