Read the fucking source code!
--By 魯迅A picture is worth a thousand words.
--By 高爾基說明:算法
從這篇文章開始,將開始Linux調度器的系列研究了。
本文也會從一些基礎的概念及數據結構入手,先打造一個粗略的輪廓,後續的文章將逐漸深刻。緩存
struct task_struct
結構來進行抽象描述。Linux內核使用task_struct
結構來抽象,該結構包含了進程的各種信息及所擁有的資源,好比進程的狀態、打開的文件、地址空間信息、信號資源等等。task_struct
結構很複雜,下邊只針對與調度相關的某些字段進行介紹。數據結構
struct task_struct { /* ... */ /* 進程狀態 */ volatile long state; /* 調度優先級相關,策略相關 */ int prio; int static_prio; int normal_prio; unsigned int rt_priority; unsigned int policy; /* 調度類,調度實體相關,任務組相關等 */ const struct sched_class *sched_class; struct sched_entity se; struct sched_rt_entity rt; #ifdef CONFIG_CGROUP_SCHED struct task_group *sched_task_group; #endif struct sched_dl_entity dl; /* 進程之間的關係相關 */ /* Real parent process: */ struct task_struct __rcu *real_parent; /* Recipient of SIGCHLD, wait4() reports: */ struct task_struct __rcu *parent; /* * Children/sibling form the list of natural children: */ struct list_head children; struct list_head sibling; struct task_struct *group_leader; /* ... */ }
就緒態
和運行態
對應的都是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)
內核默認提供了5個調度器,Linux內核使用struct sched_class
來對調度器進行抽象:工具
Stop調度器, stop_sched_class
:優先級最高的調度類,能夠搶佔其餘全部進程,不能被其餘進程搶佔;Deadline調度器, dl_sched_class
:使用紅黑樹,把進程按照絕對截止期限進行排序,選擇最小進程進行調度運行;RT調度器, rt_sched_class
:實時調度器,爲每一個優先級維護一個隊列;CFS調度器, cfs_sched_class
:徹底公平調度器,採用徹底公平調度算法,引入虛擬運行時間概念;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調度器
來調度運行;Linux內核使用struct rq
結構來描述運行隊列,關鍵字段以下:ui
/* * 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
後,調度器的調度對象不單單是進程了,Linux內核抽象出了sched_entity/sched_rt_entity/sched_dl_entity
描述調度實體,調度實體能夠是進程或task_group
;struct task_group
來描述任務組,任務組在每一個CPU上都會維護一個CFS調度實體、CFS運行隊列,RT調度實體,RT運行隊列
;Linux內核使用struct task_group
來描述任務組,關鍵的字段以下:this
/* 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()
函數;_TIF_NEED_RESCHED
);schedule()
調度;hrtick()
wake_up_process()
wake_up_process()
函數,被喚醒的進程可能搶佔當前的進程;上述講到的幾個函數都是經常使用於調度時調用。此外,在建立新進程時,或是在內核搶佔時,也會出現一些調度點。線程
本文只是粗略的介紹了一個大概,後續將針對某些模塊進行更加深刻的分析,敬請期待。