Linux進程(一)
- 2022 年 10 月 22 日
- 筆記
- Linux高並發伺服器
作業系統
概念:作業系統是管理電腦硬體與軟體資源的電腦程式,簡稱OS。
為什麼要有作業系統:
1.給用戶提供穩定、高效和安全的運行環境,為程式設計師提供各種基本功能(OS不信任任何用戶,不讓用戶或者程式設計師直接與硬體進行交互)。
2.管理好各種軟硬體資源。
從這張圖我們可以看到幾點內容:
- OS管理的硬體部分: 網卡、硬碟等
- 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鏈表形式存在內核中。
進程的狀態
進程的狀態反應進程執行過程的變化。這些狀態隨著進程的執行和外界的變化而轉換。
五態模型中,進程分為新建態,終止態,運行態,就緒態,就緒態。
(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");
}
}
運行結果如下:
非阻塞等待: 父進程不斷檢測子進程的退出狀態,期間會幹其他事情(基於阻塞的輪詢等待)
#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;
}
}
}
運行結果如下: