操作系統之進程與進程控制


一、進程概念

引子 程序運行在並發環境中的問題

(1)運行過程不確定
(2)結果不可再現

1.進程定義

  進程是程序在某個數據集合上的一次運行活動。數據集合是指軟硬件環境,多個進程共存或共享的環境。

2.進程的特征

(1)動態性
  進程是程序的一次執行過程,動態產生且動態消亡;
(2)並發性
  進程同其他進程一起向前推進;
(3)異步性
  進程按照各自的速度向前推進(每一個進程按照自定邏輯,不考慮其他進程的運行,各自占用CPU);
(4)獨立性
  進程是系統分配資源和調度CPU的單位(但是有了線程后,操作系統調度CPU的單位就變成了線程)。

3.進程與程序的區別

(1)進程是動態的:程序的一次執行過程;
(2)程序是靜態的:一組指令的有序集合;
(3)進程是暫存的:在內存中短期駐留;
(4)程序是長存的:可以在存儲介質上長期保存;
(5)一個程序可能有多個進程。

4.進程的分類

(1)按照使用資源的權限進行分類
  ①系統進程:系統內核相關的進程;
  ②用戶進程:運行於用戶態的進程。
(2)按照對CPU的依賴性進行分類
  ①CPU型進程:主要用於計算;
  ②I/O型進程:主要用於I/O操作。

二、進程狀態

1.進程的狀態

(1)運行狀態(Running)
  進程已經占用CPU,在CPU上運行。
(2)就緒狀態(Ready)
  具備運行條件但是由於沒有CPU可用,所以暫時不能運行。
(3)阻塞狀態(Block)也叫等待狀態(Wait)
  由於等待某項服務完成或者等待某個信號而不能運行的狀態,比如等待系統調用,I/O操作等等。

2.進程的三態模型

 

(1)就緒->運行:進程調度;
(2)運行->就緒:時間片到或者被強占;
(3)運行->阻塞:請求服務后等待響應,或者等待某個信號的到來;
(4)阻塞->就緒:請求的服務已經完成,或者等待的信號已經到來。

3.進程的五態模型

 

(1)新建狀態
  用戶向系統提交程序后,在進程建立之前的過程。
(2)終止狀態
  進程撤出系統的過程。
4.Linux中的狀態定義
(1)可運行態
  ①就緒:在就緒隊列中等待調度;
  ②運行:正在運行。
(2)阻塞(等待)態
  ①淺度(可中斷)阻塞:能被其他進程的信號或者時鍾喚醒;
  ②深度(不可中斷)阻塞:不能被其他進程通過信號和時鍾喚醒,一般是用來請求文件服務,I/O服務,系統服務。
(3)僵死態
  進程終止運行,釋放大部分資源。
(4)掛起態
  進程被掛起,暫停。可用於調試進程。

三、進程控制塊(Process Control Block,PCB)

1.進程控制塊的定義

(1)描述進程狀態、資源、與相關進程關系的數據結構;
(2)PCB是進程的標志。對於操作系統來說,它通過PCB來感知和管理進程;
(3)進程創建時會建立PCB,進程撤出時會銷毀PCB。

2.PCB中的基本成員

(1)name(ID):進程名稱(標識符,0~32768的正整數);
(2)status:狀態;
(3)next_pcb:指向下一個PCB的指針;
(4)start_addr:程序地址;
(5)p_priority:優先級;
(6)cpu status:現場保留區(堆棧);
(7)comminfo:進程通信;
(8)processfamily:家族;
(9)own resource:資源。
  不同的操作系統PCB成員會有所區別,但是一些基本成員都是相同的,可能只是命名上的區別而已。進程控制塊應包含以下三類信息:標識信息、現場信息、控制信息。

3.Linux中的進程控制塊

Linux的進程控制塊一般包含如下信息:
(1)進程狀態;
(2)調度信息;
(3)標識符;
(4)內部進程通信信息;
(5)鏈接信息;
(6)時間和計時器;
(7)文件系統;
(8)虛擬內存信息;
(9)處理器信息。

4.進程的切換

(1)進程的上下文

  Context,指進程運行環境,CPU環境(比如各個寄存器的取值)等等。進程的上下文由三部分組成:用戶級上下文(程序、數據、共享存儲區、用戶棧,它們占用進程的虛擬地址空間)、寄存器上下文(由各個寄存器組成)、系統級上下文(PCB、核心棧等)。
(2)進程切換過程
  ①進程被從堆棧調度到CPU運行;
  ②進程被從CPU調度到堆棧暫停運行。

四、進程控制的概念

1.進程控制的概念

  在進程生存期間,對其全部行為的控制。典型的控制行為有創建進程、阻塞進程、喚醒進程、撤銷進程。

2.進程控制行為之一——進程創建

(1)功能
  創建一個具有指定標識的進程。
(2)參數
  進程標識、優先級、進程起始地址、CPU初始狀態、資源需求等等。
(3)創建進程的過程
  ①創建一個空白PCB;
  ②獲得並賦予進程標識符ID;
  ③為進程分配空間;
  ④初始化PCB;
  ⑤把進程插入到相應的進程隊列,一般放到就緒隊列中。

3.進程控制行為之二——進程撤銷

(1)功能
  撤銷一個指定的進程,收回進程所占用的資源,撤銷該進程的PCB。
(2)進程撤銷的時機/時間
  ①正常結束;
  ②異常結束;
  ③外界因素。
(3)參數
  被撤銷的進程名(ID)。
(4)進程撤銷的過程
  ①在PCB隊列中找到要撤銷進程的PCB;
  ②獲取該進程的狀態;
  ③若該進程處於運行態,則立即終止該進程(先檢查該進程是否有子進程,若有子進程,則先撤銷子進程,遞歸執行此操作);
  ④釋放進程占有的資源;
  ⑤將進程的PCB從PCB隊列中移除。

4.進程控制行為之三——進程阻塞

(1)功能
  阻塞進程的執行。
(2)阻塞的時機/事件
  ①請求系統服務:操作系統不能立即滿足進程的請求,導致進程不能滿足運行條件;
  ②啟動某種操作:進程啟動某操作,阻塞等待該操作完成(比如進行I/O操作);
  ③新數據尚未到達:該進程要獲得另外一個進程的中間結果,該進程需要等待另一個進程運算結束;
  ④進程正常結束:進程完成任務后,自我阻塞,等待新任務到達。
(3)參數
  阻塞原因。
(4)進程阻塞的實現
  ①停止運行;
  ②將PCB“運行態”改為“阻塞態”;
  ③出入相應原因的阻塞隊列;
  ④轉到調度程序(把系統控制權交給調度模塊)。

5.進程控制行為之四——進程喚醒

(1)功能
  喚醒處於阻塞隊列中的某個進程。
(2)引起喚醒的時機/事件
  ①系統服務滿足進程要求;
  ②I/O事件完成;
  ③新數據到達;
  ④進程向系統或I/O提出新請求(服務)。
(3)參數
  進程ID。

6.進程控制原語

  由若干指令構成的具有特定功能的函數,具有原子性,其操作不可分割。以上四種進程控制行為(創建、撤銷、阻塞、喚醒)都被封裝為原語,以保證其運行的完整性。

五、Linux下的進程控制

1.創建進程

  創建進程的函數原型是pid_t fork(void);
  pid_t是一個整數類型,即fork()函數會返回新進程的ID號(0~32768的整數)。
  例如:pid_t pid = fork();

注意:
(1)新進程是當前進程的子進程。
(2)父進程和子進程
  ①父進程:fork()的調用者;
  ②子進程:新建的進程。
(3)子進程是父進程的復制(相同的代碼,相同的數據,相同的堆棧),除了ID號和時間信息外,兩者完全相同。
(4)子進程和父進程可以並發運行。

請問下面的程序會輸出什么結果?

//文件名為test.c
int main(void)
{
    fork();
    printf("Hello World!\n");

    return 0;
}

 

答案:

  

  一個是子進程輸出的,一個是父進程輸出的。

請問下面的程序會輸出什么結果?

//文件名為fork_2.c
int main(void)
{
    pid_t pid;
    pid = fork();
    
    if(pid == 0)
    {
        printf("pid == 0\n");
    }
    else
    {
        printf("pid != 0\n");
    }

    return 0;
}

 

答案:
  

  為什么if和else兩個分支都被執行了呢?

  因為fork()函數執行后,創建了一個新的進程,子進程是父進程的復制,所以相同的代碼會執行兩次。在子進程中fork()函數會返回0,在父進程中,fork()函數會返回子進程的id號。所以在子進程中,會執行if分支,在父進程中,會執行else分支。但誰先誰后不確定。

2.fork()函數的執行步驟

  由於子進程是父進程的復制,所以子進程中也會有創建子進程的語句,如果不加以限制,就會形成遞歸創建,但實際上並不是這樣的。
  實際流程是:父進程創建了子進程后,子進程中“創建進程”語句及其前面的語句都不再執行。並發運行的是fork()語句后的語句。

   

 

在linux的源碼中我們可以找到fork函數:

//linux內核中的fork函數
...
copy_files(clone_flags,p);    //克隆文件
copy_fs(clone_flags,p);    //克隆文件系統
copy_mm(clone_flags,p);    //克隆內存信息
...

  我們可以看到有三條語句,用於拷貝進程的所有信息,這也解釋了為什么說子進程是父進程的復制。

3.子進程如何執行與父進程不同的功能。

  Linux中 ,init進程(初始化進程)是所有其他進程的父進程,那么是不是就說所有的進程都執行與init進程相同的功能呢?並不是。Linux中某些子進程和父進程的執行並不是完全相同的。它是如何做到的呢?這是因為exec函數簇的存在,它是若干函數的集合。其功能是讓子進程具有和父進程完全不同的新功能。

4.一個進程控制的實例

  編寫一個代碼,創建兩個子進程,第一個子進程打印“I am the 1st subprocess!”,第二個子進程打印“I am the 2rd subprocess”,父進程打印“I am the parent process”。要求實現先打印第一個子進程的內容,再打印第二個子進程的內容,最后打印父進程的內容。

 1 int main()
 2 {
 3   int p1, p2; //進程ID
 4  
 5   p1 = fork();
 6   switch(p1)
 7   {
 8     case -1:  //進程創建失敗時執行下列語句,因為進程創建失敗fork函數會返回-1
 9         printf("Error\n");  
10         exit(1);
11     case 0:  //子進程中執行下列語句,因為在子進程中fork函數會返回0
12         execl("/bin/echo","echo", "I am the 1st subprocess!", NULL);  //使用exec函數簇中的execl函數將子進程一的功能進行改造
13         exit(1);
14     default:  //父進程中執行下列語句,因為在父進程中fork函數會返回子進程的ID號(大於0,小於32768)
15         wait(NULL);  //用於進程同步,wait函數的作用是:父進程在此處暫停運行,等待一個子進程結束后,再從此處繼續向下運行
16         break;
17   }
18  
19   p2 = fork();
20   switch(p2)
21   {
22     case -1:  //進程創建失敗時執行下列語句,因為進程創建失敗fork函數會返回-1
23         printf("Error\n");
24         exit(1);
25     case 0:  //子進程中執行下列語句,因為在子進程中fork函數會返回0
26         execl("/bin/echo", "echo", "I am the 2rd subprocess", NULL);  //使用exec函數簇中的execl函數將子進程二的功能進行改造
27         exit(1);
28     default:  //父進程中執行下列語句,因為在父進程中fork函數會返回子進程的ID號(大於0,小於32768)
29         wait(NULL);  //用於進程同步,wait函數的作用是:父進程在此處暫停運行,等待一個子進程結束后,再從此處繼續向下運行
30         break;
31   }
32  
33   printf("I am the parent process\n");
34  
35   return 0;
36 }

 

運行結果:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM