linux進程調度介紹

來源:互聯網
上載者:User

一、Linux新老版本調度器對比

在 2.6 版本的核心之前,當很多任務都處於活動狀態時,調度器有很明顯的限制。這是由於調度器是使用一個複雜度為 O(n) 的演算法實現的。在這種調度器中,調度任務所花費的時間是一個系統中任務個數的函數。換而言之,活動的任務越多,調度任務所花費的時間越長。在任務負載非常重時,處理器會因調度消耗掉大量的時間,用於任務本身的時間就非常少了。因此,這個演算法缺乏延展性。

在對稱式多處理系統(SMP)中,2.6 版本之前的調度器對所有的處理器都使用一個運行隊列。這意味著一個任務可以在任何處理器上進行調度 —— 這對於負載平衡來說是好事,但是對於記憶體緩衝來說卻是個災難。例如,假設一個任務正在CPU-1 上執行,其資料在這個處理器的緩衝中。如果這個任務被調度到 CPU-2 上執行,那麼資料就需要先在 CPU-1 使其無效,並將其放到 CPU-2 的緩衝中。

以前的調度器還使用了一個運行隊列鎖;因此在 SMP 系統中,選擇一個任務執行就會阻礙其他處理器操作這個運行隊列。結果是空閑處理器只能等待這個處理器釋放出運行隊列鎖,這樣會造成效率的降低。(Linux核心學習筆記:SMP、UMA、NUMA)

最後,在早期的核心中,搶佔是不可能的;這意味著如果有一個低優先順序的任務在執行,高優先順序的任務只能等待它完成。

1.1Linux2.6 調度器簡介

2.6版本的調度器是由 Ingo Molnar 設計並實現的。Ingo 從 1995 年開始就一直參與Linux 核心的開發。他編寫這個新調度器的動機是為喚醒、環境切換和定時器中斷開銷建立一個完全 O(1) 的調度器。觸發對新調度器的需求的一個問題是 JAVA 虛擬機器(JVM)的使用。Java編程模型使用了很多執行線程,在 O(n) 調度器中這會產生很多調度負載。O(1) 調度器在這種高負載的情況下並不會受到太多影響,因此 JVM 可以有效地執行。

2.6版本的調度器解決了以前調度器中發現的 3 個主要問題(O(n) 和 SMP 延展性的問題),還解決了其他一些問題。現在我們將開始探索一下 2.6 版本的調度器的基本設計。

1.1.1主要的調度結構

首先我們來回顧一下 2.6 版本的調度器結構。每個 CPU 都有一個運行隊列,其中包含了 140 個優先順序列表,它們是按照先進先出的順序進行服務的。被調度執行的任務都會被添加到各自運行隊列優先順序列表的末尾。每個任務都有一個時間片,這取決於系統允許執行這個任務多長時間。運行隊列的前 100 個優先順序列表保留給即時任務使用,後 40 個用於使用者任務(參見圖 1)。我們稍後將來看一下為什麼這種區別非常重要。

圖 1. Linux 2.6 調度器的運行隊列結構

除了 CPU 的運行隊列(稱為活動運行隊列(active runqueue))之外,還有一個到期運行隊列。當活動運行隊列中的一個任務用光自己的時間片之後,它就被移動到到期運行隊列(expired runqueue 中。在移動過程中,會對其時間片重新進行計算(因此會體現其優先順序的作用;稍後會更詳細地介紹)。如果活動運行隊列中已經沒有某個給定優先順序的任務了,那麼指向活動運行隊列和到期運行隊列的指標就會交換,這樣就可以讓到期優先順序列表變成活動優先順序的列表。

調度器的工作非常簡單:它在優先順序最高的隊列中選擇一個任務來執行。為了使這個過程的效率更高,核心使用了一個位元影像來定義給定優先順序列表上何時存在任務。因此,在大部分體系架構上,會使用一條 find-first-bit-set 指令在 5 個 32 位的字(140 個優先順序)中哪一位的優先順序最高。尋找一個任務來執行所需要的時間並不依賴於活動任務的個數,而是依賴於優先順序的數量。這使得 2.6 版本的調度器成為一個複雜度為 O(1) 的過程,因為調度時間既是固定的,而且也不會受到活動任務個數的影響。

儘管優先順序調度在 SMP 系統上也可以工作,但是它這種大鎖體系架構意味著當一個 CPU 選擇一個任務進行分發調度時,運行隊列會被這個 CPU 加鎖,其他 CPU 只能等待。2.6版本的調度器不是使用一個鎖進行調度;相反,它對每個運行隊列都有一個鎖。這樣允許所有的 CPU 都可以對任務進行調度,而不會與其他 CPU 產生競爭。另外,由於每個處理器都有一個運行隊列,因此任務通常都是與 CPU 密切相關的,可以更好地利用 CPU 的熱緩衝。

Linux2.6 版本調度器的另外一個優點是它允許搶佔。這意味著當高優先順序的任務準備運行時低優先順序的任務就不能執行了。調度器會搶佔低優先順序的進程,並將這個進程放回其優先順序列表中,然後重新進行調度。

似乎 2.6 版本調度器的 O(1) 特性和搶佔特性還不夠,這個調度器還提供了動態任務優先順序和 SMP 負載平衡功能。

1.1.2動態任務優先順序

為了防止任務獨佔 CPU 從而會餓死其他需要訪問 CPU 的任務,Linux 2.6 版本的調度器可以動態修改任務的優先順序。這是通過懲罰 CPU 綁定的任務而獎勵 I/O 綁定的任務實現的。I/O 綁定的任務通常使用 CPU 來設定 I/O,然後就睡眠等待I/O 操作完成。這種行為為其他任務提供了 CPU 的訪問能力。

由於 I/O 綁定型的任務對於 CPU 訪問來說是無私的,因此其優先順序減少(獎勵)最多 5 個優先順序。CPU 綁定的任務會通過將其優先順序增加最多 5 個優先順序進行懲罰。

任務到底是 I/O 綁定的還是 CPU 綁定的,這是根據互動性原則確定的。任務的互動性指標是根據任務執行所花費的時間與睡眠所花費的時間的對比程度進行計算的。注意,由於 I/O 任務先對 I/O 進行調度,然後再進行睡眠,因此 I/O 綁定的任務會在睡眠和等待 I/O 操作完成上面花費更多的時間。這會提高其互動性指標。有一點值得注意,優先順序的調整隻會對使用者任務進行,對於即時任務來說並不會對其優先順序進行調整。

1.1.3SMP負載平衡

在 SMP 系統中建立任務時,這些任務都被放到一個給定的 CPU 運行隊列中。通常來說,我們無法知道一個任務何時是短期存在的,何時需要長期運行。因此,最初任務到 CPU 的分配可能並不理想。

為了在 CPU 之間維護任務負載的均衡,任務可以重新進行分發:將任務從負載重的 CPU 上移動到負載輕的 CPU 上。Linux 2.6 版本的調度器使用負載平衡(load balancing提供了這種功能。每隔 200ms,處理器都會檢查 CPU 的負載是否不均衡;如果不均衡,處理器就會在 CPU 之間進行一次任務均衡操作。

這個過程的一點負面影響是新 CPU 的緩衝對於遷移過來的任務來說是冷的(需要將資料讀入緩衝中)。

記住 CPU 緩衝是一個本地(片上)記憶體,提供了比系統記憶體更快的訪問能力。如果一個任務是在某個 CPU 上執行的,與這個任務有關的資料都會被放到這個 CPU 的本機快取中,這就稱為熱的。如果對於某個任務來說,CPU 的本機快取中沒有任何資料,那麼這個緩衝就稱為冷的。

不幸的是,保持 CPU 繁忙會出現 CPU 緩衝對於遷移過來的任務為冷的情況。

綜上所述2.6版繼承和發揚2.4版調度器的特點:

(1)  互動式作業優先

(2)  輕載條件下調度/喚醒的高效能

(3)  公平共用

(4)  基於優先順序調度

(5)  搞CPU使用率

(6)  SMP高效親和

(7)  即時調度和cpu綁定等調度手段

在此基礎之上的新特徵:

(1)  O(1)調度演算法,調度器開銷恒定(與當前系統負載無關),即時效能更好

(2)  高可擴充性,鎖粒度大幅度減小

(3)  新設計的SMP親和方法

(4)  最佳化計算密度型的批次工作調度

(5)  重載條件下調度器工作更平滑

(6)  子進程先於父進程運行等其他改進

(7)  增加了對可搶佔核心的支援

二、程式碼分析

2.1主要檔案

include/linux/sched.h

2.2檔案代碼

   分析遠代碼可以用下載linux2.6的原始碼使用軟體source insight進行分析,也可以在http://lxr.oss.org.cn查看linux的原始碼(網站資料比較豐富,各個版本的代碼都有,比較source insight利於尋找不同的代碼,但效率較低)

   task_struct包含有進程的描述資訊、控制資訊以及資源資訊,是進程的靜態描述。2.6 版的核心仍然用 task_struct 來表徵進程,儘管對線程進行了最佳化,但線程的核心表示仍然與進程相同。隨著調度器的改進,task_struct 的內容也有了改進,互動式進程優先支援、核心搶佔支援等新特性,在task_struct 中都有所體現。在 task_struct 中,有的屬性是新增加的,有的屬性的值的含義發生了變化,而有的屬性僅僅是改了一下名字。可稱為進程式控制制塊(TCB),主要包含進程標識符、優先順序、堆棧空間、進程狀態

task_struct原始碼定義在kernel/include/Linux/sched.h中

struct
task_struct {

391         volatile longstate;   
/* -1 unrunnable, 0 runnable, >0 stopped */

392        structthread_info *thread_info;

393         atomic_tusage;

394         unsigned longflags;   
/* per process flags, defined below */

395        unsigned longptrace;

396

397        int lock_depth;        /* Lock depth */

398

399        int prio, static_prio;

400        structlist_head
run_list;

401        prio_array_t *array;

402

403        unsigned long sleep_avg;

404        long interactive_credit;

405        unsigned long longtimestamp;

406        int activated;

407

408        unsigned long policy;

409        cpumask_t cpus_allowed;

410        unsigned int time_slice, first_time_slice;

411

412        structlist_head
tasks;

413        /*

414         * ptrace_list/ptrace_children formsthe list of my children

415         * that were stolen by a ptracer.

416         */

417        structlist_head ptrace_children;

418        structlist_head ptrace_list;

419

420        structmm_struct *mm, *active_mm;

2.2.1核心結構 task_struct中的state

進程的狀態仍然用 state 表示

106 #define TASK_RUNNING            0
107 #define TASK_INTERRUPTIBLE      1
108 #define TASK_UNINTERRUPTIBLE    2
109 #define TASK_STOPPED            4
110 #define TASK_ZOMBIE             8
111 #define TASK_DEAD               16

Linux2.4 task_struct中的state

#define TASK_RUNNING            0
#define TASK_INTERRUPTIBLE      1
#define TASK_UNINTERRUPTIBLE    2
#define TASK_ZOMBIE             4
#define TASK_DEAD               8

2.6新增加了兩種狀態:TRACED、DEAD。

新增加的TASK_DEAD指的是已經退出且不需要父進程來回收的進程。TASK_TRACED供調試使用。

原有的幾個state:

TASK_ZOMBIE一個已經終止的但仍保留有任務的進程(已經死了,戶口未登出)。

TASK_RUNNING就緒態(準確的說你應該是task_runable)

TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE不同深度的睡眠態

TASK_STOPPED描述一個已經停止的進程,當進程收到一個特殊訊號或被時候ptrace系統調用的進程監控,並將監控權交給監控進程。Linux2.4版,核心在建立進程是,為每個進程配兩個連續的物理頁面(8KB)它的頂端(低地址部分)用來儲存進程的task_struct結構(約1KB),剩下的約7KB就是進程的系統空間堆棧,核心可以通過棧寄存器指標ESP快速地方位該進程。

2.2.2thread_info

在linux2.6中。這兩個頁面頂端存放的不再是進程的整個task_struct結構,而是task_中的thread_info,task_struct的大部分資訊儲存在棧外,通過thread_info的task指標可以方便地訪問到。

thread_info是描述一個描述任務的重要結構體,下面將看到thread_info的資料結構定義(/include/asm-386/thread_.h)

27 struct thread_info {
 28         struct task_struct      *task;          /* main task structure */
 29         struct exec_domain      *exec_domain;   /* execution domain */
 30         unsigned long           flags;          /* low level flags */
 31         unsigned long           status;         /* thread-synchronous flags */
 32         __u32                   cpu;            /* current CPU */
 33         __s32                   preempt_count; /* 0 => preemptable, <0 => BUG */
 34 
 35 
 36         mm_segment_t            addr_limit;     /* thread address space:
 37                                                    0-0xBFFFFFFF for user-thead
 38                                                    0-0xFFFFFFFF for kernel-thread
 39                                                 */
 40         struct restart_block    restart_block;
 41 
 42         unsigned long           previous_esp;   /* ESP of the previous stack in case
 43                                                    of nested (IRQ) stacks
 44                                                 */
 45         __u8                    supervisor_stack[0];
 46 };

一些主要的環境資訊如下:

task指標指向其對應的任務控制塊

preempt_count是用來表示核心能否被搶佔的使能成員,如果它大於0.表示核心不能被搶佔:如果等於0,則表示核心處於安全狀態(即沒有加鎖),可以搶佔。

Flags裡面有一個TIF_NEED_RESCHED位,如果此標誌位為1.則表示應該儘快啟動調度器。

2.2.3timestamp

  進程發生調度事件的時間(單位是nanosecond,見下)。包括以下幾類:

  • 被喚醒的時間(在 activate_task() 中設定);
  • 被切換下來的時間(schedule());
  • 被切換上去的時間(schedule());
  • Server Load Balancer相關的賦值(見"調度器相關的Server Load Balancer")。

從這個值與目前時間的差值中可以分別獲得"在就緒隊列中等待啟動並執行時間長度"、"運行時間長度"等與優先順序計算相關的資訊(見"最佳化了的優先順序計算方法")。

2.2.4static_prio

prio是進程的動態優先順序,相當於 2.4 中 goodness() 的計算結果,在 0~MAX_PRIO-1 之間取值(MAX_PRIO 定義為 140),其中 0~MAX_RT_PRIO-1 (MAX_RT_PRIO 定義為100)屬於即時進程範圍,MAX_RT_PRIO~MX_PRIO-1 屬於非即時進程。數值越大,表示進程優先順序越小。是調度器選擇候選進程next的主要依據,static_則是進程的靜態優先順序,應該是進程開始時從父進程繼承來的。nice值沿用 Linux 的傳統,在 -20 到 19
之間變動,數值越大,進程的優先順序越小。nice 是使用者可維護的,但僅影響非即時進程的優先順序。2.6 核心中不再儲存 nice 值,而代之以 static_prio。進程初始時間片的大小僅決定於進程的靜態優先順序,這一點不論是即時進程還是非即時進程都一樣,不過即時進程的 static_prio 不參與優先順序計算。

nice 與 static_prio 之間的關係如下:static_prio = MAX_RT_PRIO + nice + 20

核心定義了兩個宏用來完成這一轉換:PRIO_TO_NICE()、NICE_TO_PRIO()。

76 #define NS_TO_JIFFIES(TIME)     ((TIME) / (1000000000 / HZ))
 77 #define JIFFIES_TO_NS(TIME)     ((TIME) * (1000000000 / HZ))

兩種時間單位 :

系統的時間是以 nanosecond(十億分之一秒)為單位的,但這一數值粒度過細,大部分核心應用僅能取得它的絕對值,感知不到它的精度。

時間相關的核心應用通常圍繞時鐘中斷進行,在 Linux 2.6 中,系統時鐘每 1 毫秒中斷一次(時鐘頻率,用 HZ 宏表示,定義為 1000,即每秒中斷 1000 次,--2.4 中定義為100,很多應用程式也仍然沿用 100 的時鐘頻率),這個時間單位稱為一個 jiffie。很多核心應用都是以 jiffies 作為時間單位,例如進程的已耗用時間片。 

jiffies 與絕對時間之間的轉換公式如下: nanosecond=jiffies*1000000 

 核心用兩個宏來完成兩種時間單位的互換:JIFFIES_TO_NS()、NS_TO_JIFFIES(),很多時間宏也有兩種形式,例如 NS_MAX_SLEEP_AVG 和 MAX_SLEEP_AVG。

2.2.5 activated

activated表示進程因什麼原因進入就緒態,這一原因會影響到調度優先順序的計算。activated 有四個值:

  • -1,進程從 TASK_UNINTERRUPTIBLE 狀態被喚醒;
  • 0,預設值,進程原本就處於就緒態;
  • 1,進程從 TASK_INTERRUPTIBLE 狀態被喚醒,且不在中斷上下文中;
  • 2,進程從 TASK_INTERRUPTIBLE 狀態被喚醒,且在中斷上下文中。 
    activated 初值為 0,在兩個地方修改,一是在 schedule() 中,被恢複為 0,另一個就是 activate_task(),這個函數由 try_to_wake_up() 函數調用,用於啟用休眠進程:
  • 如果是中斷服務程式調用的 activate_task(),也就是說進程由中斷啟用,則該進程最有可能是互動,因此,置 activated=2;否則置activated=1。
  • 如果進程是從 TASK_UNINTERRUPTIBLE 狀態中被喚醒的,則 activated=-1(在try_to_wake_up()函數中)。 

2.2.6sleep_avg

進 程的平均等待時間(以 nanosecond 為單位),在 0 到 NS_MAX_SLEEP_AVG 之間取值,初值為 0,相當於進程等待時間與已耗用時間的差值。sleep_avg 所代表的含義比較豐富,既可用於評價該進程的"互動程度",又可用於表示該進程需要啟動並執行緊迫性。這個值是動態優先順序計算的關鍵因子,sleep_avg 越大,計算出來的進程優先順序也越高(數值越小),後面將會介紹具體函數。

2.2.7interactive_credit

這個變數記錄了本進程的"互動程度",在 -CREDIT_LIMIT 到 CREDIT_LIMIT+1 之間取值。進程被建立出來時,初值為 0,而後根據不同的條件加 1 減 1,一旦超過CREDIT_LIMIT(只可能等於 CREDIT_LIMIT+1),它就不會再降下來,表示進程已經通過了"互動式"測試,被認為是互動式進程了。

2.2.8time_slice

進程的時間片餘額,相當於 2.4 的 counter,但不再直接影響進程的動態優先順序。進 程的time_slice 值代表進程的已耗用時間片剩餘大小,在進程建立時與父進程平分時間片,在運行過程中遞減,一旦歸0,則按 static_prio 值重新賦予上述基準值,並請求調度。時間片的遞減和重設在時鐘中斷中進行(sched_tick()),除此之外,time_slice 值的變化主要在建立進程和進程退出過程中:

a)  進程建立 

和 2.4 類似,為了防止進程通過反覆 fork 來偷取時間片,子進程被建立時並不分配自己的時間片,而是與父進程平分父進程的剩餘時間片。也就是說,fork 結束後,兩者時間片之和與原先父進程的時間片相等。

b) 進程退出 

進程退出時(sched_exit()),根據 first_time_slice 的值判斷自己是否從未重新分配過時間片,如果是,則將自己的剩餘時間片返還給父進程(保證不超過 MAX_TIMESLICE)。這個動作使進程不會因建立短期子進程而受到懲罰(與不至於因建立子進程而受到"獎勵"相對應)。如果進程已經用完了從父進 程那分得的時間片,就沒有必要返還了(這一點在 2.4 中沒有考慮)。

在 2.4 中,進程剩餘時間片是除 nice 值以外對動態優先順序影響最大的因素,並且休眠次數多的進程,它的時間片會不斷疊加,從而算出的優先順序也更大,調度器正是用這種方式來體現對互動式進程的優先策略。但實際上休眠次數多並不表示該進程就是互動,只能說明它是 IO 密集型的,因此,這種方法精度很低,有時因為誤將頻繁訪問磁碟的資料庫應用當作互動式進程,反而造成真正的使用者終端響應遲緩。

2.6的調度器以時間片是否耗盡為標準將就緒進程分成active、expired 兩大類,分別對應不同的就緒隊列,前者相對於後者擁有絕對的調度優先權--僅當active 進程時間片都耗盡,expired進程才有機會運行。但在 active 中挑選進程時,調度器不再將進程剩餘時間片作為影響調度優先順序的一個因素,並且為了滿足核心可剝奪的要求,時間片太長的非即時互動式進程還會被人為地分成好幾段(每一段稱為一個運行粒度,定義見下)運行,每一段運行結束後,它都從 cpu 上被剝奪下來,放置到對應的
active 就緒隊列的末尾,為其他具有同等優先順序的進程提供啟動並執行機會。

這一操作在 schedule_tick() 對時間片遞減之後進行。此時,即使進程的時間片沒耗完,只要該進程同時滿足以下四個條件,它就會被強制從 cpu 上剝奪下來,重新入隊等候下一次調度:

  • 進程當前在 active 就緒隊列中;
  • 該進程是互動式進程(TASK_INTERACTIVE()返回真,見"更精確的互動式進程優先",nice 大於 12 時,該宏返回恒假);
  • 該進程已經耗掉的時間片(時間片基準值減去剩餘時間片)正好是運行粒度的整數倍;
  • 剩餘時間片不小於運行粒度

運行粒度的定義 運行粒度 TIMESLICE_GRANULARITY 被定義為與進程的 sleep_avg 和系統總 CPU 數相關的宏。因為 sleep_avg 實際上代表著進程的非已耗用時間與已耗用時間的差值,與互動程度判斷關係密切,所以,運行粒度的定義說明了核心的以下兩個調度策略:

  • 進程互動程度越高,運行粒度越小,這是互動式進程的運行特點所允許的;與之對應,CPU-bound 的進程為了避免 Cache 重新整理,不應該分區;
  • 系統 CPU 數越多,運行粒度越大。

 

2.2.9 新的資料結構 runqueue

2.4 的就緒隊列是一個簡單的以 runqueue_head 為頭的雙向鏈表,在 2.6 中,就緒隊列定義為一個複雜得多的資料結構 struct runqueue,並且,尤為關鍵的是,每一個 CPU 都將維護一個自己的就緒隊列,這將大大減小競爭(這在前面已經介紹,同時也是調度器的最主要的部分,放在最後詳細介紹)。

O(1)演算法中很多關鍵技術都與 runqueue 有關

1) prio_array_t*active, *expired, arrays[2]

runqueue 中最關鍵的資料結構。每個 CPU 的就緒隊列按時間片是否用完分為兩部分,分別通過 active 指標和 expired 指標訪問,active 指向時間片沒用完、當前可被調度的就緒進程,expired 指向時間片已用完的就緒進程。每一類就緒進程都用一個 structprio_array 的結構表示:

struct prio_array {
  int nr_active;  /* 本進程組中的進程數 */
  struct list_head queue[MAX_PRIO];
       /* 以優先順序為索引的 HASH 表,見下 */
  unsigned long bitmap[BITMAP_SIZE];
       /* 加速以上 HASH 表訪問的位元影像,見下 */
};

在 2.4 版的核心裡,尋找最佳候選就緒進程的過程是在調度器 schedule() 中進行的,每一次調度都要進行一次(在 for 迴圈中調用 goodness()),這種尋找過程與當前就緒進程的個數相關,因此,尋找所耗費的時間是 O(n) 級的,n 是當前就緒進程個數。正因為如此,調度動作的執行時間就和當前系統負載相關,無法給定一個上限,這與即時性的要求相違背。

在新的 O(1) 調度中,這一尋找過程分解為 n 步,每一步所耗費的時間都是 O(1) 量級的。

prio_array 中包含一個就緒隊列數組,數組的索引是進程的優先順序(,相同優先順序的進程放置在相應數組元素的鏈表 queue 中。調度時直接給出就緒隊列 active 中具有最高優先順序的鏈表中的第一項作為候選進程,而優先順序的計算過程則分布到各個進程的執行過程中進行。

為了加速尋找存在就緒進程的鏈表,2.6 核心又建立了一個位映射數組來對應每一個優先順序鏈表,如果該優先順序鏈表非空,則對應位為 1,否則為 0。核心還要求每個體繫結構都構造一個sched_find_first_bit() 函數來執行這一搜尋操作,快速定位第一個非空的就緒進程鏈表。

採用這種將集中計算過程分散進行的演算法,保證了調度器啟動並執行時間上限,同時在記憶體中保留更加豐富的資訊的做法也加速了候選進程的定位過程。這一變化簡單而又高效,是 2.6 核心中的亮點之一。

arrays 二元數組是兩類就緒隊列的容器,active 和 expired 分別指向其中一個。active 中的進程一旦用完了自己的時間片,就被轉移到 expired 中,並設定好新的初始時間片;而當 active 為空白時,則表示當前所有進程的時間片都消耗完了,此時,active 和 expired 進行一次對調,重新開始下一輪的時間片遞減過程。

回憶一下 2.4 調度系統,進程時間片的計算是比較耗時的,在早期核心版本中,一旦時間片耗盡,就在時鐘中斷中重新計算時間片,後來為了提高效率,減小時鐘中斷的處理時 間,2.4 調度系統在所有就緒進程的時間片都耗完以後在調度器中一次性重算。這又是一個 O(n) 量級的過程。為了保證 O(1) 的調度器執行時間,2.6 的時間片計算在各個進程耗盡時間片時單獨進行,而通過以上所述簡單的對調來完成時間片的輪轉。這又是 2.6 調度系統的一個亮點。

2) spinlock_tlock

runqueue 的自旋鎖,當需要對 runqueue 進行操作時,仍然應該鎖定,但這個鎖定操作隻影響一個 CPU 上的就緒隊列,因此,競爭發生的機率要小多了。

3) task_t *curr

本 CPU 正在啟動並執行進程。

4) tast_t *idle

指向本 CPU 的 idle 進程,相當於 2.4 中init_tasks[this_cpu()] 的作用。

5) intbest_expired_prio

記錄 expired 就緒進程組中的最高優先順序(數值最小)。該變數在進程進入 expired 隊列的時候儲存(schedule_tick()),用途見"expired_timestamp"的解釋)。

6) unsigned longexpired_timestamp

當 新一輪的時間片遞減開始後,這一變數記錄著最早發生的進程耗完時間片事件的時間(jiffies 的絕對值,在 schedule_tick() 中賦),它用來表徵 expired 中就緒進程的最長等待時間。它的使用體現在 EXPIRED_STARVING(rq) 宏上。

上 面已經提到,每個 CPU 上維護了兩個就緒隊列,active 和 expired。一般情況下,時間片結束的進程應該從 active 隊列轉移到 expired 隊列中(schedule_tick()),但如果該進程是互動式進程,調度器就會讓其保持在 active 隊列上以提高它的響應速度。這種措施不應該讓其他就緒進程等待過長時間,也就是說,如果 expired 隊列中的進程已經等待了足夠長時間了,即使是互動式進程也應該轉移到 expired 隊列上來,排空 active。這個閥值就體現在EXPIRED_STARVING(rq)
上:在expired_timestamp 和 STARVATION_LIMIT 都不等於 0 的前提下,如果以下兩個條件都滿足,則EXPIRED_STARVING() 返回真:

  • (當前絕對時間 - expired_timestamp) >= (STARVATION_LIMIT * 隊列中所有就緒進程總數 + 1),也就是說 expired 隊列中至少有一個進程已經等待了足夠長的時間;
  • 正在啟動並執行進程的靜態優先順序比 expired 隊列中最高優先順序要低(best_expired_prio,數值要大),此時當然應該儘快排空 active 切換到expired 上來。

7) structmm_struct *prev_mm

保 存進程切換後被調度下來的進程(稱之為 prev)的 active_mm 結構指標。因為在 2.6 中 prev 的 active_mm 是在進程切換完成之後釋放的(mmdrop()),而此時 prev 的 active_mm 項可能為 NULL,所以有必要在 runqueue 中預先保留。

8) unsigned longnr_running

本 CPU 上的就緒進程數,該數值是 active 和 expired 兩個隊列中進程數的總和,是說明本 CPU 負載情況的重要參數。

9) unsigned longnr_switches

記錄了本 CPU 上自調度器運行以來發生的進程切換的次數。

10) unsignedlong nr_uninterruptible

記錄本 CPU 尚處於TASK_UNINTERRUPTIBLE 狀態的進程數,和負載資訊有關。

11) atomic_tnr_iowait

記錄本 CPU 因等待 IO 而處於休眠狀態的進程數。

12) unsignedlong timestamp_last_tick

本就緒隊列最近一次發生調度事件的時間,在Server Load Balancer的時候會用到。

13) intprev_cpu_load[NR_CPUS]

記錄進行Server Load Balancer時各個 CPU 上的負載狀態(此時就緒隊列中的 nr_running 值),以便分析負載情況。

14) atomic_t*node_nr_running; int prev_node_load[MAX_NUMNODES]

這兩個屬性僅在 NUMA 結構下有效,記錄各個 NUMA 節點上的就緒進程數和上一次Server Load Balancer操作時的負載情況。

15) task_t*migration_thread

指向本 CPU 的遷移進程。每個 CPU 都有一個核心線程用於執行進程遷移操作。

16) structlist_head migration_queue

需要進行遷移的進程列表。

(以上各個功能將在具體的調度函數recalc_task_prio中詳細說明)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.