淺談linux系統fork()函數


一、fork函數的使用

  fork()函數通過系統調用創建一個與原來進程幾乎完全相同的進程,也就是兩個進程可以做完全相同的事,但如果初始參數或者傳入的變量不同,兩個進程也可以做不同的事。
    一個進程調用fork()函數后,系統先給新的進程分配資源,例如存儲數據和代碼的空間。然后把原來的進程的所有值都復制到新的新進程中,只有少數值與原來的進程的值不同。相當於克隆了一個自己。

 

#include <stdio.h>
#include <unistd.h>

int main ()
{
int fpid;//the return of fork function
int count=0;

fpid=fork();
if (fpid < 0)
printf("error in fork!");
else if (fpid == 0) {
printf("i am the child process, my process id is %d\n",getpid());
count++;
}
else { //pid bigger than 0;
printf("i am the parent process, my process id is %d\n",getpid());
count++;
}
printf("result count: %d\n",count);
return 0;
}

返回結果為:

i am the child process, my process id is 7067
result count: 1
i am the parent process, my process id is 7066
result count: 1

 

 fork雖然僅被調用了一次,但是卻可能有兩次返回,它的返回值有三種:

(1)在父進程中,fork返回值大於零的子進程id。

(2)在子進程中,fork返回值為零。

(3)fork進程創建失敗時返回負值。

    fork出錯可能有兩種原因:
    1)當前的進程數已經達到了系統規定的上限,這時errno的值被設置為EAGAIN。
    2)系統內存不足,這時errno的值被設置為ENOMEM。
    創建新進程成功后,系統中出現兩個基本完全相同的進程,這兩個進程執行沒有固定的先后順序,哪個進程先執行要看系統的進程調度策略。

 

二、循環中的fork函數

#include <stdio.h>
#include <unistd.h>

int main ()
{
int fpid;
int i;

for(i = 0; i < 3; i++)
{
fpid = fork();
if(fpid>0)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);
}
else if(!fpid)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);
}
}

return 0;
}

運行結果:

 

loop 0: my process id is 7281, my father process id is 7280, fpid 0
loop 1: my process id is 7282, my father process id is 7281, fpid 0
loop 1: my process id is 7281, my father process id is 7280, fpid 7282
loop 0: my process id is 7280, my father process id is 6968, fpid 7281
loop 1: my process id is 7285, my father process id is 7280, fpid 0
loop 1: my process id is 7280, my father process id is 6968, fpid 7285
loop 2: my process id is 7287, my father process id is 7280, fpid 0
loop 2: my process id is 7280, my father process id is 6968, fpid 7287
[root@localhost threadTest]# loop 2: my process id is 7284, my father process id is 7281, fpid 0
loop 2: my process id is 7281, my father process id is 1, fpid 7284
loop 2: my process id is 7286, my father process id is 7285, fpid 0
loop 2: my process id is 7285, my father process id is 1, fpid 7286
loop 2: my process id is 7283, my father process id is 7282, fpid 0
loop 2: my process id is 7282, my father process id is 1, fpid 7283
 

 

 從結果可以看到當最上層的父進程死亡之后,未死亡的子進程會繼續運行到結束,但是此時他們的父進程id變為1。id為1的進程為init進程,init進程是系統中的一個特殊進程,通常程序文件為/sbin/init,在系統啟動時負責啟動各種系統服務,之后就負責清理子進程。只要有子進程終止,init就會調用wait函數清理它。

三、fork函數產生的死進程(僵屍進程)

  我們所運行的程序,並不是都會結束的,有些程序(如服務器等)需要它一直執行下去。這些程序中如果調用到fork函數就有可能產生“僵屍進程”。

 

#include <stdio.h>
#include <unistd.h>

int main ()
{
int fpid;
int i;

for(i;; i++)
{
fpid = fork();
if(fpid>0)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);
}
else if(!fpid)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);
exit(0);
}
sleep(10);
}

return 0;
}

運行10秒后用ps -e命令可以看到結果如下:

[root@localhost root]# ps -e | grep test
22928 pts/5    00:00:00 test
22929 pts/5    00:00:00 test <defunct>

 而且每隔10秒鍾,再運行一次ps -e命令,都可以看到多增加了一個test <defunct>進程。狀態欄為defunct的進程,就是所謂的“僵屍”進程。“僵屍進程”是一個已經死亡的進程,但是其在進程表中仍占了一個位置。因為進程表的容量有限,所以defunct進程不僅占用系統的內存資源,影響系統的性能,而且如果數量太多還會導致系統癱瘓。

當以fork()系統調用建立一個新的進程后,核心進程就會在進程表中給這個新進程分配一個進入點,然后將相關信息存儲在該進入點所對應的進程表內。這些信息中有一項是其父進程的識別碼。當這個進程走完了自己的生命周期后,它會執行exit()系統調用,此時原來進程表中的數據會被該進程的退出碼(exit code)、執行時所用的CPU時間等數據所取代,這些數據會一直保留到系統將它傳遞給它的父進程為止。由此可見,defunct進程的出現時間是在子進程終止后,但是父進程尚未讀取這些數據之前。

當父進程執行終止后,再用ps -e命令觀察時,我們會發現defunct進程也隨之消失。這是因為父進程終止后,init 進程會接管父進程留下的這些“孤兒進程”(orphan process),而這些“孤兒進程”執行完后,它在進程表中的進入點將被刪除。如果一個程序設計上有缺陷,就可能導致某個進程的父進程一直處於睡眠狀態或是陷入死循環,那么當該子進程執行結束后就變成了defunct進程,這個defunct 進程可能會一直留在系統中直到系統重新啟動。

僵屍進程無法調用kill清除,因為kill命令式用來終止進程的,而僵屍進程已經終止了。可以使用wait或者waitpid函數來清除僵屍進程。

四、用wait或waitpid避免fork函數產生僵屍進程

wait和waitpid的函數原型是:

#include <sys/types.h>
#include <sys/wait.h>

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

若調用成功則返回清理掉的子進程id,若調用出錯則返回-1。父進程調用wait或waitpid時可能會:

  1. 阻塞(如果它的所有子進程都還在運行)。
  2. 帶子進程的終止信息立即返回(如果一個子進程已終止,正等待父進程讀取其終止信息)。
  3. 出錯立即返回(如果它沒有任何子進程)。

這兩個函數的區別是:

  1. 如果父進程的所有子進程都還在運行,調用wait將使父進程阻塞,而調用waitpid時如果在options參數中指定WNOHANG可以使父進程不阻塞而立即返回0。
  2. wait等待第一個終止的子進程,而waitpid可以通過pid參數指定等待哪一個子進程。

可見,調用wait和waitpid不僅可以獲得子進程的終止信息,還可以使父進程阻塞等待子進程終止,起到進程間同步的作用。如果參數status不是空指針,則子進程的終止信息通過這個參數傳出,如果只是為了同步而不關心子進程的終止信息,可以將status參數指定為NULL。

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

int main ()
{
int fpid;
int stat_val;
int i;

for(i;; i++)
{
fpid = fork();
if(fpid>0)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);

}
else if(!fpid)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);
exit(0);
}

waitpid(fpid, &stat_val,0);
if(WIFEXITED(stat_val))
printf("child exited with code %d\n", WEXITSTATUS(stat_val));
else if (WIFSIGNALED(stat_val))
printf("child terminated abnormally, signal %d\n", WTERMSIG(stat_val));
sleep(10);
}

return 0;
}

運行結果:

loop 134514078: my process id is 31080, my father process id is 31079, fpid 0
loop 134514078: my process id is 31079, my father process id is 6968, fpid 31080
child exited with code 0
loop 134514079: my process id is 31089, my father process id is 31079, fpid 0
loop 134514079: my process id is 31079, my father process id is 6968, fpid 31089
child exited with code 0

這時在使用ps -e查看就不會產生僵屍進程了。但是會引起阻塞。以下是不會引起阻塞的代碼

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

void sig_chld_handler(void) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main ()
{
int fpid;
int stat_val;
int i;

#ifdef __USE_GNU
signal(SIGCHLD, (sighandler_t )sig_chld_handler);
#endif
#ifdef __USE_BSD
signal(SIGCHLD, (sig_t )sig_chld_handler);
#endif

for(i;; i++)
{
fpid = fork();
if(fpid>0)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);

}
else if(!fpid)
{
printf("loop %d: my process id is %d, my father process id is %d, fpid %d\n",
i, getpid(), getppid(), fpid);
exit(0);
}
}

return 0;
}

 

子進程的終止信息在一個int中包含了多個字段,用宏定義可以取出其中的每個字段:如果子進程是正常終止的,WIFEXITED取出的字段值非零,WEXITSTATUS取出的字段值就是子進程的退出狀態;如果子進程是收到信號而異常終止的,WIFSIGNALED取出的字段值非零,WTERMSIG取出的字段值就是信號的編號。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM