Linux系统编程--3(exec 函数族,僵尸进程和孤儿进程,wait和wait_pid回收子进程)
exec 函數(shù)族
fork 創(chuàng)建子進(jìn)程后執(zhí)行的是和父進(jìn)程相同的程序(但有可能執(zhí)行不同的代碼分支) ,子進(jìn)程往往要調(diào)用一種 exec 函數(shù)以執(zhí)行另一個程序。當(dāng)進(jìn)程調(diào)用一種 exec 函數(shù)時,該進(jìn)程的用戶空間代碼和數(shù)據(jù)完全被新程序替換,從新程序 的啟動例程開始執(zhí)行。調(diào)用 exec 并不創(chuàng)建新進(jìn)程,所以調(diào)用 exec 前后該進(jìn)程的 id 并未改變。
將當(dāng)前進(jìn)程的.text、.data 替換為所要加載的程序的.text、.data,然后讓進(jìn)程從新的.text 第一條指令開始執(zhí)行, 但進(jìn)程 ID 不變,換核不換殼。
其實(shí)有六種以 exec 開頭的函數(shù),統(tǒng)稱 exec 函數(shù):
execlp 函數(shù)
加載一個進(jìn)程,借助 PATH 環(huán)境變量
intexeclp(constchar*file,constchar*arg,...);
參數(shù) 1:要加載的程序的名字。該函數(shù)需要配合 PATH 環(huán)境變量來使用,當(dāng) PATH 中所有目錄搜索后沒有參 數(shù) 1 則出錯返回。
該函數(shù)通常用來調(diào)用系統(tǒng)程序。如:ls、date、cp、cat 等命令。
編寫一個程序,實(shí)現(xiàn)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 函數(shù)
這個程序可以加載自定義的函數(shù)加載一個進(jìn)程, 通過 路徑+程序名 來加載。
intexecl(constchar*path,constchar*arg,...);對比 execlp,如加載"ls"命令帶有-l,-F 參數(shù)
execlp("ls","ls","-l","-F",NULL); 使用程序名在 PATH 中搜索。
execl("/bin/ls","ls","-l","-F",NULL); 使用參數(shù) 1 給出的絕對路徑搜索。
這個函數(shù)可以加載自定義的程序
被加載的程序:
#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 函數(shù)
加載一個進(jìn)程,使用自定義環(huán)境變量 env intexecvp(constcharfile,constcharargv[]);
變參形式:
變參終止條件:
將當(dāng)前進(jìn)程信息打印到文件中
int dup2(int oldfd, int newfd);完成文件描述符拷貝 #include<unistd.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h>int main() {int fd; //打開文件,文件名為ps.out,只寫方式打開,文件不存在創(chuàng)建,存在截?cái)?gt;為0.指定打開文件權(quán)限為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);//隱式回收,進(jìn)程結(jié)束時,會把所有打開的文件都關(guān)閉掉return 0; }exec 函數(shù)族一般規(guī)律
exec 函數(shù)一旦調(diào)用成功即執(zhí)行新的程序,不返回。只有失敗才返回,錯誤值-1。所以通常我們直接在 exec 函數(shù) 調(diào)用后直接調(diào)用 perror()和 exit(),無需 if 判斷。
事實(shí)上,只有 execve 是真正的系統(tǒng)調(diào)用,其它五個函數(shù)最終都調(diào)用 execve,所以 execve 在 man 手冊第 2 節(jié), 其它函數(shù)在 man 手冊第 3 節(jié)。這些函數(shù)之間的關(guān)系如下圖所示。
僵尸進(jìn)程和孤兒進(jìn)程
孤兒進(jìn)程
孤兒進(jìn)程: 父進(jìn)程先于子進(jìn)程結(jié)束,則子進(jìn)程成為孤兒進(jìn)程,子進(jìn)程的父進(jìn)程成為 init 進(jìn)程,稱為 init 進(jìn)程領(lǐng) 養(yǎng)孤兒進(jìn)程。
創(chuàng)建孤兒進(jìn)程:
僵尸進(jìn)程
僵尸進(jìn)程: 進(jìn)程終止,父進(jìn)程尚未回收,子進(jìn)程殘留資源(PCB)存放于內(nèi)核中,變成僵尸(Zombie)進(jìn)程。
特別注意,僵尸進(jìn)程是不能使用 kill 命令清除掉的。因?yàn)?kill 命令只是用來終止進(jìn)程的,而僵尸進(jìn)程已經(jīng)終止
回收子進(jìn)程
wait 函數(shù)
一個進(jìn)程在終止時會關(guān)閉所有文件描述符,釋放在用戶空間分配的內(nèi)存,但它的 PCB 還保留著,內(nèi)核在其中保 存了一些信息:
如果是正常終止則保存著退出狀態(tài),
如果是異常終止則保存著導(dǎo)致該進(jìn)程終止的信號是哪個。
這個 進(jìn)程的父進(jìn)程可以調(diào)用 wait 或 waitpid 獲取這些信息,然后徹底清除掉這個進(jìn)程。
我們知道一個進(jìn)程的退出狀態(tài)可 以在 Shell 中用特殊變量$?查看,因?yàn)?Shell 是它的父進(jìn)程,當(dāng)它終止時 Shell 調(diào)用 wait 或 waitpid 得到它的退出狀態(tài) 同時徹底清除掉這個進(jìn)程。
父進(jìn)程調(diào)用 wait 函數(shù)可以回收子進(jìn)程終止信息。該函數(shù)有三個功能:
阻塞等待子進(jìn)程退出
回收子進(jìn)程殘留資源
獲取子進(jìn)程結(jié)束狀態(tài)(退出原因)。
pid_t wait(int*status); 成功:清理掉的子進(jìn)程 ID;失敗:-1(沒有子進(jìn)程)當(dāng)進(jìn)程終止時,操作系統(tǒng)的隱式回收機(jī)制會:1.關(guān)閉所有文件描述符 2. 釋放用戶空間分配的內(nèi)存。內(nèi)核的 PCB 仍存在。其中保存該進(jìn)程的退出狀態(tài)。(正常終止→退出值;異常終止→終止信號)
可使用 wait 函數(shù)傳出參數(shù) status 來保存進(jìn)程的退出狀態(tài)。借助宏函數(shù)來進(jìn)一步判斷進(jìn)程終止的具體原因
宏函 數(shù)可分為如下三組:
1
WIFEXITED(status) 為非 0 → 進(jìn)程正常結(jié)束 WEXITSTATUS(status) 如上宏為真,使用此宏 → 獲取進(jìn)程退出狀態(tài) (exit 的參數(shù))`示例代碼:
#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 → 進(jìn)程異常終止 WTERMSIG(status) 如上宏為真,使用此宏 → 取得使進(jìn)程終止的那個信號的編號。示例代碼
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 函數(shù)
作用同 wait,但可指定 pid 進(jìn)程清理,可以不阻塞。
pid_t waitpid(pid_t pid,int* status,in options); 成功:返回清理掉的子進(jìn)程 ID;失敗:-1(無子進(jìn)程)特殊參數(shù)和返回情況:
參數(shù) pid:
返回 0:參 3 為 WNOHANG(指定為非阻塞),且子進(jìn)程正在運(yùn)行。
注意:一次 wait 或 waitpid 調(diào)用只能清理一個子進(jìn)程,清理多個子進(jìn)程應(yīng)使用循環(huán)。
總結(jié)
以上是生活随笔為你收集整理的Linux系统编程--3(exec 函数族,僵尸进程和孤儿进程,wait和wait_pid回收子进程)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webpack --watch以后报错e
- 下一篇: 江南百景图搜查令如何获取