基於Linux Kernel Version 4.13.0-36-generic的源碼分析進程模型
一、簡介
本文主要基於Linux Kernel Version 4.13.0-36-generic的源代碼,來進行深入分析其進程模型,具體包含的內容如下:
1. 操作系統是怎麽組織進程的
2. 進程狀態如何轉換
3. 進程是如何調度的
4. 自己對該操作系統進程模型的看法
(註:Linux Kernel Version 4.13.0-36-generic源代碼的連接地址:https://elixir.bootlin.com/linux/v4.13/source/kernel)
二、進程
2.1進程的理解
1).進程是對正在運行程序的一個抽象。一個進程就是一個正在執行程序的實例,包括程序計數器、寄存器和變量的當前值。
2).狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。
3).廣義定義:進程是一個具有一定獨立功能的程序關於某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
2.2走近進程
與其在進程的定義上糾結,不如親自去實踐。
在Windows操作系統上,我們可以通過打開任務管理器,查看各類進程。
在Linux操作系統上,我們可以在終端命令行輸入ps aux並回車查看所有進程。
其中各個字段的解釋:
(PID:進程ID;%CPU:CPU占有數;%MEM:物理內存;VSZ:虛擬內存;RSS:實際物理內存;STAT:進程狀態;START:啟動時間;COMMAND:進程名稱)
三、操作系統是怎麽組織進程的
task_struct是Linux內核的一種數據結構,它保存進程的信息,可以在include/linux/sched.h中找到它,所有系統的進程都是以task_struct鏈表的形式存在在內核中的。
現在介紹一下task_struct中包含的一些主要內容:
PID:為進程的標識
Processor:標識用戶正在使用的CPU
State:標識進程的狀態(有六種,見後文介紹)
Prority:實時進程的優先級,對普通進程無效
Policy:表示進程調度策略
A.進程標識符PID
進程標識符PID是描述本進程的唯一標識符,來區別其他進程。它在task_struct中的定義為如下:
對於PID的取值範圍,在include/linux/threads.h中,有如下宏定義:
在CONFIG_BASE_SMALL配置為0的情況下,PID的取值範圍是0~32767,所有系統中的進程數最大值為32768個。
B.進程的狀態
在task_struct中定義如下:
State成員的可能取值如下:
現在介紹一下Linux中的進程的幾個狀態:
1. 可運行狀態(TASK-RUNNING):表示進程要麽正在執行,要麽正要準備執行。
2. 可中斷阻塞狀態(TASK-UBERRUPTIBLE):表示進程被阻塞,直到某個條件為真。如果條件達成,則狀態變為可運行態。
3. 不可中斷阻塞狀態(TASK-UN INTERRUPTIBLE):不能通過接受一個信號來喚醒。
4. 僵死狀態(TASK-ZOMBLE):表示進程的執行被終止,但是父進程還沒有使用wait()等系統調用來獲知它的終止信息。
5. 暫停態(TASK-STOPPED):表示進程被停止執行
進程的基本狀態如下圖:
C.進程的優先級
優先級在task_struct中定義如下:
四、進程是如何調度的
4.1調度的理解
在操作系統中,完成選擇工作的這一部分就稱為是調度程序,該程序使用的算法稱為調度算法。根據如何處理時鐘中斷,可以調度算法把調度算法分為兩類:非搶占式和搶占式。不同的環境需要不同的調度算法,換句話說,在不同的系統中,調度程序的優化是不同的,所以這裏有必要劃分三種環境:批處理、交互式和實時。
典型的調度算法有:
(1) 先來先服務
(2) 短作業優先
(3) 優先級
(4) 高響應比優先
(5) 時間片輪轉
在這裏,我想來介紹一下CFS(Completely Fair Scheduler)調度器,即完全公平調度器。
4.2 CFS調度算法
CFS是在Linux Kernel 2.6.23之後采用,使用紅黑樹來實現,算法效率為O(log(n))。
調度算法調度算法最核心的兩點即為調度哪個進程執行、被調度進程執行的時間多久。前者稱為調度策略,後者為執行時間。
4.2.1、調度策略
CFS給cfs_rp(CFS的運行隊列)中的每一個進程安排一個虛擬時鐘vruntime。調度器總是選擇vruntime值最低的進程執行,它記錄著進程已經運行的時間,其大小與進程的權重、運行時間存在一個定量計算關系。
vruntime = 實際運行時間 * 1024 / 進程權重
所有的進程都以nice值為0的權重1024作為基準,以此來計算自己的vruntime增加速度。下面給出一些換算關系:
分配給進程的時間 = 調度周期 * 進程權重 / 全部進程權重之和
vruntime = 實際運行時間 * 1024 / 進程權重
vruntime = (調度周期 * 進程權重 / 全部進程權重之和) * 1024 / 進程權重
vruntime = (調度周期 / 全部進程權重之和) * 1024
雖然進程的權重不同,但是它們的 vruntime增長速度應該是一樣的 ,與權重無關。vruntime值較小的進程,說明它以前占用cpu的時間較短,受到了不公平待遇,因此選擇作為下一次運行的進程。
4.2.2、執行時間
CFS采用當前系統中全部可調度進程優先級的比重確定每一個進程執行的時間片,即:
分配給進程的時間 = 調度周期 * 進程權重 / 全部進程之和。
4.2.3、CFS調度算法內核實現
A.紅黑樹——骨架
1.紅黑樹是自平衡的,沒有路徑比其它任何路徑長兩倍以上。
2.樹上運行按O(log n)時間發生(n為樹中節點的數量),可以快速高效的插入或者刪除任務。
盜圖一波…之後繼續盜圖,請看紅黑樹數據結構
(以上內容以及相關結構均可在include/linux/sched.h中找到。)
接著我們來詳細介紹一下CFS的結構。調度實體sched_entity,它代表了要給調度單位,在組調度關閉的時候可以把他等同為進程。每一個task_struct中都有一個sched_entity進程的vruntime和權重都保存在這個結構體中。
sched_entity通過紅黑樹組織在一起,所有的sched_entity以vruntime為key插入到紅黑樹中,同時緩存樹的最左側節點,也就是vruntime最小的節點,這樣可以迅速選中vruntime最小的進程。
B.兩個重要的結構體
完全公平隊列cfs_rq:描述運行在一個cpu上的處於TASK_RUNNING狀態的普通進程的各種運行信息。
struct cfs_rq { struct load_weight load; //運行隊列總的進程權重 unsigned int nr_running, h_nr_running; /進程的個數 u64 exec_clock; //運行的時鐘 u64 min_vruntime; /該cpu運行隊列的vruntime推進值,一般是紅黑樹中最小的 #ifndef CONFIG_64BIT u64 min_vruntime_copy; #endif struct rb_root tasks_timeline;//紅黑樹的根節點 struct rb_node *rb_leftmost;//指向vruntime值最小的節點 struct sched_entity *curr, *next, *last, *skip; #ifdef CONFIG_SCHED_DEBUG unsigned int nr_spread_over; #endif #ifdef CONFIG_SMP struct sched_avg avg; u64 runnable_load_sum; unsigned long runnable_load_avg; #ifdef CONFIG_FAIR_GROUP_SCHED unsigned long tg_load_avg_contrib; unsigned long propagate_avg; #endif atomic_long_t removed_load_avg, removed_util_avg; #ifndef CONFIG_64BIT u64 load_last_update_time_copy; #endif #ifdef CONFIG_FAIR_GROUP_SCHED unsigned long h_load; u64 last_h_load_update; struct sched_entity *h_load_next; #endif /* CONFIG_FAIR_GROUP_SCHED */ #endif /* CONFIG_SMP */ #ifdef CONFIG_FAIR_GROUP_SCHED struct rq *rq; //系統中有普通進程的運行隊列,實時進程的運行隊列,這些隊列都包含在啊rp運行隊列中 int on_list; struct list_head leaf_cfs_rq_list; struct task_group *tg; /* group that "owns" this runqueue */ #ifdef CONFIG_CFS_BANDWIDTH int runtime_enabled; u64 runtime_expires; s64 runtime_remaining; u64 throttled_clock, throttled_clock_task; u64 throttled_clock_task_time; int throttled, throttle_count; struct list_head throttled_list; #endif /* CONFIG_CFS_BANDWIDTH */ #endif /* CONFIG_FAIR_GROUP_SCHED */ };
調度實體sched_entity:記錄一個進程的運行狀態信息
struct sched_entity { /* For load-balancing: */ struct load_weight load; //進程的權重 struct rb_node run_node;//運行隊列中的紅黑樹節點 struct list_head group_node;//與組調度有關 unsigned int on_rq;//進程現在是否處於TASK_RUNNING狀態 u64 exec_start;//一個調度tick的開始時間 u64 sum_exec_runtime;//進程已經運行的實際時間 u64 vruntime;//虛擬運行時間 u64 prev_sum_exec_runtime;//本次調度之前,進程已經運行的時間 u64 nr_migrations; struct sched_statistics statistics; #ifdef CONFIG_FAIR_GROUP_SCHED int depth; struct sched_entity *parent;//組調度中的父進程 /* rq on which this entity is (to be) queued: */ struct cfs_rq *cfs_rq;//進程此時在哪個運行隊列中 /* rq "owned" by this entity/group: */ struct cfs_rq *my_q; #endif struct sched_avg avg ____cacheline_aligned_in_smp; };
五、對操作系統進程模型的看法
操作系統提供了一個並行執行串行進程的概念模型,進程可以動態地創建和終止,並且每個進程都有自己的地址空間。進程之間通過進程間通信原語來交換信息,一個進程可以處在運行、管理或阻塞狀態。進程的調度,在Linux內核版本為2.6.24中開始引入CFS調度器,而之前是采用O(1)調度器。CFS是負責將CPU資源,分配給正在執行的進程,目標在於最大化程式互動效能,最小化整體CPU的運用。它使用紅黑樹來實現。
六、參考資料
1.現代操作系統第四版(Andrew S. Tanenbaum,Herbert Bos著)
2.https://blog.csdn.net/big2chris/article/details/73322717
3.https://blog.csdn.net/XD_hebuters/article/details/79623130
4.https://www.cnblogs.com/qingjiaowoxiaoxioashou/p/5547260.html
5.https://blog.csdn.net/wangiijing/article/details/51585645
6.https://blog.csdn.net/shuizhilan/article/details/6642040
基於Linux Kernel Version 4.13.0-36-generic的源碼分析進程模型