fork函數和wait/waitpid函數

複製一個進程映象fork

使用fork函數得到的子進程從父進程的繼承了整個進程的地址空間,包括:進程上下文、進程堆棧、內存信息、打開的文件描述符、信號控制設置、進程優先級、進程組號、當前工作目錄、根目錄、資源限制、控制終端等。

子進程與父進程的區別在於:

1、父進程設置的鎖,子進程不繼承

2、各自的進程ID和父進程ID不同

3、子進程的未決告警被清除;

4、子進程的未決信號集設置爲空集。


fork系統調用

包含頭文件 <sys/types.h> <unistd.h>

函數功能:

創建一個子進程

函數原型

         pid_t  fork(void);

參數:無參數。

返回值:

如果成功創建一個子進程,對於父進程來說返回子進程ID

如果成功創建一個子進程,對於子進程來說返回值爲0

如果爲-1表示創建失敗

 

代碼示例

 

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

pid_t pid;

char *message;

int n;

pid = fork();//在這裏fork調用一次,返回2次  

if (pid < 0)

{

perror("fork failed");

exit(1);

}

if (pid == 0)//子進程開始執行

{

message = "This is the child\n";

n = 6;

}

  else//父進程開始執行

{

message = "This is the parent\n";

n = 3;

}

//子進程打印了6次父進程打印了3次

for(; n > 0; n--)

{

printf(message);

sleep(1);

}

return 0;

}

 

打印的信息

./a

This is the parent

This is the child

This is the parent

This is the child

This is the parent

This is the child

This is the child

This is the child

This is the child

 


現在我們已經理解fork系統調用了,但是我們想獲得該進程的id號 則需要使用下列函數

#include <sys/types.h>

#include <unistd.h>

pid_t getpid(void); //返回調用進程的PID號

pid_t getppid(void); //返回調用進程父進程的PID號

案例

#include <stdio.h>

#include <unistd.h>

 

int main()

{

pid_t pid;

printf("before fork()\n");


pid = fork();

if (pid == 0)

{//打印子進程id號

printf("this is child, pid = %d\n", getpid());

}

else if (pid > 0)

{//打印父進程id號

printf("this parent, ppid = %d\n", getppid());

}

 

return 0;

}

 

打印結果:

before fork()

this parent, ppid = 2737

this is child, pid = 4790

注意

你不能預計父進程是在它的子進程之前還是之後運行,它的執行是無序的,是異步的。

fork的異步行爲意味着你不應該在子進程中執行依賴與父進程的代碼,反之亦然。

fork調用可能失敗,原因是系統上已經運行了太多進程,已經超過了允許它執行的最大進程數。

fork執行失敗,會向父進程返回-1,而且不創建子進程。

 

Wait函數解釋

我們用fork啓動一個進程時,子進程就有了自己的生命,並將獨立地運行。有時,我們需要知道某個子進程是否已經結束了,我們可以通過wait函數安排父進程在子進程之後結束

 

Wait函數

頭文件<sys/types.h><sys/wait.h>

函數原型

pid_t wait(int *status);

函數參數

status:該參數可以獲得你等待子進程的信息

返回值:

成功等待子進程函數返回等待子進程的ID

wait系統調用會使父進程暫停執行,直到它的一個子進程結束爲止。返回的是子進程的PID,它通常是結束的子進程狀態信息允許父進程判定子進程的退出狀態,即從子進程的main函數返回的值或子進程中exit語句的退出碼。如果status不是一個空指針,狀態信息將被寫入它指向的位置

 

Wait獲取status後檢測處理

宏定義 描述

WIFEXITED(status) 如果子進程正常結束,返回一個非零值

WEXITSTATUS(status) 如果WIFEXITED非零,返回子進程退出碼

WIFSIGNALED(status) 子進程因爲捕獲信號而終止,返回非零值

WTERMSIG(status) 如果WIFSIGNALED非零,返回信號代碼

WIFSTOPPED(status) 如果子進程被暫停,返回一個非零值

WSTOPSIG(status) 如果WIFSTOPPED非零,返回一個信號代碼

 

案例

通過這個小例子可以捕獲子進程的返回值

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

 

int main(int arg, char *args[])

{

pid_t pid = fork();

int status;

if (pid == -1)

{

printf("fork failed\n");

return 0;

}

if (pid == 0)

{

printf("child pid = %d\n", getpid());//打印子進程的pid

printf("child process start\n");

sleep(2);

printf("child process end\n");

return 11;

}

else

{

printf("parent process start\n");

pid_t child = wait(&status);//調用wait函數阻塞等待子進程結束纔開始執行下面的代碼

printf("child = %d\n", child);//通過wait函數的返回值打印子進程的pid

printf("status = %d\n", WEXITSTATUS(status));//通過這個宏WEXITSTATUS可以得到子進程的返回值

printf("parent process end\n");


return 0;

}

}

測試結果如下:

parent process start

child pid = 5629

child process start

child process end

child = 5629

status = 11

parent process end

 

Waitpid函數解釋

函數功能:

用來等待某個特定進程的結束

函數原型:

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

 參數:

status:如果不是空,會把狀態信息寫到它指向的位置

options:允許改變waitpid的行爲,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起

返回值:如果成功返回等待子進程的ID,失敗返回-1

 

對於waitpid的pid參數的解釋與其值有關:

pid == -1 等待任一子進程。於是在這一功能方面waitpid與wait等效。

pid > 0 等待其進程I D與pid相等的子進程。

pid ==0 等待其組I D等於調用進程的組I D的任一子進程。換句話說是與調用者進程同在一個組的進程。

pid < -1 等待其組I D等於pid的絕對值的任一子進程。

 

案例

#include <sys/wait.h>

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

 

int main(int arg, char *args[])

{

pid_t pid = fork();

int status;

if (pid == -1)

{

printf("fork failed\n");

return 0;

}

if (pid == 0)

{

printf("child process start\n");

printf("child pid = %d\n", getpid());

sleep(2);

printf("child process end\n");

return 10;

}

else

{

printf("parent process start\n");

pid_t chil = waitpid(pid, &status, 0);

printf("chil = %d\n", chil);

printf("status = %d\n", WEXITSTATUS(status));

printf("parent process end\n");

return 0;

}

 

return 0;

}

測試結果如下:

parent process start

child process start

child pid = 5869

child process end

chil = 5869

status = 10

parent process end

 

waittwaitpid區別和聯繫

在一個子進程終止前, wait 使其調用者阻塞,而waitpid 有一選擇項,可使調用者不阻塞。

waitpid並不等待第一個終止的子進程—它有若干個選擇項,可以控制它所等待的特定進程。

實際上wait函數是waitpid函數的一個特例。

 

什麼是僵死進程?

一個僵死進程是在父進程有機會用wait或者waitpid收集它退出狀態之前就終止的子程。之所以被稱爲僵死進程是因爲他雖然死掉了,但依然在進程表中存在。

子進程退出後分配給它的內存和其他資源都被釋放,但它還是在內核進程表中保留了一條,內核在父進程回收子進程的退出狀態前一直保留它。

有一兩個僵死進程不算什麼問題,但一旦一個程序頻繁執行fork或者exec卻又不能收集退出狀態,那麼最終將會填滿進程表,這會影響性能,可能導致系統重新啓動

 

什麼是孤兒進程(orphan process

孤兒進程是一個父進程在調用wait或者waitpid之前就已經退出的子進程。此時init進程成爲子進程的父進程。init進程爲子進程的父進程收集退出狀態,從而避免出現僵死進程。

 

下面這個例子可以查看到殭屍進程

#include <unistd.h>

#include <stdlib.h>

int main(void)

{

pid_t pid=fork();

if(pid<0)

{

perror("fork");

exit(1);

 }

if(pid>0)

{ //父進程一直執行死循環

while(1);

}

//子進程直接就退出了,此時就產生殭屍進程


return 0;

}

 

重新啓動一個終端在命令行下:ps -ajx

2737  5964  5964  2737 pts/4     5964 R+    1000   5:30 ./a

 5964  5965  5964  2737 pts/4     5964 Z+    1000   0:00 [a] <defunct>(殭屍進程)

 

通過上面的例子我們可以查看到殭屍進程,所以我們可以通過waitwaitpid2個函數來避免產生殭屍進程。當然啦,子進程正常或者異常終止,內核就會向其父進程發送一個SIGCHLD信號,在這裏就不做詳細解釋

 

 

 

 

 

 

 

 

發佈了54 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章