Linux程序(一)

2022-10-22 12:00:07

作業系統

概念:作業系統是管理計算機硬體與軟體資源的計算機程式,簡稱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;
    }
  }
  
}

執行結果如下: