搶佔(preemption)是如何發生的【轉】

轉自:http://linuxperf.com/?p=211

進程切換有自願(Voluntary)和強制(Involuntary)之分,在前文中詳細解釋了兩者的不同,簡單來說,自願切換意味着進程需要等待某種資源,強制切換則與搶佔(Preemption)有關。

搶佔(Preemption)是指內核強行切換正在CPU上運行的進程,在搶佔的過程中並不需要得到進程的配合,在隨後的某個時刻被搶佔的進程還可以恢復運行。發生搶佔的原因主要有:進程的時間片用完了,或者優先級更高的進程來爭奪CPU了。

搶佔的過程分兩步,第一步觸發搶佔,第二步執行搶佔,這兩步中間不一定是連續的,有些特殊情況下甚至會間隔相當長的時間:

  1. 觸發搶佔:給正在CPU上運行的當前進程設置一個請求重新調度的標誌(TIF_NEED_RESCHED),僅此而已,此時進程並沒有切換。
  2. 執行搶佔:在隨後的某個時刻,內核會檢查TIF_NEED_RESCHED標誌並調用schedule()執行搶佔。

搶佔只在某些特定的時機發生,這是內核的代碼決定的。

觸發搶佔的時機

每個進程都包含一個TIF_NEED_RESCHED標誌,內核根據這個標誌判斷該進程是否應該被搶佔,設置TIF_NEED_RESCHED標誌就意味着觸發搶佔。

直接設置TIF_NEED_RESCHED標誌的函數是 set_tsk_need_resched();
觸發搶佔的函數是resched_task()。

TIF_NEED_RESCHED標誌什麼時候被設置呢?在以下時刻:

  • 週期性的時鐘中斷

時鐘中斷處理函數會調用scheduler_tick(),這是調度器核心層(scheduler core)的函數,它通過調度類(scheduling class)的task_tick方法 檢查進程的時間片是否耗盡,如果耗盡則觸發搶佔:

Linux的進程調度是模塊化的,不同的調度策略比如CFS、Real-Time被封裝成不同的調度類,每個調度類都可以實現自己的task_tick方法,調度器核心層根據進程所屬的調度類調用對應的方法,比如CFS對應的是task_tick_fair,Real-Time對應的是task_tick_rt,每個調度類對進程的時間片都有不同的定義。

  • 喚醒進程的時候

當進程被喚醒的時候,如果優先級高於CPU上的當前進程,就會觸發搶佔。相應的內核代碼中,try_to_wake_up()最終通過check_preempt_curr()檢查是否觸發搶佔。

  • 新進程創建的時候

如果新進程的優先級高於CPU上的當前進程,會觸發搶佔。相應的調度器核心層代碼是sched_fork(),它再通過調度類的 task_fork方法觸發搶佔:

  • 進程修改nice值的時候

如果進程修改nice值導致優先級高於CPU上的當前進程,也會觸發搶佔。內核代碼參見 set_user_nice()。

  • 進行負載均衡的時候

在多CPU的系統上,進程調度器儘量使各個CPU之間的負載保持均衡,而負載均衡操作可能會需要觸發搶佔。

不同的調度類有不同的負載均衡算法,涉及的核心代碼也不一樣,比如CFS類在load_balance()中觸發搶佔:

RT類的負載均衡基於overload,如果當前運行隊列中的RT進程超過一個,就調用push_rt_task()把進程推給別的CPU,在這裏會觸發搶佔。

執行搶佔的時機

觸發搶佔通過設置進程的TIF_NEED_RESCHED標誌告訴調度器需要進行搶佔操作了,但是真正執行搶佔還要等內核代碼發現這個標誌纔行,而內核代碼只在設定的幾個點上檢查TIF_NEED_RESCHED標誌,這也就是執行搶佔的時機。

搶佔如果發生在進程處於用戶態的時候,稱爲User Preemption(用戶態搶佔);如果發生在進程處於內核態的時候,則稱爲Kernel Preemption(內核態搶佔)。

執行User Preemption(用戶態搶佔)的時機

  1. 從系統調用(syscall)返回用戶態時;
  2. 從中斷返回用戶態時。

執行Kernel Preemption(內核態搶佔)的時機

Linux在2.6版本之後就支持內核搶佔了,但是請注意,具體取決於內核編譯時的選項:

  • CONFIG_PREEMPT_NONE=y
    不允許內核搶佔。這是SLES的默認選項。
  • CONFIG_PREEMPT_VOLUNTARY=y
    在一些耗時較長的內核代碼中主動調用cond_resched()讓出CPU。這是RHEL的默認選項。
  • CONFIG_PREEMPT=y
    允許完全內核搶佔。

在 CONFIG_PREEMPT=y 的前提下,內核態搶佔的時機是:

  1. 中斷處理程序返回內核空間之前會檢查TIF_NEED_RESCHED標誌,如果置位則調用preempt_schedule_irq()執行搶佔。preempt_schedule_irq()是對schedule()的包裝。
  2. 當內核從non-preemptible(禁止搶佔)狀態變成preemptible(允許搶佔)的時候;
    在preempt_enable()中,會最終調用 preempt_schedule 來執行搶佔。preempt_schedule()是對schedule()的包裝。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章