個人操做系統複習——進程(上)

  上一篇博文複習了操做系統總的概述——個人操做系統複習——操做系統概述 ,包括對操做系統的定義、發展歷程以及操做系統結構。接下來咱們就開始詳細複習計算機知識,包括進程、處理器、存儲器等等。本篇首先對進程這個及其重要的概念進行復習,這是進程系列的上篇。html

 

 

1、什麼是併發

  併發是什麼?很簡單,前面介紹的多道批處理系統就是典型的併發執行。這裏再次過一遍高性能的多道批處理系統,其本質在於保持對系統資源的佔用,CPU運行一個任務,若這個任務中斷,如須要IO請求之類的,那麼CPU直接去運行其餘任務,原任務的IO請求由IO設備本身處理。有一個著名的圖——表示併發:linux

  

  如圖,假設計算機有輸入、計算、輸出這三個部件,一組任務順序執行,併發就是如圖流水線同樣的各部件配合。某一時刻,只有一個程序在佔用CPU(計算設備)。那麼什麼是並行呢?並行是創建在多核的基礎上的,即多個CPU同時運行,那麼有幾個CPU,同一時刻就有幾個程序同時運行。因此電腦只有一個CPU的苦逼程序員只能併發了。程序員

  現代系統的實際併發比上圖要複雜的多,現代計算機系統的併發是以時間片爲基礎的,即在很短的時間內,每一個進程都分別運行一次。這樣,宏觀上,每一個進程都在不斷的運行,而實際上每一個進程的運行都是間斷運行的。那麼問題來了,什麼是進程呢?數組

 


 

2、什麼是進程 

  進程的目的就是爲了對併發執行的程序進行控制。進程實體由程序段、數據段、PCB三部分構成。咱們知道計算機運行的本質就是對數據的處理的機器。session

  ——數據段就是各類數據數據結構

  ——程序段就是一系列操做計算機的指令,即操做數據的方法策略併發

  ——PCB 即進程控制塊(Process Control Block),控制運行程序段的時機。app

  書本上是這樣定義進程的:「進程是進程實體的運行過程,是系統進行資源分配和調度的一個獨立單位」。異步

  讓咱們理清楚思緒,進程是什麼?進程就是進程實體的運行過程,是一個過程。就是說,進程實體不運行,那就不叫進程。一個沒有被調用的進程實體,不叫進程。因此說,進程有動態的特徵。上面提到,進程的目的就是爲了對併發執行的程序進行控制。爲了在一個時間片內,運行多個程序才引進進程。因此說,進程有併發的特徵。進程實體是一個擁有獨立的資源(程序段和數據段)、(由於PCB)能獨立地接受調度並獨立的運行的基本單位。因此說,進程有獨立的特徵。進程運行的過程當中,因爲涉及到的資源衆多、運行環境不必定,也受到其餘進程的影響,因此,進程的運行狀況是不可具體預知的。因此書本定義,進程按各自獨立的、不可預知的速度向前推動。因此說,進程有異步的特徵。如上文所說,進程是進程實體這一數據結構被調用運行。因此說,進程有結構的特徵。ide

  能夠這麼說,真正理解了進程的這五個特徵,纔算理解了進程這個概念。高手跟咱們這些菜鳥的最大區別,不就在於對系統的理解嗎?   

 


 

3、進程的狀態

  進程有3種基本狀態:

(1)就緒(Ready)狀態

  此時的進程擁有完整的進程實體,只要得到CPU,即只要被調用就能立刻執行,這種狀態被稱爲就緒狀態。處於這種狀態的進程都會被放進就緒隊列,以便隨時接受CPU調用。

(2)執行狀態

  此時的進程已經得到CPU,正在執行。

(3)阻塞狀態

  執行中的進程,由於某種緣由(IO請求)沒法繼續執行的一種暫停狀態,暫停完畢就會變成就緒狀態。

  除了以上的3種基本狀態,有的系統額外還增長了一種狀態。

(4)掛起狀態

  爲何須要掛起狀態?由於有時候但願某些正在執行的線程暫停下來,持續一段時間後,讓它回到以前的狀態。

  掛起狀態是一種靜止的狀態,至關於把某個進程從執行的流水線上拿出來,等到須要的時候再把它放進去繼續執行。咱們來看前三種基本狀態,就緒 ->執行 -> 阻塞,阻塞完畢又回到就緒。因爲線程的異步性,阻塞是會在不肯定的有限時間內結束的。就是說,三種基本狀態是動態的,一般不存在一個線程一直處於某種狀態。掛起狀態相對於它們來講,是靜止的,由於它是被控制的,是對以不可預知的速度前進的線程的一種干擾。

  此外,爲了管理的須要,一般還有兩種比較常見的狀態。

(5)建立狀態

  咱們知道進程實體包括程序段、數據段和PCB。建立狀態指的是PCB已經被建立,由於某些緣由(程序段或數據段未放入內存等),進程還未被放入就緒隊列的這種狀態。

(6)終止狀態

  線程的終止也是有個過程的。終止狀態指的是線程除了PCB之外的系統資源都被回收後的狀態。此時線程真正終止。

 


 

4、進程的核心PCB

(1)PCB是什麼?

  做爲進程實體的一部分,PCB是用來控制進程運行的一種數據結構。它包含了進程的狀態、優先級、運行的狀態、處理機狀態、程序數據的內存地址等各類信息,一旦被操做系統調用,操做系統就從PCB中獲取的信息,來恢復進程阻塞前的現場,繼續執行。PCB通常都保存在CPU的寄存器中。

(2)PCB包含的信息

  1)進程標識符。用來標識惟一的一個進程。包括方便系統調用的內部標識符和方便用戶調用的外部標識符。進程標識符一般還包括父、子進程,以及所屬用戶等信息。

  2)處理機狀態信息

    處理機狀態信息指的是處理機調用線程時的環境信息。處理機處理調用進程時,運行過程當中的許多信息都放在處理機的寄存器中。進程阻塞或掛起時,寄存器中的運行信息會保存到PCB中,以便進程下次被調用時恢復以前的運行現場。

  3)進程調度信息

    進程調度信息指的是本進程調度所需的必要信息。包括,本進程的狀態(6種之一)、進程優先級、進程等待時間、進程執行時間(可能決定優先級)、阻塞緣由、父子進程關係等。

  4)進程控制信息

    進程控制信息指的是進程的資源信息和進程切換時的所需信息,包括進程的程序和數據的內存地址、進程同步和通訊的機制、進程資源的清單、指向下一個進程PCB的指針(若PCB的組織方式是連接方式)等。

(3)PCB的具體信息(結構)

   這裏咱們來看一下Unix中,PCB的具體結構,以便對PCB有一個清晰的認識。這玩意其實就這麼一回事:

(下面代碼摘自http://blog.sina.com.cn/s/blog_65403f9b0100gs3a.html)

struct task_struct {
volatile long state;  //說明了該進程是否能夠執行,仍是可中斷等信息
unsigned long flags;  //Flage 是進程號,在調用fork()時給出
int sigpending;    //進程上是否有待處理的信號
mm_segment_t addr_limit; //進程地址空間,區份內核進程與普通進程在內存存放的位置不一樣
                        //0-0xBFFFFFFF for user-thead
                        //0-0xFFFFFFFF for kernel-thread
//調度標誌,表示該進程是否須要從新調度,若非0,則當從內核態返回到用戶態,會發生調度
volatile long need_resched;
int lock_depth;  //鎖深度
long nice;       //進程的基本時間片
//進程的調度策略,有三種,實時進程:SCHED_FIFO,SCHED_RR, 分時進程:SCHED_OTHER
unsigned long policy;
struct mm_struct *mm; //進程內存管理信息
int processor;
//若進程不在任何CPU上運行, cpus_runnable 的值是0,不然是1 這個值在運行隊列被鎖時更新
unsigned long cpus_runnable, cpus_allowed;
struct list_head run_list; //指向運行隊列的指針
unsigned long sleep_time;  //進程的睡眠時間
//用於將系統中全部的進程連成一個雙向循環鏈表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages;       //指向本地頁面      
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt;  //進程所運行的可執行文件的格式
int exit_code, exit_signal;
int pdeath_signal;     //父進程終止是向子進程發送的信號
unsigned long personality;
//Linux能夠運行由其餘UNIX操做系統生成的符合iBCS2標準的程序
int did_exec:1; 
pid_t pid;    //進程標識符,用來表明一個進程
pid_t pgrp;   //進程組標識,表示進程所屬的進程組
pid_t tty_old_pgrp;  //進程控制終端所在的組標識
pid_t session;  //進程的會話標識
pid_t tgid;
int leader;     //表示進程是否爲會話主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group;   //線程鏈表
struct task_struct *pidhash_next; //用於將進程鏈入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;  //供wait4()使用
struct completion *vfork_done;  //供vfork() 使用
unsigned long rt_priority; //實時優先級,用它計算實時進程調度時的weight值
 
//it_real_value,it_real_incr用於REAL定時器,單位爲jiffies, 系統根據it_real_value
//設置定時器的第一個終止時間. 在定時器到期時,向進程發送SIGALRM信號,同時根據
//it_real_incr重置終止時間,it_prof_value,it_prof_incr用於Profile定時器,單位爲jiffies。
//當進程運行時,無論在何種狀態下,每一個tick都使it_prof_value值減一,當減到0時,向進程發送
//信號SIGPROF,並根據it_prof_incr重置時間.
//it_virt_value,it_virt_value用於Virtual定時器,單位爲jiffies。當進程運行時,無論在何種
//狀態下,每一個tick都使it_virt_value值減一當減到0時,向進程發送信號SIGVTALRM,根據
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer;   //指向實時定時器的指針
struct tms times;      //記錄進程消耗的時間
unsigned long start_time;  //進程建立的時間
//記錄進程在每一個CPU上所消耗的用戶態時間和核心態時間
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; 
//內存缺頁和交換信息:
//min_flt, maj_flt累計進程的次缺頁數(Copy on Write頁和匿名頁)和主缺頁數(從映射文件或交換
//設備讀入的頁面數); nswap記錄進程累計換出的頁面數,即寫到交換設備上的頁面數。
//cmin_flt, cmaj_flt, cnswap記錄本進程爲祖先的全部子孫進程的累計次缺頁數,主缺頁數和換出頁面數。
//在父進程回收終止的子進程時,父進程會將子進程的這些信息累計到本身結構的這些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示進程的虛擬地址空間是否容許換出
//進程認證信息
//uid,gid爲運行該進程的用戶的用戶標識符和組標識符,一般是進程建立者的uid,gid
//euid,egid爲有效uid,gid
//fsuid,fsgid爲文件系統uid,gid,這兩個ID號一般與有效uid,gid相等,在檢查對於文件
//系統的訪問權限時使用他們。
//suid,sgid爲備份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //記錄進程在多少個用戶組中
gid_t groups[NGROUPS]; //記錄進程所在的組
//進程的權能,分別是有效位集合,繼承位集合,容許位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS];  //與進程相關的資源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];   //進程正在運行的可執行文件名
 //文件系統信息
int link_count, total_link_count;
//NULL if no tty 進程所在的控制終端,若是不須要控制終端,則該指針爲空
struct tty_struct *tty;
unsigned int locks;
//進程間通訊信息
struct sem_undo *semundo;  //進程在信號燈上的全部undo操做
struct sem_queue *semsleeping; //當進程由於信號燈操做而掛起時,他在該隊列中記錄等待的操做
//進程的CPU狀態,切換時,要保存到中止進程的task_struct中
struct thread_struct thread;
  //文件系統信息
struct fs_struct *fs;
  //打開文件信息
struct files_struct *files;
  //信號處理函數
spinlock_t sigmask_lock;
struct signal_struct *sig; //信號處理函數
sigset_t blocked;  //進程當前要阻塞的信號,每一個信號對應一位
struct sigpending pending;  //進程上是否有待處理的信號
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;

spinlock_t alloc_lock;
void *journal_info;
};
View Code

 (4)PCB的組織形式

  系統中擁有衆多PCB,對應着衆多進程,那麼這些PCB怎麼組織的呢?通常有兩種組織方式:連接方式和索引方式。這兩種方式的共同點在於,正在執行的PCB,都有一個執行指針指向它。不一樣在於,連接方式的就緒隊列、阻塞隊列等,經過指針連接的方式組織。進程切換時,直接取就緒隊列指針便可,由於它指向的就是當前優先級最高的就緒的PCB,隨後就緒隊列指針指向其指向的下一個PCB。索引方式的就緒隊列、阻塞隊列等,經過一個表的形式來組織,就緒隊列指針指向這個表的第一條數據。這個表本質是一個指針數組,第一個指針指向的固然是優先級最高的就緒進程。

 


 

5、進程控制

  進程控制是什麼?本質就是切換進程狀態的控制。 通常由操做系統中的原語實現。原語即具備「原子操做」這種屬性的若干指令集合,說白了,就是這些指令集合,要麼所有執行,要麼所有不執行。不一樣操做系統的原語也是有區別的。

 (1)、進程建立

  進程多是由系統內核收到請求而建立,也可能由進程自己建立,由進程自己建立的進程通常是子進程,它繼承父進程擁有的所有資源。建立進程由進程建立原語實現,一般由下面幾個步驟:

 

 

 

 

 

 

 

 

 

 (2)、進程終止

   進程的終止是由操做系統執行的。當一個進程因各類緣由結束時,會通知操做系統。操做系統會調用進程終止原語來終止對應進程:

 

 

 (3)進程阻塞

  進程的阻塞是由進程自身主動執行的。但進程發現自身沒法繼續執行時,就主動調用進程阻塞原語,把本身阻塞:

 

 (4)進程喚醒

  進程的喚醒一般由其餘線程執行。但其餘線程因爲某些事件但願執行線程執行時,會調用進程喚醒原語將指定進程喚醒:

  

   值得注意的是,進程喚醒和進程阻塞是一對做用恰好相反的原語。阻塞的進程必須由進程喚醒操做才能繼續執行。

(5)進程掛起和激活

  進程掛起由自身或其餘進程執行。進程激活由其餘進程執行。過程很簡單,就不畫圖了:

  1)進程掛起:若進程爲活動就緒,就將其改成靜止就緒;若進程爲活動阻塞,就將其改成靜止阻塞;若進程正在執行,則讓調度程序從新調度。

  (PS:因爲沒有掛起隊列,因此須要把進程的PCB複製到指定的內存區域)

  2)進程激活:若進程爲靜止就緒,就將其改成活動就緒;若進程爲靜止阻塞,就將其改成活動阻塞;

 

 

 參考:《計算機操做系統(湯子瀛)》

相關文章
相關標籤/搜索