1. 程式人生 > >Linux程序的睡眠和喚醒(一個定時訊號喚醒睡眠中的程序)

Linux程序的睡眠和喚醒(一個定時訊號喚醒睡眠中的程序)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

        突然想到Nginx中時間更新這塊處理,Nginx中為了減少呼叫系統呼叫gettimeofday這個函式(因為一旦呼叫了系統呼叫,就會使得程序從使用者態切換到核心態,就會發生上下文切換,這個代價很大且不值得)而設定了系統時間更新的次數,內部時間更新有兩種方式,一種就是在配置檔案中設定更新的評論,另一種是沒有設定更新頻率,在後面這種情況下,可以使用epoll_wait()這個函式的最後一個引數來控制反覆的時間間隔,在Nginx內部使用了紅黑樹來管理定時事件,每次從堆頂選擇一個最近的定時事件最為epoll_wait函式的最後一個引數。但是第一種方式是如何處理,系統內部設定了一個定時器,定時事件的時間間隔就是系統配置檔案中使用者自己設定的事件間隔,每次到一定的時間間隔後,都會有一個定時時間發生,如果epoll_wait處於睡眠狀態,那麼此程序就會被喚醒。這麼一說,一個程序如果處於睡眠狀態,那麼使用者也是可以使用別的方式來喚醒一個睡眠中的程序了!

     我們再來回想一下前面有篇介紹EAGAIN和EINTER這兩個錯誤的文章,EINTER這個錯誤時慢系統呼叫而觸發的,如果慢系統呼叫睡眠了,這個時候有訊號到達了,睡眠的程序被喚醒了,然後習慣性的檢視訊號佇列,然後程序了使用者的訊號處理函式。這是正常的處理邏輯。所以在Nginx中使用一個定時器來更新時間是可靠的(定時器中的訊號處理函式就是更新時間的。)

  接下來我們看一下核心是如何處理程序的睡眠和喚醒的。



休眠(被阻塞)的程序處於一個特殊的不可執行狀態。程序休眠由多種原因,但肯定都是為了等待一些事件。事件可能是一段時間從檔案I/O讀取更多資料,或者是某個硬體事件。一個程序還由可能在嘗試獲取一個已被佔用的核心訊號量時被迫進入休眠。休眠的一個常見原因就是檔案I/O —— 如程序對一個檔案執行了read()操作,而這需要從磁盤裡讀取。還有,程序在獲取鍵盤輸入的時候也需要等待。無論哪種情況,核心的操作都相同:程序把自己標記成休眠狀態,從可執行紅黑樹中移出,放入等待佇列,然後呼叫schedule()選擇和執行一個其他程序。

喚醒的過程剛好相反:程序被設定為可執行狀態,然後再從等待佇列中移到可執行紅黑樹中。

休眠由兩種相關的程序狀態:TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 。它們唯一的區別是處於 TASK_UNINTERRUPTIBLE 的程序會忽略訊號,而處於 TASK_INTERRUPTIBLE 狀態的程序如果接收到一個訊號,會被提前喚醒並響應該訊號。兩種狀態的程序位於同一個等待佇列上,等待某些事件,不能夠執行。

在Linux中,僅等待CPU時間的程序稱為就緒程序,它們被放置在一個執行佇列中,一個就緒程序的狀 態標誌位為TASK_RUNNING。一旦一個執行中的程序時間片用完, Linux 核心的排程器會剝奪這個程序對CPU的控制權,並且從執行佇列中選擇一個合適的程序投入執行。 當然,一個程序也可以主動釋放CPU的控制權。函式 schedule()是一個排程函式,它可以被一個程序主動呼叫,從而排程其它程序佔用CPU。一旦這個主動放棄CPU的程序被重新排程佔用 CPU,那麼它將從上次停止執行的位置開始執行,也就是說它將從呼叫schedule()的下一行程式碼處開始執行。 有時候,程序需要等待直到某個特定的事件發生,例如裝置初始化完成、I/O 操作完成或定時器到時等。在這種情況下,程序則必須從執行佇列移出,加入到一個等待佇列中,這個時候程序就進入了睡眠狀態。

等待佇列

休眠通過等待佇列進行處理。等待佇列是由等待某些事件發生的程序組成的簡單鏈表。核心用wake_queue_head_t來代表等待佇列。等待佇列可以通過 DECLARE_WAITQUEUE() 靜態建立,也可以由 init_waitqueue_head() 動態建立。程序把自己放入等待佇列中並設定成不可執行狀態。當與等待佇列相關的事件發生的時候,佇列上的程序會被喚醒。為了避免產生競爭條件,休眠和喚醒的實現不能有紕漏。

針對休眠,以前曾經使用過一些簡單的介面。但那些介面會帶來競爭條件:有可能導致在判定條件變為真後,程序卻開始了休眠,那樣就會使程序無限期的休眠下去。所以,在核心中進行休眠的推薦操作就相對複雜些:

/* 'q' 是我們希望休眠的等待佇列 */DEFINE_WAIT(wait);add_wait_queue(q, &wait);while (!condition) { /* 'condition' 是我們在等待的事件 */        prepare_to_wait(&q, &wait, TASK_INTERRUPTIBLE);        if (signal_pending(current))                /* 處理訊號 */        schedule();}finish_wait(&q, &wait);

程序通過執行下面幾個步驟將自己加入到一個等待佇列中:

  1. 呼叫巨集 DEFINE_WAIT() 建立一個等待佇列的項。
  2. 呼叫 add_wait_queue() 把自己加入到佇列中(連結串列操作)。該佇列會在程序等待的條件滿足時喚醒它。當然我們必須在其他地方撰寫相關程式碼,在事件發生時,對等待佇列執行 wake_up() 操作。
  3. 呼叫 prepare_to_wait() 方法將程序的狀態變更為 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 。而且該函式會在必要的情況下將程序加回到等待佇列,這是在接下來的迴圈遍歷中所需要的。
  4. 如果狀態被設定為 TASK_INTERRUPTIBLE ,則訊號喚醒程序。這就是所謂的偽喚醒(喚醒不是因為事件的發生),因此檢查並處理訊號。
  5. 當程序被喚醒的時候,它會再次檢查條件是否為真。如果是,它就退出迴圈;如果不是,它再次呼叫 schedule() 並一直重複這步操作。
  6. 當條件滿足後,程序將自己設定為 TASK_RUNNING 並呼叫 finish_wait() 方法把自己移出等待佇列。

如果在程序開始休眠之前條件就已經達成,那麼迴圈會退出,程序不會存在錯誤的進入休眠的傾向。需要注意的是,核心程式碼在迴圈體內常常需要完成一些其他任務,比如,它可能在呼叫 schedule() 之前需要釋放掉鎖,而在這以後重新獲取它們,或者響應其他事件。

喚醒

喚醒操作通過函式 wake_up() 進行,它會喚醒指定的等待佇列上的所有程序。它呼叫函式 try_to_wake_up() ,該函式負責將程序設定為 TASK_RUNNING 狀態,呼叫 enqueue_task() 將此程序放入紅黑樹中,如果被喚醒的程序優先順序比當前執行的程序優先順序高,還要設定 need_resched 標誌。通常哪段程式碼促使等待條件達成,它就要負責隨後呼叫 wake_up() 函式 。舉例來說,當磁碟資料到來時,VFS 就要負責對等待佇列呼叫 wake_up() ,以便喚醒佇列中等待這些資料的程序。

關於休眠有一點需要注意,存在虛假的喚醒(訊號)。有時候程序被喚醒並不是因為它所等待的條件達成了,所以需要用一個迴圈處理來保證它等待的條件真正達成。圖 4-1 描述了每個排程程式狀態之間的關係。

需要注意核心處理程序的睡眠和喚醒是怎麼處理的。同時睡眠的程序也是有區別的!!

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述