#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid_t fpid; //fpid表示fork函數返回的值 int count=0; fpid=fork(); if (fpid < 0) printf("error in fork!\n"); else if (fpid == 0) { printf("i am the child process, my process id is %d\n",getpid()); printf(" my parent process id is %d\n",getppid()); count++; } else { printf("i am the parent process, my process id is %d\n",getpid()); count++; } printf("count 統計結果是: %d\n",count); while(1);//無限阻塞 return 0; }

fork 函數,創建子進程。
函數原型:

關於其返回值:

fork函數一次調用,兩次返回。子進程中返回0,父進程中,返回子進程的ID。如果fork失敗,返回-1.並且不會創建子進程,同時錯誤代碼errno會被設置。
fork的讀時共享,寫時復制機制。子進程擁有和父進程一樣的0-3G用戶空間,但是3-4G內核空間中PCB(進程控制塊)的進程ID號並不相同。子進程和父進程有如此多相同的地方,如果僅僅是讀取0-3G用戶空間的數據,則沒有必要復制一份父進程的數據到內存,但如果需要寫入數據,就必須開辟新的空間了。
——————————————————》》》分割線
但是,子進程創建出來如果所做的任務和父進程完全一致,那么也就沒有必要創建子進程的意義了。通常而言,子進程被創建,是要做與父進程不一樣的事情。
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid_t fpid; int count=0; fpid=fork(); if (fpid < 0) printf("error in fork!\n"); else if (fpid == 0) { char *const argv[]={"ls","-l",NULL}; printf("i am a child process\n"); execvp("ls",argv); printf("where is my process?\n"); } else { printf("i am a parent process\n"); } while(1); return 0; }

execvp:屬於exec族中的一個函數。




當進程調用了exec族的某一函數之后,該進程執行的程序被替換成全新的程序,新程序從其main函數開始執行。由於調用exec函數不會創建新的進程,所以替換前后的進程ID並不會改變。exec只是用磁盤上的一個新程序替換了當前進程的正文段、數據段、堆段和棧段。
在上面的程序中,使用了ls –l這個指令,可以發現該指令確實正確執行了,但是顯示的列表中,a.out沒有顏色信息。其次,在char *const argv[]={"ls","-l",NULL};中,第一個“ls”字符串只有一個占位符的作用,該字符串無論是什么都不會影響程序的運行結果,因為在execvp("ls",argv);中已經指定了ls程序,在argv指針數組中的argv[0]只用作占位符。哪怕將其改為”hehe”:

程序依然正常運行,exec函數會從argv[1]開始,直到NULL結束。
現在新建一個upper.c文件:
/* upper.c */ #include<stdio.h> #include <ctype.h> int main(void) { in ch; while((ch=getchar())!=EOF) putchar(toupper(ch));//小寫轉大寫 return 0; }
把之前的test.c改成:
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid_t fpid; int count=0; fpid=fork(); if (fpid < 0) printf("error in fork!\n"); else if (fpid == 0) { char *const argv[]={"upper",NULL}; printf("i am a child process\n"); execv("./app",argv); printf("where is my process?\n"); } else { printf("i am a parent process\n"); } while(1); return 0; }
先編譯upper.c生成app,然后編譯test.c生成a.out,運行a.out:

exec函數運行之后,只有出錯才會返回,這也就意味着,exec函數后面的語句不會被執行。同樣,這里的char *const argv[]={"upper",NULL};中的“upper”字符串也可以是其他任何字符串(即使為NULL也行,但通常使用能代表其含義的名稱)。
wait和waitpid函數:
僵屍進程: 子進程退出,父進程沒有回收子進程資源(PCB),則子進程變成僵屍進程。
孤兒進程: 父進程先於子進程結束,則子進程成為孤兒進程,子進程的父進程成為1號進程init進程,稱為init進程領養孤兒進程。
一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留着,內核在其中保存了一些信息:如果是正常終止則保存着退出狀態,如果是異常終止則保存着導致該進程終止的信號是哪個。這個進程的父進程可以調用wait或waitpid獲取這些信息,並徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變量$?查看(echo $?),因為Shell是它的父進程,當它終止時Shell調用wait或waitpid得到它的退出狀態同時徹底清除掉這個進程。如果一個進程已經終止,但是它的父進程尚未調用wait或waitpid對它進行清理,這時的進程狀態稱為僵屍(Zombie)進程。任何進程在剛終止時都是僵屍進程,正常情況下,僵屍進程都立刻被父進程清理了。

目前,只用知道,wait和waitpid的基本用法即可,至於到底怎么應用在程序中,可以暫時不理會。


如果使用了WNOHANG參數調用waitpid,即使沒有子進程退出,它也會立即返回,不會像wait那樣永遠等下去。

