Linux進程(一)

作業系統

概念:作業系統是管理電腦硬體與軟體資源的電腦程式,簡稱OS。

為什麼要有作業系統

1.給用戶提供穩定、高效和安全的運行環境,為程式設計師提供各種基本功能(OS不信任任何用戶,不讓用戶或者程式設計師直接與硬體進行交互)。

2.管理好各種軟硬體資源。

從這張圖我們可以看到幾點內容:

  1. OS管理的硬體部分: 網卡、硬碟等
  2. OS管理的軟體部分: 記憶體管理、驅動管理、進程管理和文件管理,還有驅動和系統調用介面

進程

進程和程式的概念

我們平時所寫的C語言程式碼,通過編譯器的編譯,最終會成為一個可執行的程式,當這個可執行程式運行起來之後,它就變成了一個進程。

程式是存放在存儲介質(程式平時都存放在磁碟當中)上的一個可執行文件,而進程就是程式執行的過程。進程的狀態是變化的,其中包括進程的創建、調度和死亡。程式是靜態的,進程是動態的。

進程: 電腦中的程式關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是作業系統結構的基礎。

如何描述進程

  • 進程的所有屬性資訊都被放在一個叫做進程式控制制塊的結構體中,可以理解為進程屬性的集合。
  • 這個數據結構的英文名稱是PCB(process control block),在Linux的OS下的PCB是task_struct(Linux內核中的一種數據結構,它會被裝載到RAM(記憶體)中並且包含並包含進程的資訊)。

task_struct內容有哪些?

  • 標識符:描述本進程的唯一標識符(就像是我們每個人的身份證)。

  • 狀態:任務狀態、退出程式碼、退出訊號等。

  • 優先順序: 程式被CPU執行的順序(後面會單獨介紹)。

  • 程式計數器: 一個暫存器中存放了一個pc指針,這個指針永遠指向即將被執行的下一條指令的地址。

  • 記憶體指針: 包含程式程式碼和進程相關的數據的指針,還有和其它進程共享的記憶體快的指針。這樣就可以PCB找到進程的實體。

  • 上下文數據: 在單核CPU中,進程需要在運行隊列(run_queue) 中排隊,等待CPU調度,每個進程在CPU中執行時間是在一個時間片內的,時間片到了,就要從CPU上下來,繼續去運行隊列中排隊。

  • I/O狀態資訊: 包括顯示的I/O請求,分配給進程的I/ O設備和被進程使用的文件列表。

  • 記賬資訊: 能包括處理器時間總和,使用的時鐘數總和,時間限制,記帳號等。

    組織進程
    在內核源程式碼中發現,所有運行在系統里的進程都以task_struct鏈表形式存在內核中。

進程的狀態

進程的狀態反應進程執行過程的變化。這些狀態隨著進程的執行和外界的變化而轉換。

五態模型中,進程分為新建態,終止態,運行態,就緒態,就緒態。

3

(1)TASK_RUNNING(運行態):進程正在被CPU執行。當一個進程被創建的時候會處於TASK_RUNNABLE,表示已經準備就緒,正在準備被調度。

(2)TASK_INTERRUPTIBLE(可中斷狀態):進程正在睡眠(阻塞)等待某些條件的達成。一旦這些條件達成,內核就會把進程狀態設置成運行態。處於此狀態的進程也會因為接收到訊號而提前被喚醒,比如給一個TASK_INTERRUPTIBLE狀態的進程發送SIGKILL訊號,這個進程將會被先喚醒(進入TASK_RUNNABLE狀態),然後再響應SIGKILL訊號而退出(變為TASK_ZOMBIE狀態),並不會從TASK_INTERRUPTIBLE狀態直接退出。

(3)TASK_UNINTERRUPTIBLE(不可中斷):處於等待中的進程,待資源被滿足的時候被喚醒,但是不可以由其他進程通過訊號或者中斷喚醒。由於不接受外來的任何訊號,因此無法用KILL殺掉這些處於該狀態的進程。而TASK_UNINTERRUPTIBLE狀態存在的意義就在於,內核的某些處理流程是不能被打斷的。

(4)TASK_ZOMBIE(僵死):表示進程已經結束,但是其父進程還沒有回收子進程的資源。為了父進程能夠獲知它的消息,子進程的進程描述符仍然被保留著。一旦父進程調用wait函數釋放子進程的資源,子進程的進程描述符就會被釋放。

(5)TASK_STOPPED(停止):進程停止執行。當進程接收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等訊號的時候。此外,在調試期間接收到任何訊號,都會使進程進入這種狀態。當接收到SIGCONT訊號,會重新回到TASK_RUNNABLE狀態。

下面是進程狀態在源碼中的定義:

static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

查看進程狀態相關的命令

ps命令可以查看進程詳細的狀態,常用選項如下:

選項 含義
-a 顯示終端上的所有進程,包括其他進程
-u 顯示進程的詳細狀態
-x 顯示沒有控制終端的進程
-w 顯示加寬,以便顯示更多的資訊
-r 只顯示正在運行的進程

PID就是進程的進程號,STAT是進程此時處於什麼狀態。

有下面兩種命令(前者查看所用進程的名字,後者可以查看進程的父子關係):

ps aux/ps axj

進程號和相關函數

每個進程都有一個進程號來標識,其類型為pid_t(整型)。進程號是唯一的,但是進程號是可以重用的。當一個進程終止後,其進程號可以再次使用。

進程號(PID)

getpid()可以獲取當前進程的進程號。

父進程號(PPID)

getppid()可以獲取當前進程的父進程號

進程組號(PGID)

getpgid()可以獲取當前進程進程組號

進程創建

fork函數(系統調用)

pid_t fork(void);

功能:通過複製當前進程,為當前進程創建一個子進程

返回值:成功:子進程中返回0,父進程中返回子進程的pid_t。

​ 失敗:返回-1。

進程調用fork函數,內核需要做什麼

  • 給子進程分配記憶體空間,並為子進程創建PCB
  • 將父進程部分數據結構內容(還有程式碼和數據暫時共享)拷貝至子進程
  • 添加子進程到系統進程列表(運行隊列)當中
  • fork返回,開始CPU調度器調度

fork之後執行什麼?

父子進程共享一份程式碼,fork之後,一起執行fork之後的程式碼,且二者之間是獨立的,不會相互影響。

父進程絕大部門東西都被子進程繼承,程式碼也是,但是在執行的過程中,父進程的PCB中存在一個pc指針,記錄著下一條指定的地址,當父進程執行到fork的時候,pc指針也只想fork的下一條指令,子進程也繼承了pc指針的虛擬地址,本來子進程全部繼承了父親的共享程式碼,但是此時pc也是指向fork的下一條指令,所以父子進程都從fork之後開始執行。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
  pid_t ret = fork();
  
  if (ret < 0)
  {
    perror("fork");
    return 1;
  }
  else if (ret == 0)// 子進程
  {
    printf("I am child-pid:%d, ppid:%d\n", getpid(), getppid());
    sleep(1);
  }
  else if (ret > 0)// 父進程
  {
    printf("I am parent-pid:%d, ppid:%d\n", getpid(), getppid());
    sleep(1);
  }

  sleep(1);

  return 0;
}

父子進程關係

使用fork函數得到的子進程是父進程的一個複製品,每個進程都有自己的進程式控制制塊PCB,再這個PCB中子進程從父進程中繼承了整個進程的地址空間:包括進程上下文,進程堆棧,打開的文件描述符,資訊控制設定,進程優先順序,進程組號等等,但是進程的地址空間都是虛擬空間,子進程PCB繼承的都是虛擬地址。

寫時拷貝

通常情況下,父子進程共享一份程式碼,並且數據都是共享的,當任意一方試圖寫入更改數據的時候,那麼這一份便要以寫時拷貝的方式各自私有一份副本。

從圖中可以看出,發生寫時拷貝後,修改方將改變頁表中對該份數據的映射關係,父子進程各自私有那一份數據,且許可權由只讀變成了只寫,虛擬地址沒有改變,改變的是物理記憶體頁的物理地址。(涉及到虛擬地址,可以看我上面發的文章)

問題思考

1.為什麼程式碼要共享?

程式碼是不可以被修改的,所以各自私有很浪費空間,大多數情況下是共享的,但要注意的是,程式碼在特殊情況下也是會發生寫時拷貝的,也就是進程的程式替換(後面會單獨介紹)。

2.寫實拷貝的作用?

  • 可以減少空間的浪費,在雙方都不對數據或程式碼進行修改的情況下,各自私有一根數據和程式碼是浪費空間的。
  • 維護進程之間的獨立性,雖然父子進程共享一份數據,但是父子中有一方對數據進行修改,那麼久拷貝該份數據到給修改方,改變修改方中頁表對這份數據的映射關係,然後對數據進行修改,這樣不管哪一方對數據進行修改都不會影響另一方,這樣就做到了獨立性。

3.寫時拷貝是對所有數據進行拷貝嗎?

答案是否定的。如果沒有修改的數據進行拷貝,那麼這樣還是會造成空間浪費的,沒有被修改的數據還是可以共享的,我們只需要將修改的那份數據進行寫時拷貝即可。

理論還是太枯燥,上程式碼!

程式碼1:棧區局部變數

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
   int var = 88;
   //創建一個子進程
   pid_t ret = fork();
   if (ret < 0)
    {
        perror("fork");
        return 1;
    }
   else if (ret == 0)// 子進程
    {
        sleep(1);
        printf("I am child-pid:%d, ppid:%d\n", getpid(), getppid());
        printf("子進程睡醒之後 var = %d\n",var);
    }
        else if (ret > 0)// 父進程
   {
        printf("I am parent-pid:%d, ppid:%d\n", getpid(), getppid());
        printf("父進程之前 var =%d\n", var);
        var++;
        printf("父進程之後 var =%d\n", var);
   }
        sleep(1);
        return 0; 
 }

運行結果:

讀時共享,寫時拷貝。這裡的父進程一開始時共享var的數據給子進程,但是此時子進程睡了一秒,就執行父進程,父進程中var的值被改變,此時寫時拷貝,var會拷貝一份到子進程當中,所以父進程修改var的值不會影響到子進程中var的值。這裡的局部變數在棧區。

程式碼2:全局變數

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int var = 88;
int main()
{
   //創建一個子進程
   pid_t ret = fork();
   if (ret < 0)
    {
        perror("fork");
        return 1;
    }
   else if (ret == 0)// 子進程
    {
        sleep(1);
        printf("I am child-pid:%d, ppid:%d\n", getpid(), getppid());
        printf("子進程睡醒之後 var = %d\n",var);
    }
        else if (ret > 0)// 父進程
   {
        printf("I am parent-pid:%d, ppid:%d\n", getpid(), getppid());
        printf("父進程之前 var =%d\n", var);
        var++;
        printf("父進程之後 var =%d\n", var);
   }
        sleep(1);
        return 0; 
 }

運行結果:

子進程var值也不會受到影響,遵循讀時共享,寫時拷貝的原則。

總結:

  • 父子進程由獨立的數據段、堆、棧、共享程式碼段(每個進程都有屬於自己的PCB)。

  • Linux中每個進程都有4G的虛擬地址空間(獨立的3G用戶空間和共享的1G內核空間),fork創建的子進程也不例外。

    (1)1G內核空間既然是所有進程共享,因此fork創建的子進程自然也將有用;

    (2)3G的用戶空間是從父進程而來。

  • fork創建子進程時繼承了父進程的數據段、程式碼段、棧、堆,值得注意的是父進程繼承來的是虛擬地址空間,進程上下文,打開的文件描述符,資訊控制設定,進程優先順序,進程組號,同時也複製了頁表(沒有複製物理塊)。因此,此時父子進程擁有相同的虛擬空間,映射的物理記憶體也是一致的。(獨立的虛擬地址空間,共享父進程的物理記憶體)。

  • 由於父進程和子進程共享物理頁面,內核將其標記為「只讀」,父子雙方均無法對其修改。無論父子進程嘗試對共享的頁面執行寫操作,就產生一個錯誤,這時內核就把這個頁複製到一個新的頁面給這個進程,並把原來的只讀頁面標誌為可寫,留給另外一個進程使用—-寫時複製技術。

  • 內核在子進程分配物理記憶體的時候,並沒有將程式碼段對應的數據另外複製一份給子進程,最終父子進程映射的時同一塊物理記憶體。

進程終止

可以通過echo$?查看進程退出碼

exit函數和return函數的區別

  • main函數結束的時候也會隱式的調用exit函數。exit函數運行的時候首先會執行由atexit()函數登記的函數,然後會做一些自身的清理工作,同時刷新所有的輸出流,關閉所有打開的流並且關閉通過標準IO函數創建的臨時文件。
  • exit時結束一個進程,他將刪除進程使用的記憶體空間,同時把錯誤資訊返回父進程;而return是返回函數值(return所在的函數框內)並且退出函數。通常情況:exit(0)表示程式正常, exit(1)和exit(-1)表示程式異常退出,exit(2)表示表示系統找不到指定的文件。在整個程式中,只要調用exit就結束(當前進程或者在main時候為整個程式)。return也是如此,如圖return在main函數中,那麼結束的就是整個進程。return是函數的結束,exit是進程的結束。
  • return是語言級別的,它表示了調用堆棧的返回return( )是當前函數返回,當然如果是在主函數main, 自然也就結束當前進程了,如果不是,那就是退回上一層調用。在多個進程時。如果有時要檢測上個進程是否正常退出。就要用到上個進程的返回值,依次類推。而exit是系統調用級別的,它表示了一個進程的結束
  • exit函數是退出應用程式,並將應用程式的一個狀態返回給OS,這個狀態標識了應用程式的一些運行資訊。
  • 在main函數中exit(0)等價於return 0。

1.return函數返回退出碼

main函數退出的時候,return的返回值就是進程的退出碼。0在函數的設計中,一般代表是正確而非0就是錯誤。

2.調用exit函數

void exit(int status);

功能:結束當前正在執行的進程。

參數:返回給父進程的參數,根據需要填寫。

在任意位置調用,都會使得進程退出,調用之後會執行執行用戶通過 atexit或on_exit定義的清理函數,還會 關閉所有打開的流,所有的快取數據均被寫入。

int main()
{
  cout << "12345";
  sleep(3);
  exit(0);// 退出進程前前會執行用戶定義的清理函數,且刷新緩衝區
  return 0;
}//輸出12345

3.調用_exit函數

exit()和_exit()函數功能和用法都是一樣的,但是區別就在於exit()函數是標準庫函數,而__exit函數是系統調用。

在Linux的標準函數庫中,有一套稱做「高級I/O」的函數,我們熟知的printf(),fopen(),fread(),fwrite()都在此列,它們也被稱作緩衝IO (buffered IO)",其特徵是對應每一個打開的文件,在記憶體中都有一片緩衝區,每次讀文件時,會多讀出若干條記錄,這樣下次讀文件時就可以直接從記憶體的緩衝區中讀取,每次寫文件的時候,也僅僅是寫入記憶體中的緩衝區,等滿足了一定的條件(達到一定數量,或遇到特定字元,如換行符\n和文件結束EOF),再將緩衝區中的內容一次性寫入文件,這樣就大大增加了文件讀寫的速度,但也為我們編程帶來了一點點麻煩。如果有一些數據,我們認為已經寫入了文件,實際上因為沒有滿足特定的條件,它們還只是保存在緩衝區內,這時我們用_exit()函數直接將進程關閉,緩衝區中的數據就會丟失,反之,如果想保證數據的完整性,就一定要使用exit()函數。
  • exit()作為庫函數,封裝的比較完善,exit將終止調用的進程,在退出程式之前,所有文件關閉,緩衝區刷新(輸出內容),將刷新定義,並且調用所有已刷新的「出口函數」,在執行完清理工作之後,會調用_exit來終止進程。
  • _exit()調用,但是不關閉文件,不刷新緩衝區,也不調用出口函數。

int main()
{
  cout << "12345";
  sleep(3);
   _exit(0);// 直接退出進程,不刷新緩衝區
  return 0;
}//不輸出12345

4.異常終止

  • ctrl+C終止前台進程
  • kill發生9號訊號殺死進程

進程等待

進程等待的必要性:

在每個進程退出的時候,內核釋放該進程所有的資源、包括打開的文件、佔用的記憶體等,這就是在執行exit時候執行的工作。但是仍然會保留一定的資訊,這些資訊主要指的是進程式控制制塊PCB的資訊(包括進程號,退出狀態,運行事件等),而這些資訊需要父進程調用wait或者waitpid函數得到他的退出狀態同時徹底清理掉這個進程殘留的資訊。

  • 子進程必須要比父進程先退出,否則會變成孤兒孤兒進程
  • 父進程必須讀取子進程的退出狀態,回收子進程的資源。如果父進程不讀取子進程退出狀態,還不會釋放子進程資源,那麼子進程將處於僵死狀態,會造成記憶體泄漏
  • 父進程派給子進程的任務完成的如何,得知子進程執行結果

wait方法

*pid_wait(int status);

功能:等待任意一個子進程結束,如果任意一個子進程結束了,此函數會回收子進程的資源。

參數:status進程退出時候的狀態。

返回值:成功:返回結束子進程的進程號。失敗:-1.

注意以下幾點:

  • 調用wait會阻塞當前的進程,直到任意一個子進程退出或者收到一個不能忽視的訊號才能被喚醒。
  • 若調用進程沒有子進程,該函數立刻返回;若它的子進程已經結束,該函數同樣會立刻返回,並且會回收那個早已經結束進程的資源。
  • 如果參數status的值不是NULL,wait就會把子進程退出時候的狀態取出來並存入,這是一個整數值,指出了子進程是正常退出還是被非正常結束。

演示:

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
	pid_t ret= fork();
	if (ret< 0){
	  cerr << "fork error" << endl;
	}
	else if (ret== 0){
	  // child
	  int count = 5;
	  while (count){
		printf("child[%d]:I am running... count:%d\n", getpid(), count--);
		sleep(1);
	  }
	  exit(1);
	}
	// parent
	printf("father begins waiting...\n");
	sleep(10);
	pid_t id = wait(NULL);// 不關心子進程退出狀態
	
	printf("father finish waiting...\n");
	if (id > 0){ 
	  printf("child success exited\n"); 
	} else{
	  printf("child exit failed\n"); 
	} 
	//父進程再活5秒 
	sleep(5);
	return 0;
}

由運行結果可以看出,父進程一隻等待子進程結束,等待的時候子進程變成殭屍進程,等父進程徹底釋放資源,子進程的狀態由殭屍變成死亡狀態。

waitpid方法

*pid_t waitpid(pid_t pid, int status , int options);

功能:等待子進程結束,如果子進程終止,此函數就會回收子進程資源。

參數

pid:參數pid有以下幾種類型:

​ pid>0 等待進程ID等於pid的子進程結束。

​ pid=0 等待同一個進程組中的任何子進程,如果子進程已經進入了別的進程組,waitpid不會等待它。

​ pid=-1 等待任意子進程,此時waitpid和wait的作用是一樣的。

​ pid<-1 等待指定進程組中的任何子進程,這個進程組的ID等於pid的絕對值。

options:options提供了一些額外的選項來控制waitpid()

​ 0:通wait(),阻塞父進程,等待子進程退出。

​ WNOHANG: 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該子進程的ID(可以進行基於阻塞等待的輪詢訪問)。

​ WUNTRACED:如果子進程暫停了此函數立馬返回,並且不予理會子進程的結束狀態(很少調用)。

返回值:

​ waitpid有三種情況:

(1)正常返回的時候,waitpid返回收集到的已回收子進程的進程的進程號。

(2)如果設置了WNOHANG,而調用中發現了沒有已經退出的子進程可以等待,返回0。

(3)如果調用中出錯,返回-1,此時errno會被設置成相應的值來指示錯誤所在。

程式碼示例:

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
	pid_t ret= fork();
	if (ret< 0){
	  cerr << "fork error" << endl;
	}
	else if (ret== 0){
	  // child
	  int count = 5;
	  while (count){
		printf("child[%d]:I am running... count:%d\n", getpid(), count--);
		sleep(1);
	  }
	  exit(1);
	}
	// parent
	printf("father begins waiting...\n");
	sleep(10);
	pid_t id = waitpid(-1, NULL, 0);// 不關心子進程退出狀態,以阻塞方式等待
	
	printf("father finish waiting...\n");
	if (id > 0){ 
	  printf("child success exited\n"); 
	} else{
	  printf("child exit failed\n"); 
	} 
	//父進程再活5秒 
	sleep(5);
	return 0;
}

獲取子進程的status

  • wait和waitpid中都有一個status參數,該參數是一個輸出型參數,由作業系統來填充
  • 如果該參數給NULL,那麼代表不關心子進程的退出資訊

status的幾種狀態:(我們只研究status的低16位)

看圖可以知道,低7位代表的是終止訊號,第8位時core dump標誌,高八位是進程退出碼(只有正常退出是這個退出碼才有意義)
status的0-6位和8-15位有不同的意義。我們要先讀取低7位的內容,如果是0,說明進程正常退出,那就獲取高8位的內容,也就是進程退出碼;如果不是0,那就說明進程是異常退出,此時不需要獲取高八位的內容,此時的退出碼是沒有意義的。

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
	pid_t ret = fork();
	if (ret < 0){
	  cerr << "fork error" << endl;
	}
	else if (ret == 0){
	  // child
	  int count = 5;
	  while (count){
	    printf("child[%d]:I am running... count:%d\n", getpid(), count--);
	    sleep(1);
	  }
	
	  exit(1);
	}
	// parent
	printf("father begins waiting...\n");
	
	int status;
	pid_t id = wait(&status);// 從status中獲取子進程退出的狀態資訊
	printf("father finish waiting...\n");
	
	if (id > 0 && (status&0x7f) == 0){
	  // 正常退出
	  printf("child success exited, exit code is:%d\n", (status>>8)&0xff);
	}
	else if (id > 0){
	  // 異常退出
	  printf("child exit failed,core dump is:%d,exit singal is:%d\n", (status&(1<<7)), status&0x7f);
	}
	else{
	  printf("father wait failed\n");
	}
	if (id > 0){ 
	  printf("child success exited\n"); 
	} else{
	  printf("child exit failed\n"); 
	} 
 	return 0;
}

運行結果如下:

阻塞等待和非阻塞等待

操控者: 作業系統
阻塞的本質: 父進程從運行隊列放入到了等待隊列,也就是把父進程的PCB由R狀態變成S狀態,這段時間不可被CPU調度器調度
等待結束的本質: 父進程從等待隊列放入到了運行隊列,也就是把父進程的PCB由S狀態變成R狀態,可以由CPU調度器調度

阻塞等待: 父進程一直等待子進程退出,期間不幹任何事情

示例:

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
  pid_t id = fork();
  if (id < 0){
    cerr << "fork error" << endl;
  }
  else if (id == 0){
    // child
    int count = 5;
    while (count){
      printf("child[%d]:I am running... count:%d\n", getpid(), count--);
      sleep(1);
    }
    exit(0);
  }
  
  // 阻塞等待
  // parent
  printf("father begins waiting...\n");
  int status;
  pid_t ret = waitpid(id, &status, 0);
  printf("father finish waiting...\n");

  if (id > 0 && WIFEXITED(status)){
    // 正常退出
    printf("child success exited, exit code is:%d\n", WEXITSTATUS(status));
  }
  else if (id > 0){
    // 異常退出
    printf("child exit failed,core dump is:%d,exit singal is:%d\n", (status&(1<<7)), status&0x7f);
  }
  else{
    printf("father wait failed\n");
  }
}

運行結果如下:

14

非阻塞等待: 父進程不斷檢測子進程的退出狀態,期間會幹其他事情(基於阻塞的輪詢等待)

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
  pid_t id = fork();
  if (id < 0){
    cerr << "fork error" << endl;
  }
  else if (id == 0){
    // child
    int count = 5;
    while (count){
      printf("child[%d]:I am running... count:%d\n", getpid(), count--);
      sleep(1);
    }
    exit(0);
  }
  // 基於阻塞的輪詢等待
  // parent
  while (1){
    int status;
    pid_t ret = waitpid(-1, &status, WNOHANG);
    if (ret == 0){
      // 子進程還未結束
      printf("father is running...\n");
      sleep(1);
    }
    else if (ret > 0){
      // 子進程退出
      if (WIFEXITED(status)){
        // 正常退出
        printf("child success exited, exit code is:%d\n", WEXITSTATUS(status));
      }
      else{
        // 異常退出
        printf("child exited error,exit singal is:%d", status&0x7f);
      }
      break;
    }
    else{
      printf("wait child failed\n");
      break;
    }
  }
  
}

運行結果如下: