Linux系统编程--3(exec 函数族,僵尸进程和孤儿进程,wait和wait_pid回收子进程)
exec 函數族
fork 創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支) ,子進程往往要調用一種 exec 函數以執行另一個程序。當進程調用一種 exec 函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序 的啟動例程開始執行。調用 exec 并不創建新進程,所以調用 exec 前后該進程的 id 并未改變。
將當前進程的.text、.data 替換為所要加載的程序的.text、.data,然后讓進程從新的.text 第一條指令開始執行, 但進程 ID 不變,換核不換殼。
其實有六種以 exec 開頭的函數,統稱 exec 函數:
execlp 函數
加載一個進程,借助 PATH 環境變量
intexeclp(constchar*file,constchar*arg,...);
參數 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,...);對比 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[]);
變參形式:
變參終止條件:
將當前進程信息打印到文件中
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 判斷。
事實上,只有 execve 是真正的系統調用,其它五個函數最終都調用 execve,所以 execve 在 man 手冊第 2 節, 其它函數在 man 手冊第 3 節。這些函數之間的關系如下圖所示。
僵尸進程和孤兒進程
孤兒進程
孤兒進程: 父進程先于子進程結束,則子進程成為孤兒進程,子進程的父進程成為 init 進程,稱為 init 進程領 養孤兒進程。
創建孤兒進程:
僵尸進程
僵尸進程: 進程終止,父進程尚未回收,子進程殘留資源(PCB)存放于內核中,變成僵尸(Zombie)進程。
特別注意,僵尸進程是不能使用 kill 命令清除掉的。因為 kill 命令只是用來終止進程的,而僵尸進程已經終止
回收子進程
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
示例代碼
#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:參 3 為 WNOHANG(指定為非阻塞),且子進程正在運行。
注意:一次 wait 或 waitpid 調用只能清理一個子進程,清理多個子進程應使用循環。
總結
以上是生活随笔為你收集整理的Linux系统编程--3(exec 函数族,僵尸进程和孤儿进程,wait和wait_pid回收子进程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webpack --watch以后报错e
- 下一篇: Linux系统编程---4(进程间通信I