exec 函數族
fork 創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支) ,子進程往往要調用一種 exec 函數以執行另一個程序。當進程調用一種 exec 函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序 的啟動例程開始執行。調用 exec 并不創建新進程,所以調用 exec 前后該進程的 id 并未改變。
將當前進程的.text、.data 替換為所要加載的程序的.text、.data,然后讓進程從新的.text 第一條指令開始執行, 但進程 ID 不變,換核不換殼。
其實有六種以 exec 開頭的函數,統稱 exec 函數:
1. int execl(constchar*path,const char*arg,...);
2. int execlp(constchar*file,const char*arg,...);
3. int execle(constchar*path,const char*arg,...,char*constenvp[]);
4. int execv(constchar*path,char*constargv[]);
5. int execvp(constchar*file,char*constargv[]);
6. int execve(constchar*path,char*constargv[],char*constenvp[]);
execlp 函數
加載一個進程,借助 PATH 環境變量
intexeclp(constchar*file,constchar*arg,...);
- 成功:無返回;
- 失敗:-1
參數 1:要加載的程序的名字。該函數需要配合 PATH 環境變量來使用,當 PATH 中所有目錄搜索后沒有參 數 1 則出錯返回。
該函數通常用來調用系統程序。如:ls、date、cp、cat 等命令。
編寫一個程序,實現Linux下shell命令ls -l -a
#include<stdio.h>#include<unistd.h>#include<stdlib.h>int main(void){pid_t pid;pid=fork();if(pid==-1){ perror("fork error:");exit(1);}else if(pid>0){ sleep(1);printf("parent\n");}else{execlp("ls","ls","-l","-a",NULL); } return 0;}
execl 函數
這個程序可以加載自定義的函數加載一個進程, 通過 路徑+程序名 來加載。
intexecl(constchar*path,constchar*arg,...);
- 成功:無返回;
- 失敗:-1
對比 execlp,如加載"ls"命令帶有-l,-F 參數
execlp("ls","ls","-l","-F",NULL);
使用程序名在 PATH 中搜索。
execl("/bin/ls","ls","-l","-F",NULL);
使用參數 1 給出的絕對路徑搜索。
這個函數可以加載自定義的程序
被加載的程序:
#include<stdlib.h>
#include<unistd.h>
#include<stdio.h>
int main()
{while(1){sleep(1);printf("-------\n");} return 0;
}
加載程序:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(void)
{pid_t pid;pid=fork();if(pid==-1){ perror("fork error:");exit(1);}else if(pid>0){ sleep(1);printf("parent\n");}else{// execlp("ls","ls","-l","-h",NULL);// execl("/bin/ls","ls","-l","-h",NULL);execl("./execl_test","execl_test","NULL"); } return 0;
}
execvp 函數
加載一個進程,使用自定義環境變量 env intexecvp(constcharfile,constcharargv[]);
變參形式:
- …
- argv[] (main 函數也是變參函數,形式上等同于 intmain(intargc,char*argv0,…))
變參終止條件:
- NULL 結尾
- 固參指定 execvp 與 execlp 參數形式不同,原理一致。
將當前進程信息打印到文件中
int dup2(int oldfd, int newfd);完成文件描述符拷貝
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>int main()
{int fd; //打開文件,文件名為ps.out,只寫方式打開,文件不存在創建,存在截斷>為0.指定打開文件權限為644fd=open("ps.out",O_WRONLY|O_CREAT|O_TRUNC,0644);//返回為0為打開失敗if(fd<0){perror("open ps.out error");exit(1);} dup2(fd,STDOUT_FILENO);//dup2(3,1);fd,stdoutexeclp("ps","ps","ax",NULL);//隱式回收,進程結束時,會把所有打開的文件都關閉掉return 0;
}
exec 函數族一般規律
exec 函數一旦調用成功即執行新的程序,不返回。只有失敗才返回,錯誤值-1。所以通常我們直接在 exec 函數 調用后直接調用 perror()和 exit(),無需 if 判斷。
- l (list) 命令行參數列表
- p (path) 搜素 file 時使用 path 變量
- v(vector)使用命令行參數數組
- e(environment) 使用環境變量數組,不使用進程原有的環境變量,設置新加載程序運行的環境變量
事實上,只有 execve 是真正的系統調用,其它五個函數最終都調用 execve,所以 execve 在 man 手冊第 2 節, 其它函數在 man 手冊第 3 節。這些函數之間的關系如下圖所示。
僵尸進程和孤兒進程
孤兒進程
孤兒進程: 父進程先于子進程結束,則子進程成為孤兒進程,子進程的父進程成為 init 進程,稱為 init 進程領 養孤兒進程。
創建孤兒進程:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{pid_t pid;pid=fork();if(pid==-1){perror("fork");exit(1);}else if(pid>0){sleep(1);printf("I am parent pid= %d,parentID= %d\n",getpid(),getppid());}else if(pid==0){printf("child pid = %d,parentID=%d \n",getpid(),getppid()); sleep(3);printf("child pid = %d,parentID=%d \n",getpid(),getppid());} return 0;
}
僵尸進程
僵尸進程: 進程終止,父進程尚未回收,子進程殘留資源(PCB)存放于內核中,變成僵尸(Zombie)進程。
特別注意,僵尸進程是不能使用 kill 命令清除掉的。因為 kill 命令只是用來終止進程的,而僵尸進程已經終止
用wait或者waitpid回收或者殺死他爸
回收子進程
wait 函數
一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的 PCB 還保留著,內核在其中保 存了一些信息:
如果是正常終止則保存著退出狀態,
如果是異常終止則保存著導致該進程終止的信號是哪個。
這個 進程的父進程可以調用 wait 或 waitpid 獲取這些信息,然后徹底清除掉這個進程。
我們知道一個進程的退出狀態可 以在 Shell 中用特殊變量$?查看,因為 Shell 是它的父進程,當它終止時 Shell 調用 wait 或 waitpid 得到它的退出狀態 同時徹底清除掉這個進程。
父進程調用 wait 函數可以回收子進程終止信息。該函數有三個功能:
-
阻塞等待子進程退出
-
回收子進程殘留資源
-
獲取子進程結束狀態(退出原因)。
pid_t wait(int*status); 成功:清理掉的子進程 ID;失敗:-1(沒有子進程)
當進程終止時,操作系統的隱式回收機制會:1.關閉所有文件描述符 2. 釋放用戶空間分配的內存。內核的 PCB 仍存在。其中保存該進程的退出狀態。(正常終止→退出值;異常終止→終止信號)
可使用 wait 函數傳出參數 status 來保存進程的退出狀態。借助宏函數來進一步判斷進程終止的具體原因
宏函 數可分為如下三組:
-
1
WIFEXITED(status) 為非 0 → 進程正常結束 WEXITSTATUS(status) 如上宏為真,使用此宏 → 獲取進程退出狀態 (exit 的參數)`
示例代碼:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>int main(void)
{pid_t pid,wpid;pid=fork();int status;if(pid==0){// pritnf("---child,my parent= %d,going to sleep 10s\n",getppid());sleep(3);printf("----------------child die--------------\n");return 100 ; }else if(pid>0){wpid=wait(&status);if(wpid==-1){perror("wait error:");exit(1);} if(WIFEXITED(status)){printf("child exit with %d\n",WEXITSTATUS(status));} while(1){// printf("I am parent,pid= %d,myson = %d\n",getpid(),pid);sleep(1);} }else{perror("fork");return 1;} return 0;
}
-
2
WIFSIGNALED(status) 為非 0 → 進程異常終止 WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進程終止的那個信號的編號。
示例代碼
int main(void)
{pid_t pid,wpid;pid=fork();int status;if(pid==0){// pritnf("---child,my parent= %d,going to sleep 10s\n",getppid());sleep(20);printf("----------------child die--------------\n");return 100 ;}else if(pid>0){wpid=wait(&status);if(wpid==-1){perror("wait error:");exit(1);} if(WIFEXITED(status)){printf("child exit with %d\n",WEXITSTATUS(status));} if(WIFSIGNALED(status)){printf("child killed by %d\n",WTERMSIG(status));} while(1){// printf("I am parent,pid= %d,myson = %d\n",getpid(),pid);sleep(1);} }else{perror("fork");return 1;} return 0;
}
3. 3
WIFSTOPPED(status) 為非 0 → 進程處于暫停狀態 WSTOPSIG(status) 如上宏為真,使用此宏 → 取得使進程暫停的那個信號的編號。 WIFCONTINUED(status) 為真 → 進程暫停后已經繼續運行
示例代碼
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>int main(void)
{pid_t pid,wpid;pid=fork();if(pid==0){// pritnf("---child,my parent= %d,going to sleep 10s\n",getppid()); sleep(10);printf("----------------child die--------------\n");}else if(pid>0){wpid=wait(NULL);if(wpid==-1){perror("wait error:");exit(1);} while(1){// printf("I am parent,pid= %d,myson = %d\n",getpid(),pid);sleep(1);} }else{perror("fork");return 1;} return 0;
}
waitpid 函數
作用同 wait,但可指定 pid 進程清理,可以不阻塞。
pid_t waitpid(pid_t pid,int* status,in options); 成功:返回清理掉的子進程 ID;失敗:-1(無子進程)
特殊參數和返回情況:
參數 pid:
>
0 回收指定 ID 的子進程- -1 回收任意子進程(相當于 wait)
- 0 回收和當前調用 waitpid 一個組的所有子進程
- <-1 回收指定進程組內的任意子進程
- 如果傳的是進程組ID,則回收全部該組內全部進程,
kill
也可以用
返回 0:參 3 為 WNOHANG(指定為非阻塞),且子進程正在運行。
注意:一次 wait 或 waitpid 調用只能清理一個子進程,清理多個子進程應使用循環。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>int main(int argc,char *argv[])
{int n=5,i; //默認創建5個進程pid_t p,q;pid_t wpid;if(argc==2){n=atoi(argv[1]);} for(i=0;i<n;i++){ //出口1,父進程專用出口p=fork();if(p==0){break; //出口2,子進程出口,i不自增}else if(i==3){q=p;} } //父進程if(n==i){sleep(n);printf("I am parent,pid= %d\n",getpid(),getgid());do{ wpid=waitpid(-1,NULL,WNOHANG);//====wait(NULL);if(wpid>0){n--;} //if wpid==0 說明子進程正在運行,sleep(1);}while(n>0); //循環回收多個子進程//while(wait(NULL));//while(1);printf("wait finish\n");//打印子進程}else{sleep(i);printf("I am %dth child,pid= %d,gpid=%d\n",i+1,getpid(),getgid());}return 0;
}