1. 程式人生 > >Linux進程切換(2) 同步處理

Linux進程切換(2) 同步處理

選擇 釋放 ron 是否 evel 時間片 禁止搶占 調用 這才

一、前言

本文主要描述了主調度器(schedule函數)中的同步處理。

二、進程調度簡介

進程切換有兩種,一種是當進程由於需要等待某種資源而無法繼續執行下去,這時候只能是主動將自己掛起(調用schedule函數),引發一次任務調度過程。另外一種是進程被搶占。所謂搶占(preempt)就是在當前進程歡快執行的時候,終止其對CPU資源的占用,切換到另外一個更高優先級的進程執行。進程被搶占往往是由於各種調度事件的發生:

1、 時間片用完

2、 在中斷上下文中喚醒其他優先級更高的進程

3、 在其他進程上下文中喚醒其他優先級更高的進程。

4、 在其他進程上下文中修改了其他進程的調度參數

5、 ……

在當前進程被搶占的場景下,調度並不是立刻發生,而是延遲執行,具體的方法是設定當前進程的need_resched等於1,然後靜靜的等待最近一個調度點的來臨,當調度點到來的時候,內核會調用schedule函數,搶占當前task的執行。

此外,我們還需要了解基本的搶占控制的知識。在一個進程的thread info中有一個preempt_count的成員用來控制搶占,當該成員等於0的時候表示允許搶占,在本文中,我們分別用preempt counter、hardirq counter和softirq counter分別表示其中的bit field。更詳細的描述可以參考相關文檔的描述

三、schedule函數使用了哪些同步機制

schedule函數的代碼框架如下:

asmlinkage __visible void __sched schedule(void)
{
do {
preempt_disable();-----------------a

raw_spin_lock_irq(&rq->lock);--------b

選擇next task

切到next task執行

raw_spin_unlock_irq(&rq->lock); -------c
sched_preempt_enable_no_resched(); --------d
} while (need_resched()); ---------------e
}

我們以X進程切換到Y進程為例,描述schedule函數中同步機制的使用情況。在X進程上下文中,a點首先關閉了搶占,X task的preempt counter會加1。然後在b點會持有該CPU runqueue的spinlock,當然在這個過程中會disable CPU中斷處理,同時將X task的preempt counter再次加1,這時候X task的preempt counter應該等於2。

打開X task的搶占的時候是在重新調度X在某個CPU上執行的時候,這時候,在上面代碼中的c和d點來遞減preempt counter,當進入e點的時候,preempt counter已經等於0。

由於在切換過長設計runqueue隊列的操作,因此需要spin lock來保護。不過在進程切換過程中,runqueue spin lock是不同進程來協同處理的。我們仍然以X進程切換到Y進程為例。在X進程中,在b點持鎖並disable了本地中斷,而spin lock的釋放是在Y進程中完成的(c點),在釋放spin lock的同時,也會打開cpu中斷。

四、可不可以禁止搶占的時候調用schedule函數

在進程上下文中,下面的調用序列是否可以呢?

preempt_disable

……schedule……

preempt_enable

無論什麽場景,disable preempt然後調用schedule都是很奇怪的一件事情:本來你已經禁止搶占了,但是又顯示的調用schedule函數,你這不是精神分裂嗎?schedule函數怎麽處理這個精神分裂的task呢?在調用schedule函數之前,它毫無疑問是期待preempt count等於0的,只有當前task的preempt count等於0才說明搶占的合理性。不過在整個進程切換的過程中,首先會在a點禁止搶占,這樣可以確保CPU和當前task之間的關系不變(cpu不變、current task不變,runqueue不變)。這樣,在a和b之間的對caller的調用檢查就比較好開展了,具體如下:

static inline void schedule_debug(struct task_struct *prev)
{

if (unlikely(in_atomic_preempt_off())) {
__schedule_bug(prev);
preempt_count_set(PREEMPT_DISABLED);
}
}

in_atomic_preempt_off這個宏就是對當前preempt count進行測試,這時候正確的preempt counter應該是等於1,其他的bit field,例如softirq counter、hardirq count等都是0。具體關於preempt count的位域描述可以參考本站軟中斷的文檔。如果沒有設定正確的preempt_count就調用schedule函數,那麽說明在atomic上下文中錯誤的進行了調度,__schedule_bug會打印出相關信息,方便調試。

雖然在錯誤的場景中調用了schedule函數,但是內核還是要艱難前行啊,因此這裏會修改preempt count的值為PREEMPT_DISABLED,而這才是進入schedule函數正確的姿勢。

五、可不可以關閉中斷調用schedule函數?

在進程上下文中,下面的調用序列是否可以呢?

local_irq_disable

……schedule……

local_irq_enable

當然這裏也許不是直接調用schedule函數,很多內核接口API會隱含調用schedule函數,因此也許你會有意無意的寫出上面形態的代碼。

首先需要明確一點:從X進程切換到Y進程的時候,如果在X進程中關閉中斷,然後切換到Y進程,如果中斷不恢復的話,那麽Y進程會一直執行,直到Y自己良心發現,讓出CPU。這當然是不被允許的。因此,在調用schedule進行進程切換的時候,無論調用者是否關閉中斷,在b點都會關閉中斷(註意,這時候並沒有記錄之前的中斷狀態)。而在切入到Y進程之後,在c點都會顯式的打開CPU中斷。因此,上面的代碼雖然不推薦,但是也不會對調度產生太大的影響。

六、禁止中斷是否可以禁止搶占?

禁止了中斷的確等於了禁止搶占,但是並不意味著它們兩個完全等同,因為在preempt disable---preempt enable這個的調用過程中,在打開搶占的時候有一個搶占點,內核控制路徑會在這裏檢查搶占,如果滿足搶占條件,那麽會立刻調度schedule函數進行進程切換,但是local irq disable---local irq enable的調用中,並沒有顯示的搶占檢查點,當然,中斷有點特殊,因為一旦打開中斷,那麽pending的中斷會進來,並且在返回中斷點的時候會檢查搶占,但是也許下面的這個場景就無能為力了。進程上下文中調用如下序列:

(1)local irq disable

(2)wake up high level priority task

(3)local irq enable

當喚醒的高優先級進程被調度到本CPU執行的時候,按理說這個高優先級進程應該立刻搶占當前進程,但是這個場景無法做到。在調用try_to_wake_up的時候會設定need resched flag並檢查搶占,但是由於中斷disable,因此不會立刻調用schedule,但是在step (3)的時候,由於沒有檢查搶占,這時候本應立刻搶占的高優先級進程會發生嚴重的調度延遲.....直到下一個搶占點到來。

Linux進程切換(2) 同步處理