搶占(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()的包裝。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM