一、進程概念
引子 程序運行在並發環境中的問題
(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 }
運行結果: