自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于 Linux 進程的睡眠和喚醒 ,來看這篇就夠了~

系統(tǒng) Linux
在 Linux 中,僅等待 CPU 時間的進程稱為就緒進程,它們被放置在一個運行隊列中,一個就緒進程的狀 態(tài)標志位為 TASK_RUNNING。

1 Linux 進程的睡眠和喚醒

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

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

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

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

TASK_INTERRUPTIBLE;

另一種是不可中斷 的睡眠狀態(tài),其狀態(tài)標志位為 TASK_UNINTERRUPTIBLE。可中斷的睡眠狀態(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)。

  1. sleeping_task = current
  2. set_current_state(TASK_INTERRUPTIBLE); 
  3. schedule(); 
  4. func1(); 
  5. /* 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í)行:當前執(zhí)行的進程在另外一個進程被調(diào)度之前會被從運行隊列中移出,這將導(dǎo)致正在運行的那個進程進入睡眠,因為 它已經(jīng)不在運行隊列中了。

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

  1. wake_up_process(sleeping_task); 

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

2 無效喚醒

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

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

A 進程:

  1. spin_lock(&list_lock); 
  2. if 
  3. (list_empty(&list_head)) { 
  4.  spin_unlock(&list_lock); 
  5.  set_current_state(TASK_INTERRUPTIBLE); 
  6. schedule(); 
  7. spin_lock(&list_lock); 
  8.  } 
  9. /* Rest of the code ... */ 
  10. spin_unlock(&list_lock); 

B 進程:

  1. 100 spin_lock(&list_lock); 
  2. 101 list_add_tail(&list_head, new_node); 
  3. 102 spin_unlock(&list_lock); 
  4. 103 wake_up_process(processa_task); 

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

3 避免無效喚醒

如何避免無效喚醒問題呢?我們發(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 進程:

  1. set_current_state(TASK_INTERRUPTIBLE); 
  2.  spin_lock(&list_lock); 
  3. if 
  4. (list_empty(&list_head)) { 
  5.  spin_unlock(&list_lock); 
  6.  schedule(); 
  7.  spin_lock(&list_lock); 
  8.  } 
  9.  set_current_state(TASK_RUNNING);  
  10. /* Rest of the code ... */ 
  11.  spin_unlock(&list_lock); 

可以看到,這段代碼在測試條件之前就將當前執(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,所以仍然不會被從運行隊列中移出,因而不會錯誤的進入睡眠,當然也就避免了無效喚醒問題。

4 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’是我們希望睡眠的等待隊列 */

  1. /* ‘q’是我們希望睡眠的等待隊列 */ 
  2. DECLARE_WAITQUEUE(wait,current); 
  3. add_wait_queue(q, &wait); 
  4. set_current_state(TASK_INTERRUPTIBLE); 
  5. /* 或 TASK_INTERRUPTIBLE */ 
  6. while 
  7. (!condition)  
  8. /* ‘condition’ 是等待的條件 */ 
  9. schedule(); 
  10. set_current_state(TASK_RUNNING); 
  11. 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()。當進程 檢查的條件滿足后,進程又將自己設(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ǎo)致出現(xiàn)無效喚醒問題。

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

  1. 4253 /* Wait for kthread_stop */ 
  2. 4254  set_current_state(TASK_INTERRUPTIBLE); 
  3. 4255 
  4.   
  5. while (!kthread_should_stop()) { 
  6. 4256  schedule(); 
  7. 4257  set_current_state(TASK_INTERRUPTIBLE); 
  8. 4258  } 
  9. 4259  __set_current_state(TASK_RUNNING); 
  10. 4260   
  11. return   

上面的這些代碼屬于遷移服務(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() 之前有其他進程試圖喚醒它,那么該進程的喚醒操作不會失效。

小結(jié)

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

責任編輯:武曉燕 來源: 馬哥Linux運維
相關(guān)推薦

2021-09-30 07:59:06

zookeeper一致性算法CAP

2019-08-16 09:41:56

UDP協(xié)議TCP

2017-08-02 15:28:58

Linux進程睡眠和喚醒

2023-11-22 07:54:33

Xargs命令Linux

2022-03-29 08:23:56

項目數(shù)據(jù)SIEM

2021-05-07 07:52:51

Java并發(fā)編程

2022-08-18 20:45:30

HTTP協(xié)議數(shù)據(jù)

2023-12-07 09:07:58

2024-08-27 11:00:56

單例池緩存bean

2017-03-30 22:41:55

虛擬化操作系統(tǒng)軟件

2021-12-13 10:43:45

HashMapJava集合容器

2021-09-10 13:06:45

HDFS底層Hadoop

2023-09-25 08:32:03

Redis數(shù)據(jù)結(jié)構(gòu)

2023-10-04 00:32:01

數(shù)據(jù)結(jié)構(gòu)Redis

2023-11-07 07:46:02

GatewayKubernetes

2021-07-28 13:29:57

大數(shù)據(jù)PandasCSV

2018-10-31 17:22:25

AI人工智能芯片

2021-09-02 07:00:32

鑒權(quán)Web 應(yīng)用Cookie-sess

2023-11-03 08:53:15

StrconvGolang

2021-04-11 08:30:40

VRAR虛擬現(xiàn)實技術(shù)
點贊
收藏

51CTO技術(shù)棧公眾號