1.1.概述:Linux是一個基於POSIX和UNIX的多用戶、多任務、支持多線程和多CPU的操做系統。node
1.2.介紹:Linux繼承了Unix以網絡爲核心的設計思想,能運行主要的UNIX工具軟件、應用程序和網絡協議;它支持32位和64位硬件。同時,也是一個性能穩定的多用戶網絡操做系統。而且可無償使用並自由傳播。linux操做系統大部分用來架設服務器,提供高負載數據處理。linux
1.3.特色:Linux具備開源,免費,高效,安全等特色,更爲重要的是Linux是一款免費的操做系統,用戶能夠經過網絡或其餘途徑免費得到,並能夠任意修改其源代碼。這使得Linux始終與時俱進。程序員
2.1進程的定義:算法
狹義定義:進程是正在運行的程序的實例(an instance of a computer program that is being executed)。shell
廣義定義:進程是一個具備必定獨立功能的程序關於某個數據集合的一次運行活動。它是操做系統動態執行的基本單元,在傳統的操做系統中,進程既是基本的分配單元,也是基本的執行單元。windows
2.2進程的概念:緩存
主要有兩點:第一,進程是一個實體。每個進程都有它本身的地址空間,通常狀況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲着活動過程調用的指令和本地變量。第二,進程是一個「執行中的程序」。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操做系統執行之),它才能成爲一個活動的實體,咱們稱其爲進程。安全
2.3進程的特徵:服務器
·動態性:進程的實質是程序在多道程序系統中的一次執行過程,進程是動態產生,動態消亡的。網絡
·併發性:任何進程均可以同其餘進程一塊兒併發執行
·獨立性:進程是一個能獨立運行的基本單位,同時也是系統分配資源和調度的獨立單位;
·異步性:因爲進程間的相互制約,使進程具備執行的間斷性,即進程按各自獨立的、不可預知的速度向前推動
·結構特徵:進程由程序、數據和進程控制塊三部分組成。
3.1建立進程
(1) 系統初始化會建立新的進程
(2) 當一個正在運行的進程中,若執行了建立進程的系統調用,那麼也會建立新的進程
(3) 用戶發出請求,建立一個進程
(4) 初始化一個批處理做業時,也會建立新的線程
在Linux中主要提供了fork、vfork、clone三個進程建立方法。
Linux中可使用fork函數來建立新進程。以下列代碼所示:(2) 當一個正在運行的進程中,若執行了建立進程的系統調用,那麼也會建立新的進程
(3) 用戶發出請求,建立一個進程
(4) 初始化一個批處理做業時,也會建立新的線程
在Linux中主要提供了fork、vfork、clone三個進程建立方法。
Linux中可使用fork函數來建立新進程。以下列代碼所示:
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main(){ pid_t ret = fork(); printf("hello proc:%d,ret = %d\n",getpid(),ret); return 0; }
fork函數調用的用途 :一個進程但願複製自身,從而父子進程能同時執行不一樣段的代碼。
3.2進程終止
(1) 正常退出
(2)錯誤退出
(3) 致命錯誤
(4) 被其餘進程殺死
3.3進程控制塊
廣義義上,全部的進程信息被放在⼀個叫作進程控制塊的數據結構中,能夠理解爲進程屬性的集合,該控制塊由操做系統建立和管理。
進程控制塊是操做系統可以支持多線程和提供多重處理技術的關鍵工具。每一個進程在內核中都有⼀個進程控制塊(PCB)來維護進程相關的信息,
在Linux內核中 進程控制塊是task_struct結構體,它包含了一個進程的全部信息。
該結構體被定義在" include/linux/sched.h"
文件中。
task_struct主要包含了下面這些內容:
標識符:描述本進程的惟一標識符,用來區別其餘進程。
狀態:任務狀態、退出代碼、退出信號等。
優先級:相對於其餘進程的優先級。
程序計數器(PC):程序中即將被執行的下一條指令的地址。
內存指針:包括程序代碼和進程相關數據的指針,還有和其它進程共享的內存塊的指針。
上下文數據:進程執行時處理器的寄存器中的數據。
I/O狀態信息:包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表。
記帳信息:可能包括處理器時間總和,使用的時鐘數綜合、時間限制、記帳號等。
3.4進程的標識符
進程標識符(PID)是一個進程的基本屬性,其做用相似於每一個人的身份證號碼。
每一個進程在系統中都有惟一的一個ID標識它。正是由於其惟一,因此係統能夠根據它準肯定位到一個進程。
4.1進程的狀態:
進程狀態反映進程執行過程的變化,這些狀態隨着進程的執行和外界條件的變化而轉換。
·可運行狀態 處於這種狀態的進程的包括正在運行和要麼正準備運行的進程正在運行的進程就是當前進程(由current所指向的進程),而準備運行的進程只要獲得CPU就能夠當即投入運行,CPU是這些進程惟一等待的系統資源。系統中有一個運行隊列(run_queue),用來容納全部處於可運行狀態的進程,調度程序執行時,從中選擇一個進程投入運行。
·等待狀態 處於該狀態的進程正在等待某個事件(event)或某個資源,它確定位於系統中的某個等待隊列(wait_queue)中。Linux中處於等待狀態的進程分爲兩種:可中斷的等待狀態和不可中斷的等待狀態。
·暫停狀態 此時的進程暫時中止運行來接受某種特殊處理。一般當進程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信號後就處於這種狀態。
·僵死狀態 顧名思義,處於該狀態的進程就是死進程,這種進程其實是系統中的垃圾,必須進行相應處理以釋放其佔用的資源。
4.2進程的調度
4.2.1進程調度的概述:
不管是在批處理系統仍是分時系統中,用戶進程數通常都多於處理機數、這將致使它們互相爭奪處理機。另外,系統進程也一樣須要使用處理機。這就要求進程調度程序按必定的策略,動態地把處理機分配給處於就緒隊列中的某一個進程,以使之執行。
那麼就須要調度程序利用一部分信息決定系統中哪一個進程最應該運行,並結合進程的狀態信息以保證系統運轉的公平和高效。
目的:調度程序運行時,要在全部可運行狀態的進程中選擇最值得運行的進程投入運行。
4.2.2進程調度發生在什麼時候呢?
(1)正在執行的進程執行完畢。這時,若是不選擇新的就緒進程執行,將浪費處理機資源。
(2)執行中進程本身調用阻塞原語將本身阻塞起來進入睡眠等狀態。
(3)執行中進程調用了P原語操做,從而因資源不足而被阻塞;或調用了v原語操做激活了等待資源的進程隊列。
(4)執行中進程提出I/O請求後被阻塞。
(5)在分時系統中時間片已經用完。
(6)在執行完系統調用等系統程序後返回用戶進程時,這時可看做系統進程執行完畢,從而可調度選擇一新的用戶進程執行。
(7)就緒隊列中的某進程的優先級變得高於當前執行進程的優先級,從而也將引起進程調度。
4.2.3兩種佔用CPU的方式
可剝奪式 (可搶佔式preemptive):就緒隊列中一旦有優先級高於當前執行進程優先級的進程存在時,便當即發生進程調度,轉讓處理機。
不可剝奪式 (不可搶佔式non_preemptive):即便在就緒隊列存在有優先級高於當前執行進程時,當前進程仍將佔用處理機直到該進程本身因調用原語操做或等待I/O而進入阻塞、睡眠狀態,或時間片用完時才從新發生調度讓出處理機。
4.3進程調度的管理
這麼多的進程須要佔用CPU,那麼哪一個進程優先佔用CPU就餓尤其重要了。
操做系統須要一個管理單元,負責調度進程,由管理單元來決定下一刻應該由誰使用CPU,這裏充當管理單元的就是進程調度器。進程調度器的任務就是合理分配CPU時間給運行的進程。
5.1簡介
一般來講,操做系統是應用程序和可用資源之間的媒介。
典型的資源有內存和物理設備。可是CPU也能夠認爲是一個資源,調度器能夠臨時分配一個任務在上面執行(單位是時間片)。調度器使得咱們同時執行多個程序成爲可能,所以能夠與具備各類需求的用戶共享CPU。
內核必須提供一種方法, 在各個進程之間儘量公平地共享CPU時間, 而同時又要考慮不一樣的任務優先級.
調度器的一個重要目標是有效地分配 CPU 時間片,同時提供很好的用戶體驗。調度器還須要面對一些互相沖突的目標,例如既要爲關鍵實時任務最小化響應時間, 又要最大限度地提升 CPU 的整體利用率.
5.3調度策略
開始的調度器是複雜度爲O(n)O(n)的始調度算法(實際上每次會遍歷全部任務,因此複雜度爲O(n)), 這個算法的缺點是當內核中有不少任務時,調度器自己就會耗費很多時間,因此,從linux2.5開始引入赫赫有名的O(1)O(1)調度器
然而,linux是集全球不少程序員的聰明才智而發展起來的超級內核,沒有最好,只有更好,在O(1)O(1)調度器風光了沒幾天就又被另外一個更優秀的調度器取代了,它就是CFS調度器(Completely Fair Scheduler.) 這個也是在2.6內核中引入的,具體爲2.6.23。
CFS設計思路很簡單,就是根據各個進程的權重分配運行時間。
重點是紅黑樹。
紅黑樹 (Red–black tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構。
在 CFS 調度器中將 sched_entity 存儲在以時間爲順序的紅黑樹中,vruntime 最低的進程存儲在樹的左側,vruntime 最高的進程存儲在樹的右側。
其實CFS 調度器的就緒隊列就是一棵以 vruntime 爲鍵值的紅黑樹, vruntime 越小的進程就越靠近整棵紅黑樹的最左端。所以,調度器只須要每次都選擇位於紅黑樹最左端的那個進程便可,由於該進程的 vruntime 最小,即該進程最值得運行。
5.6CFS 調度器的基本原理:
1.設定一個調度週期 (sched_latency_ns),目的是讓每一個進程在這個週期內至少有機會運行一次.
2.而後根據進程的數量,各個進程平均分配這個調度週期內的 CPU 使用權。這時因爲各個進程的優先級 (即 nice 值不一樣),分割調度週期的時候須要加權分配.
3.每一個進程的累計運行時間保存在本身的 vruntime 字段內,哪一個進程的 vruntime 最小就能得到運行的權利。
4.總的來講,全部進程的vruntime增加速度宏觀上看應該是同時推動的.
那麼就能夠用這個vruntime來選擇運行的進程,誰的vruntime值較小就說明它之前佔用cpu的時間較短,受到了「不公平」對待,所以下一個運行進程就是它。這樣既能公平選擇進程,又能保證高優先級進程得到較多的運行時間。這就是CFS的主要思想了。
6.1主調度器
一旦肯定了要進行進程調度,那麼schedule函數被調用。
主調度器被定義在 kernel/sched.c 文件中,由 schedule() 函數實現。
/* * CFS stats for a schedulable entity (task, task-group etc) * * Current field usage histogram: * * 4 se->block_start * 4 se->run_node * 4 se->sleep_start * 4 se->sleep_start_fair * 6 se->load.weight * 7 se->delta_fair * 15 se->wait_runtime */ struct sched_entity { long wait_runtime; unsigned long delta_fair_run; unsigned long delta_fair_sleep; unsigned long delta_exec; s64 fair_key; struct load_weight load; /* for load-balancing */ struct rb_node run_node; unsigned int on_rq; u64 exec_start; u64 sum_exec_runtime; u64 prev_sum_exec_runtime; u64 wait_start_fair; u64 sleep_start_fair; #ifdef CONFIG_SCHEDSTATS u64 wait_start; u64 wait_max; s64 sum_wait_runtime; u64 sleep_start; u64 sleep_max; s64 sum_sleep_runtime; u64 block_start; u64 block_max; u64 exec_max; unsigned long wait_runtime_overruns; unsigned long wait_runtime_underruns; #endif
struct cfs_rq { struct load_weight load;/*運行負載*/ unsigned long nr_running;/*運行進程個數*/ u64 exec_clock; u64 min_vruntime;/*保存的最小運行時間*/ struct rb_root tasks_timeline;/*運行隊列樹根*/ struct rb_node *rb_leftmost;/*保存的紅黑樹最左邊的節點,這個爲最小運行時間的節點,當進程選擇下一個來運行時,直接選擇這個*/ struct list_head tasks; struct list_head *balance_iterator; /* * 'curr' points to currently running entity on this cfs_rq. * It is set to NULL otherwise (i.e when none are currently running). */ struct sched_entity *curr, *next, *last; unsigned int nr_spread_over; #ifdef CONFIG_FAIR_GROUP_SCHED struct rq *rq; /* cpu runqueue to which this cfs_rq is attached */ /* * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in * a hierarchy). Non-leaf lrqs hold other higher schedulable entities * (like users, containers etc.) * * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This * list is used during load balance. */ struct list_head leaf_cfs_rq_list; struct task_group *tg; /* group that "owns" this runqueue */ #ifdef CONFIG_SMP /* * the part of load.weight contributed by tasks */ unsigned long task_weight; /* * h_load = weight * f(tg) * * Where f(tg) is the recursive weight fraction assigned to * this group. */ unsigned long h_load; /* * this cpu's part of tg->shares */ unsigned long shares; /* * load.weight at the time we set shares */ unsigned long rq_weight; #endif #endif };
struct sched_entity { struct load_weight load; /* for load-balancing負荷權重,這個決定了進程在CPU上的運行時間和被調度次數 */ struct rb_node run_node; unsigned int on_rq; /* 是否在就緒隊列上 */ u64 exec_start; /* 上次啓動的時間*/ u64 sum_exec_runtime; u64 vruntime; u64 prev_sum_exec_runtime; /* rq on which this entity is (to be) queued: */ struct cfs_rq *cfs_rq; ... };
static struct task_struct *pick_next_task_fair(struct rq *rq) { struct task_struct *p; struct cfs_rq *cfs_rq = &rq->cfs; struct sched_entity *se; if (unlikely(!cfs_rq->nr_running)) return NULL; do {/*此循環爲了考慮組調度*/ se = pick_next_entity(cfs_rq); set_next_entity(cfs_rq, se);/*設置爲當前運行進程*/ cfs_rq = group_cfs_rq(se); } while (cfs_rq); p = task_of(se); hrtick_start_fair(rq, p); return p; }
調用_pick_next_entity完成實質工做
/*函數自己並不會遍歷數找到最左葉子節點(是 全部進程中vruntime最小的那個),由於該值已經緩存 在rb_leftmost字段中*/ static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq) { /*rb_leftmost爲保存的紅黑樹的最左邊的節點*/ struct rb_node *left = cfs_rq->rb_leftmost; if (!left) return NULL; return rb_entry(left, struct sched_entity, run_node);
_pick_next_entity其實就是挑選了紅黑樹最左端的子節點做爲下一個調度執行的進程,即挑選當前 vruntime 最小的進程做爲下一個調度執行的進程。
此次對於linux的進程的學習,由linux開始瞭解,再到進程的建立,轉換,組織,管理,調度的效率,再去深刻了解CFS 調度器,才感到建立一種新的算法來讓當前系統全部的進程更高效地運行是多麼難。也從中瞭解到,進程是操做系統的重要組成部分。
可能在生活,我 們並不會去深刻進程的做用也看不到操做系統在這背後對進程的優化以及調度作了多大的努力。可是那些不斷追求進程的效率與公平的努力始終在背後推進着咱們的生活。
在將從一開始的非搶佔式單任務運行系統到如今的搶佔式多任務並行系統,進程的調度算法一次又一次的優化創新,到學到CFS 調度器2.6版。在將來咱們研究出更好的算法去提升效率。