Linux進程調度 - 1

Table of Contents

runqueue 運行隊列

task_group 任務分組

調度程序

主動調度 - schedule()

週期調度 - schedule_tick()

高精度時鐘調度 - hrtick()

進程喚醒時調度 - wake_up_process()


 

 

  • 進程是資源分配的最小單位,而線程是CPU調度的的最小單位。
  • 進程不僅包括可執行程序的代碼段,還包括一系列的資源,比如:打開的文件、內存、CPU時間、信號量、多個執行線程流等等。而線程可以共享進程內的資源空間。
  • 在Linux內核中,進程和線程都使用struct task_struct結構來進行抽象描述。

  • 進程的虛擬地址空間分爲用戶虛擬地址空間和內核虛擬地址空間,所有進程共享內核虛擬地址空間,沒有用戶虛擬地址空間的進程稱爲內核線程。

Linux內核使用task_struct結構來抽象,該結構包含了進程的各類信息及所擁有的資源,比如進程的狀態、打開的文件、地址空間信息、信號資源等等。

Linux中的就緒態運行態對應的都是TASK_RUNNING標誌位,就緒態表示進程正處在隊列中,尚未被調度;運行態則表示進程正在CPU上運行;

內核中主要的狀態字段定義如下

/* Used in tsk->state: */
#define TASK_RUNNING			0x0000
#define TASK_INTERRUPTIBLE		0x0001
#define TASK_UNINTERRUPTIBLE		0x0002

/* Used in tsk->exit_state: */
#define EXIT_DEAD			0x0010
#define EXIT_ZOMBIE			0x0020
#define EXIT_TRACE			(EXIT_ZOMBIE | EXIT_DEAD)

/* Used in tsk->state again: */
#define TASK_PARKED			0x0040
#define TASK_DEAD			0x0080
#define TASK_WAKEKILL			0x0100
#define TASK_WAKING			0x0200
#define TASK_NOLOAD			0x0400
#define TASK_NEW			0x0800
#define TASK_STATE_MAX			0x1000

/* Convenience macros for the sake of set_current_state: */
#define TASK_KILLABLE			(TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_STOPPED			(TASK_WAKEKILL | __TASK_STOPPED)
#define TASK_TRACED			(TASK_WAKEKILL | __TASK_TRACED)

#define TASK_IDLE			(TASK_UNINTERRUPTIBLE | TASK_NOLOAD)

  • 所謂調度,就是按照某種調度的算法,從進程的就緒隊列中選取進程分配CPU,主要是協調對CPU等的資源使用。進程調度的目標是最大限度利用CPU時間。

內核默認提供了5個調度器,Linux內核使用struct sched_class來對調度器進行抽象:

  1. Stop調度器, stop_sched_class:優先級最高的調度類,可以搶佔其他所有進程,不能被其他進程搶佔;

  2. Deadline調度器, dl_sched_class:使用紅黑樹,把進程按照絕對截止期限進行排序,選擇最小進程進行調度運行;

  3. RT調度器, rt_sched_class:實時調度器,爲每個優先級維護一個隊列;

  4. CFS調度器, cfs_sched_class:完全公平調度器,採用完全公平調度算法,引入虛擬運行時間概念;

  5. IDLE-Task調度器, idle_sched_class:空閒調度器,每個CPU都會有一個idle線程,當沒有其他進程可以調度時,調度運行idle線程;

Linux內核提供了一些調度策略供用戶程序來選擇調度器,其中Stop調度器IDLE-Task調度器,僅由內核使用,用戶無法進行選擇:

  • SCHED_DEADLINE:限期進程調度策略,使task選擇Deadline調度器來調度運行;

  • SCHED_RR:實時進程調度策略,時間片輪轉,進程用完時間片後加入優先級對應運行隊列的尾部,把CPU讓給同優先級的其他進程;

  • SCHED_FIFO:實時進程調度策略,先進先出調度沒有時間片,沒有更高優先級的情況下,只能等待主動讓出CPU;

  • SCHED_NORMAL:普通進程調度策略,使task選擇CFS調度器來調度運行;

  • SCHED_BATCH:普通進程調度策略,批量處理,使task選擇CFS調度器來調度運行;

  • SCHED_IDLE:普通進程調度策略,使task以最低優先級選擇CFS調度器來調度運行;

runqueue 運行隊列

  • 每個CPU都有一個運行隊列,每個調度器都作用於運行隊列;

  • 分配給CPU的task,作爲調度實體加入到運行隊列中;

  • task首次運行時,如果可能,儘量將它加入到父task所在的運行隊列中(分配給相同的CPU,緩存affinity會更高,性能會有改善);

Linux內核使用struct rq結構來描述運行隊列,關鍵字段如下:

/*
 * This is the main, per-CPU runqueue data structure.
 *
 * Locking rule: those places that want to lock multiple runqueues
 * (such as the load balancing or the thread migration code), lock
 * acquire operations must be ordered by ascending &runqueue.
 */
struct rq {
	/* runqueue lock: */
	raw_spinlock_t lock;

	/*
	 * nr_running and cpu_load should be in the same cacheline because
	 * remote CPUs use both these fields when doing load calculation.
	 */
	unsigned int nr_running;
    
    /* 三個調度隊列:CFS調度,RT調度,DL調度 */
	struct cfs_rq cfs;
	struct rt_rq rt;
	struct dl_rq dl;

    /* stop指向遷移內核線程, idle指向空閒內核線程 */
    struct task_struct *curr, *idle, *stop;
    
    /* ... */
}

task_group 任務分組

  • 利用任務分組的機制,可以設置或限制任務組對CPU的利用率,比如將某些任務限制在某個區間內,從而不去影響其他任務的執行效率;

  • 引入task_group後,調度器的調度對象不僅僅是進程了,Linux內核抽象出了sched_entity/sched_rt_entity/sched_dl_entity 描述調度實體,調度實體可以是進程或task_group

  • 使用數據結構struct task_group來描述任務組,任務組在每個CPU上都會維護一個CFS調度實體、CFS運行隊列,RT調度實體,RT運行隊列

Linux內核使用struct task_group來描述任務組,關鍵的字段如下:

/* task group related information */
struct task_group {
    /* ... */

    /* 爲每個CPU都分配一個CFS調度實體和CFS運行隊列 */
#ifdef CONFIG_FAIR_GROUP_SCHED
  /* schedulable entities of this group on each cpu */
  struct sched_entity **se;
  /* runqueue "owned" by this group on each cpu */
  struct cfs_rq **cfs_rq;
  unsigned long shares;
#endif

    /* 爲每個CPU都分配一個RT調度實體和RT運行隊列 */
#ifdef CONFIG_RT_GROUP_SCHED
  struct sched_rt_entity **rt_se;
  struct rt_rq **rt_rq;

  struct rt_bandwidth rt_bandwidth;
#endif

    /* task_group之間的組織關係 */
  struct rcu_head rcu;
  struct list_head list;

  struct task_group *parent;
  struct list_head siblings;
  struct list_head children;

    /* ... */
};

調度程序

調度程序依靠幾個函數來完成調度工作的,下邊將介紹幾個關鍵的函數。

主動調度 - schedule()

schedule()函數,是進程調度的核心函數,大體的流程如上圖所示。

核心的邏輯:選擇另外一個進程來替換掉當前運行的進程。進程的選擇是通過進程所使用的調度器中的pick_next_task函數來實現的,不同的調度器實現的方法不一樣;進程的替換是通過context_switch()來完成切換的,具體的細節後續的文章再深入分析。

週期調度 - schedule_tick()

  • 時鐘中斷處理程序中,調用schedule_tick()函數;
  • 時鐘中斷是調度器的脈搏,內核依靠週期性的時鐘來處理器CPU的控制權;
  • 時鐘中斷處理程序,檢查當前進程的執行時間是否超額,如果超額則設置重新調度標誌(_TIF_NEED_RESCHED);
  • 時鐘中斷處理函數返回時,被中斷的進程如果在用戶模式下運行,需要檢查是否有重新調度標誌,設置了則調用schedule()調度;

高精度時鐘調度 - hrtick()

  • 時鐘中斷處理程序中,調用schedule_tick()函數;

  • 時鐘中斷是調度器的脈搏,內核依靠週期性的時鐘來處理器CPU的控制權;

  • 時鐘中斷處理程序,檢查當前進程的執行時間是否超額,如果超額則設置重新調度標誌(_TIF_NEED_RESCHED);

  • 時鐘中斷處理函數返回時,被中斷的進程如果在用戶模式下運行,需要檢查是否有重新調度標誌,設置了則調用schedule()調度;

  • 高精度時鐘調度,與週期性調度類似,不同點在於週期調度的精度爲ms級別,而高精度調度的精度爲ns級別;

  • 高精度時鐘調度,需要有對應的硬件支持;

進程喚醒時調度 - wake_up_process()

  • 喚醒進程時調用wake_up_process()函數,被喚醒的進程可能搶佔當前的進程;

上述講到的幾個函數都是常用於調度時調用。此外,在創建新進程時,或是在內核搶佔時,也會出現一些調度點。

本文只是粗略的介紹了一個大概,後續將針對某些模塊進行更加深入的分析,敬請期待。

轉載授權

 

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