主頁 > 軟體設計 > 深入Linux內核架構——行程管理和調度(二)

深入Linux內核架構——行程管理和調度(二)

2020-12-13 10:26:52 軟體設計

一、調度器的實作

調度器的任務是在程式之間共享CPU時間,創造并行執行的錯覺,該任務可分為調度策略和背景關系切換兩個不同部分,

1、概觀
暫時不考慮實時行程,只考慮CFS調度器,經典的調度器對系統中的行程分別計算時間片,使行程運行直至時間片用盡,所有行程的所有時間片用完時,需要重新計算,相比之下,CFS只考慮行程等待時間,即行程在就緒佇列(run_queue)中已等待的時間,對CPU時間需求最嚴格的行程被調度執行,每次調度器會挑選具有最高等待時間的行程提供CPU,如此行程的不公平等待不會被積累,而會均勻分布到系統所有行程,

下圖是CFS調度器的作業原理,所有可運行行程都按等待時間在一個紅黑樹中排序,等待CPU時間最長的行程是最左側的項,調度器下一次會考慮該行程,等待時間稍短的行程在該樹上從左至右排序,(調度器時間復雜度為O(logn))
在這里插入圖片描述
除了紅黑樹外,就緒佇列還裝備了虛擬時鐘,該時鐘的時間流逝速度慢于實際的時鐘,精確的速度依賴于當前等待調度器挑選的行程數目(如4個行程,那么虛擬時鐘將以實際時鐘的四分之一的速度運行,如果以完全公平的方式分享計算能力,那么該時鐘是判斷等待行程將獲得多少CPU時間的基準),

就緒佇列的虛擬時間由fair_clock給出,行程的等待時間保存在wait_runtime中,為排序紅黑樹上的行程,內核使用差值fair_clock - wait_runtime的絕對值,fair_clock是完全公平調度的情況下行程將會得到的CPU時間的度量,而wait_runtime直接度量了實際系統的不足造成的不公平,

在行程允許運行時,將從wait_runtime減去它已經運行的時間,這樣,在按時間排序的樹中它會向右移動到某一點,另一個行程將成為最左邊,下一次會被調度器選擇,

當前該調度策略還受到的影響因素:

  • 行程的不同優先級(即,nice值)必須考慮,更重要的行程必須比次要行程更多的CPU時間份額,
  • 行程不能切換得太頻繁,因為背景關系切換,即從一個行程改變到另一個,是有一定開銷的,另一方面,兩次相鄰的任務切換之間,時間也不能太長,否則會累積比較大的不公平值,

2、資料結構
調度器使用一系列資料結構排序和管理系統中的行程,調度器的作業方式與這些結構的設計密切相關,幾個組件在許多方面彼此互動,如圖2所示,在這里插入圖片描述
激活調度器方法:

  • 直接的,行程打算睡眠或其他因素放棄cpu,(通用調度器,generic scheduler,本質上是分配器)
  • 周期性的,以固定頻率運行,不時檢查是否有必要進行行程切換,(核心調度器,core scheduler)

調度類:用于判斷接下來運行哪個行程,內核支持不同的調度策略(完全公平調度、實時調度、在無事可做時調度空閑行程),調度類使得能夠以模塊化方法實作這些策略,即一個類的代碼不需要與其他類的代碼互動,

行程切換:在選中將執行的程式之后,要執行底層的任務切換,(需要與CPU緊密互動)

注:每個行程都剛好屬于某一調度類,各個調度類負責管理所屬的行程,通用調度器自身完全不涉及行程管理,其作業都委托給調度器類,

(1)task_struct成員

各行程的task_struct有幾個成員與調度相關:

struct task_struct {
...
    int prio, static_prio, normal_prio; // prio和normal_prio表示動態優先級,static_prio表示行程的靜態優先級,靜態優先級是行程啟動時分配的優先級,它可以用nice和sched_setscheduler系統呼叫修改,否則在行程運行期間會一直保持恒定,normal_priority表示基于行程的靜態優先級和調度策略計算出的優先級,調度器考慮的優先級則保存在prio,由于在某些情況下內核需要暫時提高行程的優先級,因此需要第3個成員來表示,
    
    unsigned int rt_priority; //表示實時行程的優先級,該值不會代替先前討論的那些值,最低的實時優先級為0,最高的是99,值越大,優先級越高,這里使用的慣例不同于nice值,
    
    struct list_head run_list; //是回圈實時調度器所需要的,但不用于完全公平調度器,run_list是一個表頭,用于維護包含各行程的一個運行表
    
    const struct sched_class *sched_class; //表示該行程所屬的調度器類
    
    struct sched_entity se; //由于調度器設計為處理可調度的物體,在調度器看來各個行程必須也像是這樣的物體,因此se在task_struct中內嵌了一個sched_entity實體,調度器可據此操作各個task struct,
    
    unsigned int policy; //保存了對該行程應用的調度策略,(SCHED_NORMAL用于普通行程,SCHED_BATCH用于非互動、CPU使用密集的批處理行程,SCHED_IDLE是空閑行程,SCHED_RR和SCHED_FIFO用于實作軟實時行程,)
    
    cpumask_t cpus_allowed; //是一個位域,在多處理器系統上用來限制行程可以在哪些CPU上運行
    
    unsigned int time_slice; // 是回圈實時調度器所需要的,但不用于完全公平調度器,time_slice則指定行程可使用CPU的剩余時間段
...
}

(2)調度器類

調度器類由特定資料結構中匯集的幾個函式指標表示,全域調度器請求的各個操作都可以由一個指標表示,這使得無需了解不同調度器類的內部作業原理,即可創建通用調度器,

對各個調度類,都必須提供struct sched_class的一個實體,調度類之間的層次結構是平坦的:實時行程最重要,在完全公平行程之前處理;而完全公平行程則優先于空閑行程;空閑行程只有CPU無事可做時才處于活動狀態,next成員將不同調度類的sched_class實體,按上述順序連接起來,要注意這個層次結構在編譯時已經建立,

用戶層應用程式無法直接與調度類互動,它們只知道上文定義的常量SCHED_…,在這些常量和可用的調度類之間提供適當的映射,這是內核的作業,SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE映射到fair_sched_class,而SCHED_RR和SCHED_FIFO與rt_sched_class關聯,fair_sched_class和rt_sched_class都是struct sched_class的實體,分別表示完全公平調度器和實時調度器,

(3)就緒佇列

就緒佇列:核心調度器用于管理活動行程的主要資料結構,各個CPU都有自身的就緒佇列,各個活動行程只出現在一個就緒佇列中,行程不是由就緒佇列的成員直接管理的,而是有調度類管理,就緒佇列中嵌入了特定于調度器類的子就緒佇列,

就緒佇列核心成員及解釋:

struct rq {
    unsigned long nr_running; //指定了佇列上可運行行程的數目,不考慮其優先級或調度類
    
#define CPU_LOAD_IDX_MAX 5

    unsigned long cpu_load[CPU_LOAD_IDX_MAX]; //用于跟蹤此前的負荷狀態
...
    struct load_weight load; //提供了就緒佇列當前負荷的度量
    
    struct cfs_rq cfs;  //嵌入的子就緒佇列,用于完全公平調度器
    
    struct rt_rq rt;  //嵌入的子就緒佇列,用于和實時調度器
    
    struct task_struct *curr, *idle; //指向idle行程的task_struct實體,該行程亦稱為idle執行緒
    
    u64 clock; //用于實作就緒佇列自身的時鐘,每次呼叫周期性調度器時,都會更新clock的值,
...
};

系統的所有就緒佇列都在runqueues陣列中,該陣列的每個元素分別對應于系統中的一個CPU,在單處理器系統中,由于只需要一個就緒佇列,陣列只有一個元素,

(4)調度物體

由于調度器可以操作比行程更一般的物體,因此需要一個適當的資料結構來描述此類物體,其定義為:

struct sched_entity {

    struct load_weight load;  //指定了權重,用于負載均衡
    
    struct rb_node run_node; //是標準的樹結點,使得物體可以在紅黑樹上排序
    
    unsigned int on_rq; //表示該物體當前是否在就緒佇列上接受調度
    
    u64 exec_start; //新行程加入就緒佇列時,或者周期性調度器中,每次呼叫時,會計算當前時間和exec_start之間的差值,exec_start則更新到當前時間,差值則被加到sum_exec_runtime,
    
    u64 sum_exec_runtime; //用于記錄消耗的CPU時間
    
    u64 vruntime; //統計行程執行期間虛擬時鐘上流逝的時間數量
    
    u64 prev_sum_exec_runtime; //行程被撤銷CPU時,保存當前sum_exec_runtime
...
}

3、處理優先級

(1)優先級的內核表示

在用戶空間可以通過nice命令設定行程的靜態優先級,這在內部會呼叫nice系統呼叫,行程的nice值在-20和+19之間(包含),值越低,表明優先級越高,

內核使用一個簡單些的數值范圍,從0到139(包含),用來表示內部優先級,同樣是值越低,優先級越高,從0到99的范圍專供實時行程使用,nice值[-20, +19]映射到范圍100到139,

(2)計算優先級

行程的優先級計算需要考慮動態優先級(prio),普通優先級(normal_prio)和靜態優先級(static_prio),呼叫相關函式計算結果,(完成設定到優先級內核表示的轉換)

圖3綜述了針對不同型別行程的計算結果,
在這里插入圖片描述
在行程分支出子行程時,子行程的靜態優先級繼承自父行程,子行程的動態優先級,即task_struct->prio,則設定為父行程的普通優先級,這確保了實時互斥量引起的優先級提高不會傳遞到子行程,

(3)計算負荷權重

行程的重要性由優先級和保存在task_struct->se.load的負荷權重同時決定,行程每降低一個nice值,則多獲得10%的CPU時間,每升高一個nice值,則放棄10%的CPU時間,

4、核心調度器

調度器的實作基于兩個函式:周期性調度器函式和主調度器函式,

(1)周期性調度器

在scheduler_tick中實作,如果系統正在活動中,內核會按照頻率HZ自動呼叫該函式,
沒行程等待時,供電不足情況下,可以關閉周期性調度器以減少電能消耗,
主要任務是管理內核中與系統和每個行程的調度相關的統計量和激活負責當前行程的調度類的周期性調度方法,

void scheduler_tick(void)
{
    int cpu = smp_processor_id();
    
    struct rq *rq = cpu_rq(cpu);
    
    struct task_struct *curr = rq->curr;
...

    __update_rq_clock(rq);  //更新struct rq當前實體的時鐘時間戳
    
    update_cpu_load(rq);   //更新rq->cup_load[]陣列
    
    if (curr != rq->idle)
    
    curr->sched_class->task_tick(rq, curr);
}

如果當前行程應該被重新調度,那么調度器類方法會在task_struct中設定TIF_NEED_RESCHED標志,以表示該請求,而內核會在接下來的適當時機完成該請求,

(2)主調度器

將當前cpu分配給另一個行程需要呼叫主調度器函式(schedule),從該系統呼叫回傳后也要檢查當前行程是否設定了重調度標志TIF_NEED_RESCHEDULE,如果有,內核會呼叫schedule,

__sched前綴的用處:有該前綴的函式,都是可能呼叫schedule的函式,包括schedule自身,該前綴目的在于將相關代碼的函式編譯后,放到目標檔案的特定段中,.sched.text中,該資訊使內核在現實堆疊轉儲或類似資訊時,忽略所有與調度有關的呼叫,由于調度器函式呼叫不是普通代碼流程的部分,因此這種情況下是無意義的,

asmlinkage void __sched schedule( void );該函式的程序:

首先確定當前就緒佇列,并在prev中保存一個指向(當前)活動行程的task_struct的指標,
更新就緒佇列的時鐘,清除當前行程task_struct的重調度標志TIF_NEED_RESCHED,
判斷當前行程是否在可中斷睡眠狀態,而且現在接收到信號,那么它將再次提升為可運行,否則,用deactivate_task講行程停止,
用put_prev_task通知調度類,當前行程要被另一行程代替,pick_next_task,選擇下一個要執行的行程,
只有1個行程,是不要切換的,還讓它留在cpu,如果已經選擇了一個新行程,就用context_switch進行背景關系切換,
當前行程,被重新調度回來時,檢測是否要重新調度,如果要,就又重復前面(1)至(5)的步驟了,
(3)與fork的互動

用fork或其變體新建行程時,調度器有機會用sched_fork函式掛鉤到該行程,

單處理器中,sched_fork執行如下:

  • 初始化新行程與調度相關的欄位,
  • 建立資料結構(相當簡單直接),
  • 確定行程的動態優先級,

通過使用父行程的普通優先級作為子行程的動態優先級,內核確保父行程優先級的臨時提高不會被子行程繼承,在用wake_up_new_task喚醒行程時,內核呼叫調度類的task_new將新行程加入相應類的就緒佇列中,

(4)背景關系切換

內核選擇新行程之后,必須處理與多任務相關的技術細節,這些細節總稱為背景關系切換(context switching),

  • context_switch是個分配器,它會呼叫所需的特定于體系結構的方法,主要進行如下操作:
  • prepare_task_switch,執行特定于體系結構的代碼,為切換做準備,
  • switch_mm更換task_struct->mm描述的記憶體管理背景關系,
  • switch_to切換處理器暫存器和內核堆疊(虛擬地址空間的用戶部分在第一步已經變更,其中也包括了用戶狀態下的堆疊,因此用戶堆疊就不需要顯式變更了),
  • 切換前,用戶空間行程的暫存器進入和心態時保存在內核堆疊上,在背景關系切換時,內核堆疊的值自動回復暫存器資料,再回傳用戶空間,

內核執行緒沒有自身的用戶空間記憶體背景關系,可能在某個隨機行程地址空間的上部執行,其task_struct->mm為NULL,從當前行程“借來”的地址空間記錄在active_mm中,

此外,由于背景關系切換的速度對系統性能的影響舉足輕重,所以內核使用了一種技巧來減少所需的CPU時間,浮點暫存器(及其他內核未使用的擴充暫存器,例如IA-32平臺上的SSE2暫存器)除非有應用程式實際使用,否則不會保存,此外,除非有應用程式需要,否則這些暫存器也不會恢復,這稱之為惰性FPU技術,由于使用了匯編語言代碼,因此其實作依平臺而有所不同,但基本原理總是同樣的,

小編推薦自己的Linux、C/C++技術交流群:【960994558】整理了一些個人覺得比較好的學習書籍、視頻資料共享在里面(包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等等.),有需要的可以自行添加哦!~

在這里插入圖片描述

二、完全公平調度類

1、資料結構
CFS的就緒佇列cfs_rq:

struct cfs_rq {
    struct load_weight load;        //維護了所有這些行程的累積負荷值
    unsigned long nr_running;    //計算了佇列上可運行行程的數目
    u64 min_vruntime;        //跟蹤記錄佇列上所有行程的最小虛擬運行時間
    struct rb_root tasks_timeline;        //用于在按時間排序的紅黑樹中管理所有行程
    struct rb_node *rb_leftmost;        //總是設定為指向樹最左邊的結點,即最需要被調度的行程
    struct sched_entity *curr;    //指向當前執行行程的可調度物體
}

2、CFS操作
(1)虛擬時鐘

完全公平調度演算法依賴于虛擬時鐘,用以度量等待行程在完全公平系統中所能得到的CPU時間,資料結構中,可以根據現存的實際時鐘與每個行程相關的負荷權重推算出來,所有與虛擬時鐘有關的計算都在update_curr中執行,該函式在系統中各個不同地方呼叫,包括周期性調度器之內,
在這里插入圖片描述
首先,該函式確定就緒佇列的當前執行行程,并獲取主調度器就緒佇列的實際時鐘值(每個調度周期都會更新),如果就緒佇列上當前沒有行程正在執行,則無事可做,否則,內核會計算當前和上一次更新負荷統計量時兩次的時間差,并將其余的作業委托給__update_curr,

然后,__update_curr需要更新當前行程在CPU上執行花費的物理時間和虛擬時間,物理時間的更新只要將時間差加到先前統計的時間即可;對于虛擬時間,對于運行在nice級別0的行程來說,定義虛擬時間和物理時間是相等的,nice值不為0時,必須根據行程的負荷權重重新衡定時間,

最后,內核需要設定min_vruntime(min_vruntime是單調遞增的),

CFS調度器真正關鍵點:紅黑樹根據鍵值進行排序(se->vruntime -cfs_rq->min_vruntime),鍵值較小的結點,排序位置就更靠左(被更快調度),由此,內核實作了以下兩種對立機制:

  • 在行程運行時,其vruntime穩定地增加,它在紅黑樹中總是向右移動的,
  • 如果行程進入睡眠,則其vruntime保持不變(行程再被喚醒后,在紅黑樹的位置會更靠左),

(2)延遲跟蹤

良好的調度延遲是 保證每個可運行的行程都應該至少運行一次的某個時間間隔(它在sysctl_sched_latency給出),一個延遲周期中處理的最大活動數目為sched_nr_latency,超出該上限,則延遲周期也成比例線性擴展,

通過考慮各個行程的相對權重,將一個延遲周期的時間在活動行程之間進行分配,

3、佇列操作
enqueue_task_fair和dequeue_task_fair分別用來增刪就緒佇列的成員,圖5為enqueue_task_fair代碼流程圖,
在這里插入圖片描述
如果通過struct sched_entity的on_rq成員判斷行程已經在就緒佇列上,則無事可做,否則,具體的作業委托給enqueue_entity,

進入enqueue_entity后,首先用updater_curr更新統計量,然后若行程此前在睡眠,那么在place_entity中首先會調整行程的虛擬運行時間;如果行程最近在運行,其虛擬運行時間仍然有效,那么(除非它當前在執行中)它可以直接用__enqueue_entity加入紅黑樹中,

4、選擇下一個行程
選擇下一個將要運行的行程由pick_next_task_fair執行,pick_next_task_fair的代碼流程圖如圖6所示,
在這里插入圖片描述
如果nr_running計數器為0,即當前佇列上沒有可運行行程,則無事可做,函式可以立即回傳,否則將具體作業委托給pick_next_entity,

如果樹中最左邊的行程可用,可以使用輔助函式first_fair立即確定,然后用__pick_next_entity從紅黑樹中提取出sched_entity實體,

完成了選擇作業之后,通過set_next_entity函式將該行程標記為運行行程,當前執行行程不保存在就緒佇列上,因此使用__dequeue_entity將其從樹中移除,如果當前行程是最左邊的結點,則將leftmost指標設定到下一個最左邊的行程,

5、處理周期性調度器
在處理周期調度時,差值sum_exec_runtime - prev_sum_exec_runtime(表示行程在CPU上執行所花時間)很重要,這個差值形式上由函式task_tick_fair負責,但實際作業由entity_tick完成,
在這里插入圖片描述
首先,使用update_curr更新統計量,

然后判斷nr_running計數器表明佇列上可運行的行程數,如果少于兩個,則無事可做;否則由由check_preempt_tick作出決策(確保沒有哪個行程能夠比延遲周期中確定的份額運行得更長),如果行程運行時間比期望的時間間隔長,那么通過resched_task發出重調度請求,這會在task_struct中設定TIF_NEED_RESCHED標志,核心調度器會在下一個適當時機發起重調度,

6、喚醒搶占
當在try_to_wake_up和wake_up_new_task中喚醒行程時,內核使用check_preempt_curr看看是否新行程可以搶占當前運行的行程(該程序不涉及核心調度器),

新喚醒的行程不必一定由完全公平調度器處理,如果新行程是一個實時行程,則會立即請求重調度,因為實時行程總是會搶占CFS行程,

當運行行程被新行程搶占時,內核確保被搶占者至少已經運行了某一最小時間限額(sysctl_sched_wakeup_granularity),如果新行程的虛擬運行時間,加上最小時間限額,仍然小于當前執行行程的虛擬運行時間(由其調度物體se表示),則請求重調度,

7、處理新行程
CFS在創建新行程時呼叫的掛鉤函式:task_new_fair,該函式的行為可用sysctl_sched_child_runs_first控制,用于判斷新建子行程是否需要在父行程之前運行,如果父行程的虛擬運行時間(由curr表示)小于子行程的虛擬運行時間,則意味著父行程將在子行程之前調度運行,如果子行程應該在父行程之前運行,則二者的虛擬運算時間需要換過來,然后子行程按常規加入就緒佇列,并請求重調度,

三、實時調度類

1、性質
按照POSIX標準的要求,除了“普通”行程之外,Linux還支持兩種實時調度類,調度器結構使得實時行程可以平滑地集成到內核中,而無需修改核心調度器,

實時行程與普通行程有一個根本的不同之處:如果系統中有一個實時行程且可運行,那么調度器總是會選中它運行,除非有另一個優先級更高的實時行程,

現有的兩種實時類:

  • 回圈行程(SCHED_RR)有時間片,其值在行程運行時會減少,就像是普通行程,在所有的時間段都到期后,則該值重置為初始值,而行程則置于佇列的末尾,
  • 先進先出行程(SCHED_FIFO)沒有時間片,在被調度器選擇執行后,可以運行任意長時間,

2、資料結構
實時行程的調度類定義:

const struct sched_class rt_sched_class = {
    .next = &fair_sched_class,
    .enqueue_task = enqueue_task_rt,
    .dequeue_task = dequeue_task_rt,
    .yield_task = yield_task_rt,
    .check_preempt_curr = check_preempt_curr_rt,
    .pick_next_task = pick_next_task_rt,
    .put_prev_task = put_prev_task_rt,
    .set_curr_task = set_curr_task_rt,
    .task_tick = task_tick_rt,
};

實時調度器類的實作比完全公平調度器簡單(核心調度器的就緒佇列也包含了用于實時行程的子就緒佇列,是一個嵌入的struct rt_rq實體)

圖8是時調度類就緒佇列示意圖,一個鏈表中,表頭為active.queue[prio],而active.bitmap位圖中的每個位元位對應于一個鏈表,凡包含了行程的鏈表,對應的位元位則置位,如果鏈表中沒有行程,則對應的位元位不置位,

在這里插入圖片描述
sched_find_first_bit是一個標準函式,可以找到active.bitmap中第一個置位的位元位(高優先級),取出所選鏈表的第一個行程,并將se.exec_start設定為就緒佇列的當前實際時鐘值,

對于周期調度:SCHED_FIFO行程可以運行任意長的時間,而且必須使用yield系統呼叫將控制權顯式傳遞給另一個行程,對回圈行程(SCHED_RR),則減少其時間片,在尚未超出時間段時,行程可以繼續執行,計數器歸0后,其值重置為DEF_TIMESLICE,即100 * HZ / 1000( 100毫秒),如果該行程不是鏈表中唯一的行程,則重新排隊到末尾,通過用set_tsk_need_resched設定TIF_NEED_RESCHED標志,照常請求重調度,

為將行程轉換為實時行程,必須使用sched_setscheduler系統呼叫,該系統呼叫完成了以下幾個任務:

  • 使用deactivate_task將行程從當前佇列移除,
  • 在task_struct中設定實時優先級和調度類,
  • 重新激活行程,

只有具有root權限(或等價于CAP_SYS_NICE)的行程執行了sched_setscheduler系統呼叫,才能修改調度器類或優先級,否則,調度類只能從SCHED_NORMAL改為SCHED_BATCH,只有目標行程的UID或EUID與呼叫者行程的EUID相同時,才能修改目標行程的優先級,此外,優先級只能降低,不能提升,

四、調度器增強

1、SMP調度
對于多處理器系統,CPU負荷必須盡可能公平地在所有的處理器上共享;行程與系統中某些處理器的親合性(affinity)必須是可設定的;內核必須能夠將行程從一個CPU遷移到另一個,

行程對特定CPU 的親合性, 定義在task_struct 的cpus_allowed成員中,可以通過sched_setaffinity系統呼叫修改行程與CPU的現有分配關系,

(1)資料結構的擴展

每當內核認為有必要重新均衡時,核心調度器就會呼叫load_balance和move_one_task函式,特定于調度器類的函式建立一個迭代器,使核心調度器能遍歷所有可能遷移到另一個佇列的備選行程,load_balance函式指標采用了一般性的函式load_balance,允許從最忙的就緒佇列分配多個行程到當前CPU,但移動的負荷不能比max_load_move更多;move_one_task使用了iter_move_one_task,從最忙碌的就緒佇列移出一個行程,遷移到當前CPU的就緒佇列,

負載均衡發起程序:在SMP系統上,周期性調度器函式scheduler_tick按上文所述完成所有系統都需要的任務之后,會呼叫trigger_load_balance函式,這會引發SCHEDULE_SOFTIRQ軟中斷softIRQ(確保會在適當的時機執行run_rebalance_domains),該函式最終對當前CPU呼叫rebalance_domains,實作負載均衡,

就緒佇列是特定于CPU的,內核為每個就緒佇列提供了一個遷移執行緒,可以接收遷移請求,這些請求保存在鏈表migration_queue中,這樣的請求通常發源于調度器自身,但如果行程被限制在某一特定的CPU集合上,而不能在當前執行的CPU上繼續運行時,也可能出現這樣的請求,內核試圖周期性地均衡就緒佇列,但如果對某個就緒佇列效果不佳,則必須使用主動均衡(active balancing),

所有的就緒佇列組織為調度域(scheduling domain),這可以將物理上鄰近或共享高速快取的CPU群集起來,應優先選擇在這些CPU之間遷移行程,

對于load_balance函式,它會檢測在上一次重新均衡操作之后是否已經過去了足夠多的時間,在必要的情況下,它會發起一輪新的均衡操作,首先該函式通過find_busiest_queue標識出哪個佇列作業量最大,如果至少有一個行程在該佇列上執行,則使用move_tasks將該佇列中適當數目的行程遷移到當前佇列,move_tasks函式接下來會呼叫特定于調度器類的load_balance方法,如果均衡操作失敗,那么將喚醒負責最忙的就緒佇列的遷移執行緒,

(2)遷移執行緒

遷移執行緒是一個執行migration_thread的內核執行緒(如圖10所示),用于兩個目的:

  • 完成發自調度器的遷移請求;
  • 實作主動均衡,
    在這里插入圖片描述
    migration_thread內部是一個無限回圈,在無事可做時進入睡眠狀態,

首先,該函式檢測是否需要主動均衡,如果需要,則呼叫active_load_balance滿足該請求,該函式試圖從當前就緒佇列移出一個行程,且移至發起主動均衡請求CPU的就緒佇列,它使用move_one_task完成該作業,后者又對所有的調度器類,分別呼叫特定于調度器類的move_one_task函式,直至其中一個成功,

完成主動負載均衡之后,遷移執行緒會檢測migrate_req鏈表中是否有來自調度器的待決遷移請求,如果沒有,則執行緒發出重調度請求,否則,用__migrate_task完成相關請求,該函式會直接移出所要求的行程,而不再與調度器類進一步互動,

(3)核心調度器的改變

SMP系統與單處理器系統相比的主要差別:

  • 在用exec系統呼叫啟動一個新行程時,由于行程尚未執行,這時是調度器跨越CPU移動該行程的一個良好的時機,
  • 完全公平調度器的調度粒度與CPU的數目是成比例的,系統中處理器越多,可以采用的調度粒度就越大,

2、調度域和控制組
對于組調度,行程置于不同的組中,調度器首先在這些組之間保證公平,然后在組中的所有行程之間保證公平(比如系統可以向每個用戶授予相同的CPU時間份額),

把行程按用戶分組不是唯一可能的做法,內核還提供了控制組(control group),該特性使得通過特殊檔案系統cgroups可以創建任意的行程集合,甚至可以分為多個層次,

3、內核搶占和低延遲相關作業
(1)內核搶占

在系統呼叫時回傳用戶狀態之前,或者是內核中某些指定的點上,都會呼叫調度器,這確保除了一些明確指定的情況之外,內核是無法中斷的,這不同于用戶行程,如果內核處于相對耗時較長的操作中,這種行為可能會帶來問題,啟用了搶占特性的內核能夠比普通內核更快速地用緊急行程替代當前行程,

在編譯內核時啟用對內核搶占的支持,如果高優先級行程有事情需要完成,那么在啟用內核搶占(與用戶空間程式被其他行程搶占不同)的情況下,不僅用戶空間應用程式可以被中斷,內核也可以被中斷,

為了避免競態條件使系統不一致,內核不能在任意點上被中斷,大多數不能中斷的點已被SMP實作標識,并且實作內核搶占時可以重用這些資訊,內核的某些易于出現問題(臨界區)的部分每次只能由一個處理器訪問,這些部分使用自旋鎖保護,每次內核進入臨界區時,我們必須停用內核搶占,

系統中的每個行程都有一個特定于體系結構的struct thread_info實體,該結構包含了一個搶占計數器,

struct thread_info {
 ...
     int preempt_count; /* 0 => 可搶占, <0 => BUG */
 ...
 }

preempt_count的值(該值通過輔助函式dec_preempt_count和inc_preempt_count分別進行減1和加1操作)確定了內核當前是否處于一個可被中斷的位置,在內核再次啟用搶占之前,必須確認已經離開所有的臨界區,

搶占機制中主要的函式是preempt_schedule,設定了TIF_NEED_RESCHED標志,它不能保證一定可以搶占內核(內核有可能正處于臨界區中),可以通過preempt_reschedule檢查是否可搶占,

激活搶占的兩種方法(本質區別在于,preempt_schedule_irq呼叫時停用了中斷,防止中斷造成遞回呼叫):

  • 使用preempt_schedule,如果調度是由搶占機制發起的(查看搶占計數器中是否設定了PREEMPT_ACTIVE),無需停止當前行程的活動(跳過使用deactivate_task停止不處于可運行狀態行程的活動),盡可能快速選擇下一個行程,
  • 是通過preempt_schedule_irq,處理中斷請求后回傳和心態,會檢查搶占技術企的值和是否設定了重調度標志,若都滿足,則呼叫調度器,

(2)低延遲

內核中耗時長的操作(比如繁重的IO操作)不應該完全占據整個系統,相反,它們應該不時地檢測是否有另一個行程變為可運行,并在必要的情況下呼叫調度器選擇相應的行程運行,該機制不依賴于內核搶占,即使內核聯編時未指定支持搶占,也能夠降低延遲,發起有條件重調度的函式是cond_resched,內核代碼中,長時間運行的函式都在適當之處插入了對cond_resched的呼叫,保證較高的相應速度,
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/233909.html

標籤:其他

上一篇:Spring_Mybatis整合 注解配置類與xml組態檔兩種方式分析及初始化IOC容器與監聽獲取取IOC容器

下一篇:淺談汽車軟體Boot的五種自重繪方式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more