Linux進程調度器-基礎

2021-03-02 嵌入式Linux

背景

Read the fucking source code!  --By 魯迅A picture is worth a thousand words. --By 高爾基

說明:

使用工具:Source Insight 3.5, Visio1. 概述

從這篇文章開始,將開始Linux調度器的系列研究了。本文也會從一些基礎的概念及數據結構入手,先打造一個粗略的輪廓,後續的文章將逐漸深入。

2. 概念2.1 進程


從教科書上,我們都能知道:進程是資源分配的最小單位,而線程是CPU調度的的最小單位。進程不僅包括可執行程序的代碼段,還包括一系列的資源,比如:打開的文件、內存、CPU時間、信號量、多個執行線程流等等。而線程可以共享進程內的資源空間。在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; struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
struct list_head children; struct list_head sibling; struct task_struct *group_leader; }

2.2 進程狀態


上圖中左側為作業系統中通俗的進程三狀態模型,右側為Linux對應的進程狀態切換。每一個標誌描述了進程的當前狀態,這些狀態都是互斥的;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)

2.3 scheduler 調度器


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

內核默認提供了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調度器來調度運行;2.4 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; /* ... */}

2.5 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來描述任務組,關鍵的欄位如下:

struct task_group {    
#ifdef CONFIG_FAIR_GROUP_SCHED struct sched_entity **se; struct cfs_rq **cfs_rq; unsigned long shares;#endif
#ifdef CONFIG_RT_GROUP_SCHED struct sched_rt_entity **rt_se; struct rt_rq **rt_rq;
struct rt_bandwidth rt_bandwidth;#endif
struct rcu_head rcu; struct list_head list;
struct task_group *parent; struct list_head siblings; struct list_head children;
};

3. 調度程序

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

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

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

高精度時鐘調度,與周期性調度類似,不同點在於周期調度的精度為ms級別,而高精度調度的精度為ns級別;進程喚醒時調度 - wake_up_process()

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

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

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



相關焦點

  • 能感知功耗的Linux調度器(EAS)
    在解釋之前,我們需要討論Linux調度器。Linux調度器的演變輪轉調度輪轉(round robin)是一個容易解釋也容易理解的概念,而且也不難理解其缺點。輪轉使用時間片為每個進程分配時間。假設我們的計算機上正在運行四個進程。現在,讓我們描述輪轉調度器的工作。在繼續進行下一步之前,我們將為每個進程分配100毫秒(時間片)。
  • 設置Linux進程的睡眠和喚醒
    一旦一個運行中的進程時間片用完, Linux 內核的調度器會剝奪這個進程對CPU的控制權,並且從運行隊列中選擇一個合適的進程投入運行。 當然,一個進程也可以主動釋放CPU的控制權。函數 schedule()是一個調度函數,它可以被一個進程主動調用,從而調度其它進程佔用CPU。
  • 關於 Linux 進程的睡眠和喚醒 ,來看這篇就夠了~
    一旦一個運行中的進程時間片用完, Linux 內核的調度器會剝奪這個進程對 CPU 的控制權,並且從運行隊列中選擇一個合適的進程投入運行。當然,一個進程也可以主動釋放 CPU 的控制權。函數 schedule() 是一個調度函數,它可以被一個進程主動調用,從而調度其它進程佔用 CPU。
  • 嵌入式Linux開發必懂:基於ARM64的init用戶進程究竟如何啟動?
    [導讀] 前面的文章有提到linux啟動的第一個進程為init,那麼該進程究竟是如何從內核啟動入口一步一步運行起來的,而該進程又有些什麼作用呢?做嵌入式Linux開發,有必要對這些概念了解清楚。本文基於ARM體系的內核啟動做出解析。
  • 進程調度案例分析與常見疑惑2:為啥fork之後有兩個返回值?
    微信公眾號:奔跑吧linux社區本文節選自《奔跑吧linux
  • Linux 概念架構的理解
    當一個進程需要等待一個硬體動作完成 時,相應子系統會阻塞這個進程;當這個硬體動作完成時,子系統會將這個進程恢復:這個阻塞和恢復動作都要依賴於進程調度器完成。 上圖中的每一個依賴箭頭都有原因: 進程調度器依賴內存管理器(Memory manager):進程恢復執行時,需要依靠內存管理器分配供它運行的內存。
  • Linux怎樣查詢出當前系統的所有進程
    本經驗已linux發行版Ubuntu為例,Linux下使用PS命令結合相關參數可以查看linux當前系統下的所有進程、所有運行中的進程、所有非root運行的進程、所有指定用戶運行的進程。使用搜索功能搜索「Terminal」,打開Ubuntu命令行終端。
  • Linux內核獲取當前進程結構的current宏
    的幾個關鍵變量,最後提到了在內核裡獲取當前進程的pid的代碼:current->pid。current,實際獲得的是當前進程結構的指針,current->pid ,自然就是當前進程號。pid是task_struct的一個成員變量,類型為pid_t,與系統調用getpid()函數的返回值一致。
  • Linux進程管理命令:nohup、&、jobs、fg、bg、ps、kill
    &3. jobs4. ps5. kill對Linux進程的管理是我們經常遇到的,如何查看一個進程的狀態?如何把一個後臺的進程調至進程執行?如何殺死一個進程.看了本文後,你將會全部掌握!jobs jobs命令用於查看正在執行的後臺進程,但只能看當前終端生效的進程,如果關閉當前終端後,在另一個終端下,jobs已經無法看到後臺跑得程序了,此時利用ps(進程查看命令)。
  • 改善Linux內核實時性方法的研究與實現
    上半部分立即執行,下半部分將喚醒相應的和中斷處理相關的進程稍後執行。雖然這種機制使得中斷處理變得更加高效和易於維護,但是對於系統如果有嚴重的網絡負載或其他I/O負載時,中斷將非常頻繁,內核當前的實時任務會被不停中斷,這對於Linux的實時應用來說是不可接受的。  另外,Linux為了使內核同步而採用了關中斷,在內核的關中斷區域,中斷是被屏蔽的。
  • linux內核啟動流程
    本文以linux-2.6.37版源碼為例分三個階段來描述內核啟動全過程。start_kernel是所有Linux平臺進入系統內核初始化後的入口函數,它主要完成剩餘的與 硬體平臺相關的初始化工作,在進行一系列與內核相關的初始化後,調用第一個用戶進程- init 進程並等待用戶進程的執行,這樣整個 Linux內核便啟動完畢。該函數位於init/main.c文件中,主要工作流程如圖3所示:
  • 嵌入式Linux內核啟動主要分為這三個階段
    start_kernel是所有Linux平臺進入系統內核初始化後的入口函數,它主要完成剩餘的與 硬體平臺相關的初始化工作,在進行一系列與內核相關的初始化後,調用第一個用戶進程- init 進程並等待用戶進程的執行,這樣整個 Linux內核便啟動完畢。該函數位於init/main.c文件中,主要工作流程如圖3所示:
  • linux下進程和線程狀態查看
    Lf pid 查看對應進程下的線程數.ps -eLf |grep pid|grep -v greppstree -p `ps -aux | grep server | awk '{print $2}'` | wc -l查看線程其實linux
  • 【乾貨】網絡虛擬化—— linux虛擬網絡基礎
    在linux裡面devic(設備)與傳統網絡概念裡的物理設備(如交換機、路由器)不同,Linux所說的設備,其背後指的是一個類似於數據結構、內核模塊或設備驅動這樣的含義。就是說device可能只是軟體系統裡的一個驅動,一個函數接口。Tap位於二層數據鏈路層,tun位於三層網絡層,兩者在linux裡的函數結構幾乎一致,除了一個flag值區分tap/tun。
  • Linux系統從入門到放棄?
    Linux是一個命令行組成的作業系統,精髓在命令行,學習如何在Linux環境中執行linux命令,包括有關文件、目錄、文件系統、進程等概念,如何使用相應的命令對文件、目錄、進程等進行管理,了解遇到問題時,如何找到幫助信息等等。都將是我們學習入門Linux的第二大步。
  • 透過Tracepoint理解內核 - 調度器框架和性能
    進程相關的主要事件sched_process_hang. 進程hangsched_process_wait. 等子進程的狀態變化sched_wait_task. 等待其他任務unschedule, 比如用於ptrace.sched_wake_idle_without_ipi.
  • Linux的fork()系統調用
    Linux的fork()系統調用,就是以父進程為模版創建子進程,是Linux系統的進程管理機制的核心API之一,另一個是調度器函數schedule(),它的用戶態API就是之前說自旋鎖時提到的sched_yield()。如果是「21天學寫作業系統」,那麼最先要實現的就是這兩個API。
  • 從零開始入門 K8s:調度器的調度流程和算法介紹
    導讀:Kubernetes 作為當下最流行的容器自動化運維平臺,以聲明式實現了靈活的容器編排,本文以 v1.16 版本為基礎詳細介紹了 K8s 的基本調度框架、流程,以及主要的過濾器、Score 算法實現等,並介紹了兩種方式用於實現自定義調度能力。
  • Linux 內核的測試和調試(6)
    使用 git send-email 命令是提交補丁最安全的方式,可以避免你的郵箱的兼容性問題。你的 .gitconfig 文件裡面需要配置好有效的 smtp 伺服器,詳細操作參考 git 的幫助文檔。更多提交補丁的規矩,請參考下面的資料:linux_git/Documentation/applying-patches.txtlinux_git/Documentation/SubmitChecklistlinux_git/Documentation/SubmittingDriverslinux_git/Documentation