?
一.進程的概念
? ? ?第一,進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括文本區域(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); }
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的調度
調度器介紹
- 公平:保證每個進程得到合理的CPU時間。
- 高效:使CPU保持忙碌狀態,即總是有進程在CPU上運行。
- 響應時間:使交互用戶的響應時間盡可能短。
- 周轉時間:使批處理用戶等待輸出的時間盡可能短。
- 吞吐量:使單位時間內處理的進程數量盡可能多。
- 負載均衡:在多核多處理器系統中提供更高的性能
- 實時進程:對系統的響應時間要求很高,它們需要短的響應時間,并且這個時間的變化非常小,典型的實時進程有音樂播放器,視頻播放器等。
- 普通進程:包括交互進程和非交互進程,交互進程如文本編輯器,它會不斷的休眠,又不斷地通過鼠標鍵盤進行喚醒,而非交互進程就如后臺維護進程,他們對IO,響應時間沒有很高的要求,比如編譯器。
調度策略
- SCHED_NORMAL:普通進程使用的調度策略,現在此調度策略使用的是CFS調度器。
- SCHED_FIFO:實時進程使用的調度策略,此調度策略的進程一旦使用CPU則一直運行,直到有比其更高優先級的實時進程進入隊列,或者其自動放棄CPU,適用于時間性要求比較高,但每次運行時間比較短的進程。
- SCHED_RR:實時進程使用的時間片輪轉法策略,實時進程的時間片用完后,調度器將其放到隊列末尾,這樣每個實時進程都可以執行一段時間。適用于每次運行時間比較長的實時進程
??
調度
- 調用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 };
?
? 操作系統(Operating System,簡稱OS)是管理和控制計算機硬件與軟件資源的計算機程序,是直接運行在“裸機”上的最基本的系統軟件,任何其他軟件都必須在操作系統的支持下才能運行。
七.參考資料
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?