第一次作业:深入Linux源码分析进程模型
?
一.進程的概念
? ? ?第一,進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括文本區域(text region)、數據區域(data region)和堆棧(stack region)。文本區域存儲處理器執行的代碼;數據區域存儲變量和進程執行期間使用的動態分配的內存;堆棧區域存儲著活動過程調用的指令和本地變量。
? ? ?第二,進程是一個“執行中的程序”。程序是一個沒有生命的實體,只有處理器賦予程序生命時(操作系統執行之),它才能成為一個活動的實體,我們稱其為進程。
其中在Linux內核中賦予它更通用的名稱----任務(task)
進程在整個內核中的功能位置:
我們還可以分兩個層次對操作系統進程進行討論。?
在較高的層次上,“進程”是一個重要的組織概念,用其說明一個計算機系統作為一個整體的活動。將計算機系統視作若干進程的組合活動是適合的,每一個進程與一道特定的程序相結合。例如“shell”或者“vi”編輯程序。在這一層次上,進程本身被視作系統中的活動實體,而真正的活動部件本體,即處理機和外部設備則被消隱,不引起人們的注意。進程誕生、生長,然后死亡;它們存在的數量在不斷變化;它們可以獲得并釋放資源;它們可以交互作用、合作、沖突、共享資源等等。?
在較低的層次上,進程是不活動的實體,它們依靠活動的實體,例如處理機才起作用。借助于頻繁地使用處理機從一個進程映像的執行切換到另一個,就可以產生一種印象:每一個進程映像都連續發生變化,這就導致較高層次上的解釋。
? ? ? ?Linux進程的四個要素:
1.有一段程序供其執行,這段程序不一定是某個進程所專有的,可以與其他進程共用。
2.有進程專用的內核空間堆棧。
3.在內核中有一個task_struct數據結構,即通常所說的“進程控制塊”。有了這個數據結構,進程才能成為內核調度的一個基本單位接受內核的調度。同時,這個結構還記錄著進程所占用的各項資源。
4.有獨立的存儲空間,這意味著擁有專有的用戶空間;進一步,還意味著除前述的內核空間堆棧外還有其專用的用戶空間堆棧。有一點必須指出,內核空間是不能獨立的,任何進程都不可能直接(不通過系統調用)改變內核空間的內容(除其本身的內核空間堆棧以外)。
二.進程的組織:
進程控制塊
進程創建時,操作系統就新建一個PCB結構,它之后就常駐內存,任一時刻可以存取,?在進程結束時刪除。PCB是進程實體的一部分,是進程存在的唯一標志。當創建一個進程時,系統為該進程建立一個PCB;當進程執行時,系統通過其PCB?了 解進程的現行狀態信息,以便對其進行控制和管理;當進程結束時,系統收回其PCB,該進 程隨之消亡。操作系統通過PCB表來管理和控制進程。
| 進程標識符(PID) | 進程當前狀態 | 代碼段指針 | 通用寄存器值 |
| 用戶標識符(UID) | 進程優先級 | 數據段指針 | 地址寄存器值 |
| ? | 代碼運行入口地址 | 堆棧段指針 | 控制寄存器值 |
| ? | 程序的外存地址 | 文件描述符 | 標志寄存器值 |
| ? | 進入內存時間 | 鍵盤 | 狀態字 |
| ? | 處理機占用時間 | 鼠標 | ? |
| ? | 信號量使用 | ? | ? |
表2-1是一個PCB的實例,PCB主要包括進程描述信息、進程控制和管理信息、資源 分配清單和處理機相關信息等。各部分的主要說明如下:
1) 進程描述信息
進程標識符:標志各個進程,每個進程都有一個并且是唯一的標識號。
用戶標識符:進程歸屬的用戶,用戶標識符主要為共享和保護服務。
2) 進程控制和管理信息
進程當前狀態:描述進程的狀態信息,作為處理機分配調度的依據。
進程優先級:描述進程搶占處理機的優先級,優先級高的進程可以優先獲得處理機。
3) 資源分配清單,用于說明有關內存地址空間或虛擬地址空間的狀況;所打開文件的 列表和所使用的輸入/輸出設備信息。
4) 處理機相關信息,主要指處理機中各寄存器值,當進程被切換時,處理機狀態信息 都必須保存在相應的PCB中,以便在該進程重新執行時,能再從斷點繼續執行。
在一個系統中,通常存在著許多進程,有的處于就緒狀態,有的處于阻塞狀態,而且阻塞的原因各不相同。為了方便進程的調度和管理,需要將各進程的PCB用適當的方法組織起來。目前,常用的組織方式有鏈接方式和索引方式兩種。鏈接方式將同一狀態的PCB鏈接成一個隊列,不同狀態對應不同的隊列,也可以把處于阻塞狀態的進程的PCB,根據其阻塞原因的不同,排成多個阻塞隊列。索引方式是將同一狀態的進程組織在一個索引表中,索引表的表項指向相應的PCB,不同狀態對應不同的索引表,如就緒索引表和阻塞索引表等。 大量的進程是如何組織的: /* wq為某個等待隊列的隊列頭 */ void sleep_on (wait_queue_head_t *wq) {/* 聲明一個等待隊列結點 */wait_queue_t wait;/* 用當前進程初始化這個等待隊列結點 */init_waitqueue_entry (&wait, current);/* 設置當前進程狀態為TASK_UNINTERRUPTIBLE */current->state = TASK_UNINTERRUPTIBLE;/* 將這個代表著當前進程的等待隊列結點加入到wq這個等待隊列 */add_wait_queue (wq, &wait);/* 請求調度器進行調度,執行完schedule后進程會被移除CPU運行隊列,只有等待隊列喚醒后才會重新回到CPU運行隊列 */schedule ();/* 這里進程已經被等待隊列喚醒,重新移到CPU運行隊列,也就是等待的條件已經為真,喚醒后第一件事就是將自己從等待隊列wq中移除 */remove_wait_queue (wq, &wait); } 在Linux操作系統中,用戶創建一個新進程的一個方法是調用系統調用fork。調用fork的進程是父進程(parent process),而新創建的進程是子進程(child? process)。在系統中調用fork返回時,子進程是父進程的一個拷貝,兩個進程除了返回PID(Process ID)不同外,具有完全一樣的變量值,它們打開的文件都相同。而在系統初啟時由內核內部地創建的#0進程(idle進程)是唯一不通過fork而創建的進程;#1進程是系統創建的init進而在系統初啟時由內核內部地創建的#0進程(idle進程)是唯一不通過fork而創建的進程;#1進程是系統創建的init進程,是系統其他每個進程的父進程。在Linux中,fork和_clone函數的具體實現是通過do_fork函數來實現的。 do_fork的算法如下: int do_fork(unsigned long clone_flags,unsigned long usp,struct pt_regs *regs) {取一個空閑的task數組表項和唯一的PID號;根據clone_flags參數的值將父進程的進程表現拷貝到子進程表項中或設置為共享;把進程加入進程圖表設置跟蹤進程的數據結構調用hash_pid把新進程置入pidhash表中;調用wake_up_process設進程為TASK_RUNNING并置入運行隊列;return(p->pid) }
在創建新進程后,我們需要它來處理其他實際的工作,通過調用exec來執行別的實際程序,就能夠變成獨立于其他進程的進程了,因此,創建一個真正的進程--與其祖先不同的程序鏡像,要分為兩步,一步是fork,另一步是exec.下面是C代碼描述:
/*實驗fork和exec*/ if((result=fork()==0) { /*child code*/if(exec( "new_program")<0)perror("exec failed");exit(1); } else if(result<0) { perror ("fork failde"); }三.進程狀態轉換
在一個給定時刻內,進程處于下面六種狀態之一,進程的當前狀態被記錄在struct task_struct結構中的state成員中
struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ …… };在/include/linux/sched.h定義的進程狀態:
#define TASK_RUNNING 0 /* 進程準備好運行 */ #define TASK_INTERRUPTIBLE 1 /* 等待特定事件,可以被信號中斷 */ #define TASK_UNINTERRUPTIBLE 2 /* 等待特定硬件條件,不可以被信號中斷*/ #define TASK_ZOMBIE 4 /* 進程已經退出 */ #define TASK_STOPPED 8 /* 進程已經停止運行 */ #define TASK_SWAPPING 16 /* 進程正在執行磁盤交換工作 */何時刻一個處理機僅能執行一個進程,而可能不止一個進程處于TASK_RUNNING狀態。TASK_RUNNING并不意味著該進程可以立即獲得CPU(雖然有時候是這樣),而是僅僅說明只要CPU一旦可用,進程就可以立即準備執行了。進程處于TASK_ZOMBIE狀態,意味進程已經退出了(或已經被殺掉了),但是其相關的struct task_struct結構并沒有被刪除。這樣即使子進程已經退出,也允許父進程對已經死去的子孫進程進行查詢。父進程通過wait來獲取TASK_ZOMBIE進程的信息,同時釋放它占用的struct task_struct結構。
?
四.task_struct數據結構
?
?
struct task_struct {volatile long state; /*state成員的可能取值如下#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_STOPPED 4
#define TASK_TRACED 8
#define EXIT_DEAD 16#define EXIT_ZOMBIE 32#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
#define TASK_DEAD 64#define TASK_WAKEKILL 128 #define TASK_WAKING 256#define TASK_PARKED 512#define TASK_NOLOAD 1024#define TASK_STATE_MAX 2048#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)#define TASK_STOPPED (TASK_WAKEKILL | __TASK_STOPPED)#define TASK_TRACED (TASK_WAKEKILL | __TASK_TRACED)*/
struct list_head run_list;struct task_struct * next_task,*prev_task;pid_t pid;struct task_struct*p_opptr,*P_pptr;/*1.p_opptr指向進程的原始祖先2.p_pptr指向進程的當前祖先3.p_cptr指向進程的最年輕子孫4.p_ysptr指向進程的下一個最年輕兄弟5.p_osptr指向進程的下一個最古老兄弟*/*p_cptr,*p_ysptr,*p_osptr;struct task_struct*pidhash_next;struct task_struct**pidhash_pprev; }
進程標識符:
pid_t pid; //進程的標識符 pid_t tgid; //線程組標識符進程標記符:
unsigned int flags; /* per process flags, defined below */?五.Linux的調度
調度器介紹
隨著時代的發展,linux也從其初始版本穩步發展到今天,從2.4的非搶占內核發展到今天的可搶占內核,調度器無論從代碼結構還是設計思想上也都發生了翻天覆地的變化,其普通進程的調度算法也從O(1)到現在的CFS,一個好的調度算法應當考慮以下幾個方面:- 公平:保證每個進程得到合理的CPU時間。
- 高效:使CPU保持忙碌狀態,即總是有進程在CPU上運行。
- 響應時間:使交互用戶的響應時間盡可能短。
- 周轉時間:使批處理用戶等待輸出的時間盡可能短。
- 吞吐量:使單位時間內處理的進程數量盡可能多。
- 負載均衡:在多核多處理器系統中提供更高的性能
- 實時進程:對系統的響應時間要求很高,它們需要短的響應時間,并且這個時間的變化非常小,典型的實時進程有音樂播放器,視頻播放器等。
- 普通進程:包括交互進程和非交互進程,交互進程如文本編輯器,它會不斷的休眠,又不斷地通過鼠標鍵盤進行喚醒,而非交互進程就如后臺維護進程,他們對IO,響應時間沒有很高的要求,比如編譯器。
調度策略
在linux系統中,調度策略分為- SCHED_NORMAL:普通進程使用的調度策略,現在此調度策略使用的是CFS調度器。
- SCHED_FIFO:實時進程使用的調度策略,此調度策略的進程一旦使用CPU則一直運行,直到有比其更高優先級的實時進程進入隊列,或者其自動放棄CPU,適用于時間性要求比較高,但每次運行時間比較短的進程。
- SCHED_RR:實時進程使用的時間片輪轉法策略,實時進程的時間片用完后,調度器將其放到隊列末尾,這樣每個實時進程都可以執行一段時間。適用于每次運行時間比較長的實時進程
??
調度
首先,我們需要清楚,什么樣的進程會進入調度器進行選擇,就是處于TASK_RUNNING狀態的進程,而其他狀態下的進程都不會進入調度器進行調度。系統發生調度的時機如下- 調用cond_resched()時
- 顯式調用schedule()時
- 從系統調用或者異常中斷返回用戶空間時
- 從中斷上下文返回用戶空間
管理組調度,內核引進了struct task_group結構,如下:
/* 進程組,用于實現組調度 */struct task_group {/* 用于進程找到其所屬進程組結構 */struct cgroup_subsys_state css;#ifdef CONFIG_FAIR_GROUP_SCHED/* CFS調度器的進程組變量,在 alloc_fair_sched_group() 中進程初始化及分配內存 *//* 該進程組在每個CPU上都有對應的一個調度實體,因為有可能此進程組同時在兩個CPU上運行(它的A進程在CPU0上運行,B進程在CPU1上運行) */struct sched_entity **se;/* 進程組在每個CPU上都有一個CFS運行隊列(為什么需要,稍后解釋) */struct cfs_rq **cfs_rq;/* 用于保存優先級默認為NICE 0的優先級 */unsigned long shares;#ifdef CONFIG_SMPatomic_long_t load_avg;atomic_t runnable_avg;#endif#endif#ifdef CONFIG_RT_GROUP_SCHED/* 實時進程調度器的進程組變量,同 CFS */struct sched_rt_entity **rt_se;struct rt_rq **rt_rq;struct rt_bandwidth rt_bandwidth;#endifstruct rcu_head rcu;/* 用于建立進程鏈表(屬于此調度組的進程鏈表) */struct list_head list;/* 指向其上層的進程組,每一層的進程組都是它上一層進程組的運行隊列的一個調度實體,在同一層中,進程組和進程被同等對待 */struct task_group *parent;/* 進程組的兄弟結點鏈表 */struct list_head siblings;/* 進程組的兒子結點鏈表 */struct list_head children;#ifdef CONFIG_SCHED_AUTOGROUPstruct autogroup *autogroup;#endifstruct cfs_bandwidth cfs_bandwidth;};?
?
調度實體(struct sched_entity)
1 /* 一個調度實體(紅黑樹的一個結點),其包含一組或一個指定的進程,包含一個自己的運行隊列,一個父親指針,一個指向需要調度的運行隊列指針 */2 struct sched_entity {3 /* 權重,在數組prio_to_weight[]包含優先級轉權重的數值 */4 struct load_weight load; /* for load-balancing */5 /* 實體在紅黑樹對應的結點信息 */6 struct rb_node run_node; 7 /* 實體所在的進程組 */8 struct list_head group_node;9 /* 實體是否處于紅黑樹運行隊列中 */ 10 unsigned int on_rq; 11 12 /* 開始運行時間 */ 13 u64 exec_start; 14 /* 總運行時間 */ 15 u64 sum_exec_runtime; 16 /* 虛擬運行時間,在時間中斷或者任務狀態發生改變時會更新 17 * 其會不停增長,增長速度與load權重成反比,load越高,增長速度越慢,就越可能處于紅黑樹最左邊被調度 18 * 每次時鐘中斷都會修改其值 19 * 具體見calc_delta_fair()函數 20 */ 21 u64 vruntime; 22 /* 進程在切換進CPU時的sum_exec_runtime值 */ 23 u64 prev_sum_exec_runtime; 24 25 /* 此調度實體中進程移到其他CPU組的數量 */ 26 u64 nr_migrations; 27 28 #ifdef CONFIG_SCHEDSTATS 29 /* 用于統計一些數據 */ 30 struct sched_statistics statistics; 31 #endif 32 33 #ifdef CONFIG_FAIR_GROUP_SCHED 34 /* 代表此進程組的深度,每個進程組都比其parent調度組深度大1 */ 35 int depth; 36 /* 父親調度實體指針,如果是進程則指向其運行隊列的調度實體,如果是進程組則指向其上一個進程組的調度實體 37 * 在 set_task_rq 函數中設置 38 */ 39 struct sched_entity *parent; 40 /* 實體所處紅黑樹運行隊列 */ 41 struct cfs_rq *cfs_rq; 42 /* 實體的紅黑樹運行隊列,如果為NULL表明其是一個進程,若非NULL表明其是調度組 */ 43 struct cfs_rq *my_q; 44 #endif 45 46 #ifdef CONFIG_SMP 47 /* Per-entity load-tracking */ 48 struct sched_avg avg; 49 #endif 50 };?
實際上,紅黑樹是根據 struct?rb_node 建立起關系的,不過 struct rb_node 與 struct sched_entity 是一一對應關系,也可以簡單看為一個紅黑樹結點就是一個調度實體。可以看出,在 struct sched_entity 結構中,包含了一個進程(或進程組)調度的全部數據,其被包含在 struct task_struct 結構中的se中,如下: 1 struct task_struct {2 ........3 /* 表示是否在運行隊列 */4 int on_rq;5 6 /* 進程優先級 7 * prio: 動態優先級,范圍為100~139,與靜態優先級和補償(bonus)有關8 * static_prio: 靜態優先級,static_prio = 100 + nice + 20 (nice值為-20~19,所以static_prio值為100~139)9 * normal_prio: 沒有受優先級繼承影響的常規優先級,具體見normal_prio函數,跟屬于什么類型的進程有關 10 */ 11 int prio, static_prio, normal_prio; 12 /* 實時進程優先級 */ 13 unsigned int rt_priority; 14 /* 調度類,調度處理函數類 */ 15 const struct sched_class *sched_class; 16 /* 調度實體(紅黑樹的一個結點) */ 17 struct sched_entity se; 18 /* 調度實體(實時調度使用) */ 19 struct sched_rt_entity rt; 20 #ifdef CONFIG_CGROUP_SCHED 21 /* 指向其所在進程組 */ 22 struct task_group *sched_task_group; 23 #endif 24 ........ 25 }?
而在 struct task_struct 結構中,我們注意到有個調度類,里面包含的是調度處理函數,它具體如下: 1 struct sched_class {2 /* 下一優先級的調度類3 * 調度類優先級順序: stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class4 */5 const struct sched_class *next;6 7 /* 將進程加入到運行隊列中,即將調度實體(進程)放入紅黑樹中,并對 nr_running 變量加1 */8 void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);9 /* 從運行隊列中刪除進程,并對 nr_running 變量中減1 */ 10 void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags); 11 /* 放棄CPU,在 compat_yield sysctl 關閉的情況下,該函數實際上執行先出隊后入隊;在這種情況下,它將調度實體放在紅黑樹的最右端 */ 12 void (*yield_task) (struct rq *rq); 13 bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt); 14 15 /* 檢查當前進程是否可被新進程搶占 */ 16 void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags); 17 18 /* 19 * It is the responsibility of the pick_next_task() method that will 20 * return the next task to call put_prev_task() on the @prev task or 21 * something equivalent. 22 * 23 * May return RETRY_TASK when it finds a higher prio class has runnable 24 * tasks. 25 */ 26 /* 選擇下一個應該要運行的進程運行 */ 27 struct task_struct * (*pick_next_task) (struct rq *rq, 28 struct task_struct *prev); 29 /* 將進程放回運行隊列 */ 30 void (*put_prev_task) (struct rq *rq, struct task_struct *p); 31 32 #ifdef CONFIG_SMP 33 /* 為進程選擇一個合適的CPU */ 34 int (*select_task_rq)(struct task_struct *p, int task_cpu, int sd_flag, int flags); 35 /* 遷移任務到另一個CPU */ 36 void (*migrate_task_rq)(struct task_struct *p, int next_cpu); 37 /* 用于上下文切換后 */ 38 void (*post_schedule) (struct rq *this_rq); 39 /* 用于進程喚醒 */ 40 void (*task_waking) (struct task_struct *task); 41 void (*task_woken) (struct rq *this_rq, struct task_struct *task); 42 /* 修改進程的CPU親和力(affinity) */ 43 void (*set_cpus_allowed)(struct task_struct *p, 44 const struct cpumask *newmask); 45 /* 啟動運行隊列 */ 46 void (*rq_online)(struct rq *rq); 47 /* 禁止運行隊列 */ 48 void (*rq_offline)(struct rq *rq); 49 #endif 50 /* 當進程改變它的調度類或進程組時被調用 */ 51 void (*set_curr_task) (struct rq *rq); 52 /* 該函數通常調用自 time tick 函數;它可能引起進程切換。這將驅動運行時(running)搶占 */ 53 void (*task_tick) (struct rq *rq, struct task_struct *p, int queued); 54 /* 在進程創建時調用,不同調度策略的進程初始化不一樣 */ 55 void (*task_fork) (struct task_struct *p); 56 /* 在進程退出時會使用 */ 57 void (*task_dead) (struct task_struct *p); 58 59 /* 用于進程切換 */ 60 void (*switched_from) (struct rq *this_rq, struct task_struct *task); 61 void (*switched_to) (struct rq *this_rq, struct task_struct *task); 62 /* 改變優先級 */ 63 void (*prio_changed) (struct rq *this_rq, struct task_struct *task, 64 int oldprio); 65 66 unsigned int (*get_rr_interval) (struct rq *rq, 67 struct task_struct *task); 68 69 void (*update_curr) (struct rq *rq); 70 71 #ifdef CONFIG_FAIR_GROUP_SCHED 72 void (*task_move_group) (struct task_struct *p, int on_rq); 73 #endif 74 };?
這個調度類具體有什么用呢,實際上在內核中不同的調度算法它們的操作都不相同,為了方便修六改、替換調度算法,使用了調度類,每個調度算法只需要實現自己的調度類就可以了,CFS算法有它的調度類,SCHED_FIFO也有它自己的調度類,當一個進程創建時,用什么調度算法就將其 task_struct->sched_class 指向其相應的調度類,調度器每次調度處理時,就通過當前進程的調度類函數進程操作,大大提高了可移植性和易修改性。?
?六.對操作系統進程模型的看法? 操作系統(Operating System,簡稱OS)是管理和控制計算機硬件與軟件資源的計算機程序,是直接運行在“裸機”上的最基本的系統軟件,任何其他軟件都必須在操作系統的支持下才能運行。
操作系統是用戶和計算機的接口,同時也是計算機硬件和其他軟件的接口。操作系統的功能包括管理計算機系統的硬件、軟件及數據資源,控制程序運行,改善人機界面,為其它應用軟件提供支持,讓計算機系統所有資源最大限度地發揮作用,提供各種形式的用戶界面,使用戶有一個好的工作環境,為其它軟件的開發提供必要的服務和相應的接口等。實際上,用戶是不用接觸操作系統的,操作系統管理著計算機硬件資源,同時按照應用程序的資源請求,分配資源,如:劃分CPU時間,內存空間的開辟,調用打印機等。 在進程模型中,計算機上所有可運行的軟件,通常也包括操作系統,被組織成若干順序進程(sequential process),簡稱進程(process)。操作系統中最核心的概念是進程, 進程也是并發程序設計中的一個最重要、 最基本的概念。進程是一個動態的過程, 即進程有生命周期, 它擁有資源, 是程序的執行過程, 其狀態是變化的。?Windows、?unix和Linux是目前最流行的幾個操作系統。七.參考資料
https://wenku.baidu.com/view/64179a4bcf84b9d528ea7a0a.html https://www.doc88.com/p-7019532024389.html http://www.cnblogs.com/tolimit/ https://blog.csdn.net/bit_clearoff/article/details/54292300 https://blog.csdn.net/hgnuxc_1993/article/details/54847732#0-qzone-1-93817-d020d2d2a4e8d1a374a433f596ad1440?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
轉載于:https://www.cnblogs.com/liyuanZhang/p/8971268.html
總結
以上是生活随笔為你收集整理的第一次作业:深入Linux源码分析进程模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 广发银行信用卡还款日能更改吗?还款日第二
- 下一篇: 信用卡到期没钱还怎么办?几个小窍门帮您避