日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UNIX环境编程学习笔记(21)——进程管理之获取进程终止状态的 wait 和 waitpid 函数...

發布時間:2023/12/18 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UNIX环境编程学习笔记(21)——进程管理之获取进程终止状态的 wait 和 waitpid 函数... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

lienhua34
2014-10-12

當一個進程正常或者異常終止時,內核就向其父進程發送 SIGCHLD信號。父進程可以選擇忽略該信號,或者提供一個該信號發生時即被調用的函數(信號處理程序)。對于這種信號的系統默認動作是忽略它。

在文檔“進程控制三部曲”中,我們講的第三部曲是使用 wait 函數來獲取終止子進程的終止狀態。那么,有幾個問題我們這里需要詳細的學習一下。

1. 父進程一定能夠獲取到子進程的終止狀態嗎?如果子進程在父進程調用 wait 函數前就終止了,怎么辦?

2. 如果父進程沒有獲取子進程的終止狀態,那會發生什么?

3. 如果父進程有多個子進程,那么獲取的是哪個子進程的終止狀態呢?

對于第一個問題的回答是:內核為每個終止進程保存了一定量的信息,包括進程 ID、該進程的終止狀態、以及該進程使用的 CPU 時間總量。所以,當終止進程的父進程調用 wait 或者 waitpid 函數,即可獲取到這些信息。當父進程獲取終止進程的終止信息之后,內核就可以釋放終止進程所使用的所有存儲區、關閉其所有打開的文件。

在 UNIX 術語中,一個已經終止、但是其父進程尚未對其進行善后處理(獲取終止子進程的相關信息)的進程被稱為僵尸進程(zombie)。如果編寫一個長期運行的程序,調用 fork 產生子進程之后,需要調用 wait 來獲取這些子進程的終止狀態,否則這些子進程在終止之后將會變成僵尸進程。(后面會講到用一個技巧以避開父進程調用 wait 獲取所有子進程的終止狀態。)

那么如果那些被 init 進程領養的進程在終止之后會不會也變成僵尸進程?答案是:不會。因為 init 進程無論何時只要有一個子進程終止,init 就會調用 wait 函數獲取其終止狀態。

那么關于上面的第三個問題,我們得通過詳細學習 wait 和 waitpid 函數才能都做出回答了。

1 wait 函數

#include <sys/wait.h>

pid_t wait(int *statloc);

返回值:若成功則返回終止進程的ID,若出錯則返回-1

參數 statloc 是一個整形指針。如果 statloc 不是一個空指針,則終止進程的終止狀態將存儲在該指針所指向的內存單元中。如果不關心終止狀態,可以將 statloc 參數設置為空。

調用 wait 函數時,調用進程將會出現下面的情況:

? 如果其所有子進程都還在運行,則阻塞。

? 如果一個子進程已經終止,正等待父進程獲取其終止狀態,則獲取該子進程的終止狀態然后立即返回。

? 如果沒有任何子進程,則立即出錯返回。

wait 函數獲取的終止狀態是一個 int 型數值,那我們如何得到具體的終止信息呢?POSIX.1 規定終止狀態用定義在 <sys/wait.h> 中的各個宏來參看。有四個互斥的宏可以用來得到進程的終止原因。這四個宏見表 1,

表 1: 檢查終止狀態的宏
說明
WIFEXITED(status)若正常終止子進程返回的狀態,則為真。此種情況,調用 WEXITSTATUS(status) 可以獲取子進程調用 exit 函數的參數的低 8位。
WIFSIGNALED(status)若為異常終止子進程返回的狀態,則為真。此種情況,調用 WTERMSIG(status) 取得使子進程終止的信號編號。
WIFSTOPPED(status)若為當前暫停子進程返回的狀態,則為真。
WIFCONTINUED(status)若在作業控制暫停后已經繼續的子進程返回了狀態,則為真。

下面我們來看一下打印終止進程狀態說明的例子,

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/wait.h>extern void print_exit(int status);int main(void) {pid_t pid;int status;if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {exit(8);}if (wait(&status) != pid) {printf("wait error: %s\n", strerror(errno));exit(-1);}print_exit(status);if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {abort();}if (wait(&status) != pid) {printf("wait error: %s\n", strerror(errno));exit(-1);}print_exit(status);exit(0); }void print_exit(int status) {if (WIFEXITED(status)) {printf("normal termination, exit status = %d\n",WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("abnormal termination, signal number =%d\n",WTERMSIG(status));} } waitdemo.c

編譯該程序,生成并運行 waitdemo 文件,

lienhua34:demo$ gcc -o waitdemo waitdemo.c lienhua34:demo$ ./waitdemo normal termination, exit status = 8 abnormal termination, signal number =6

下面我們再來看一個產生僵尸進程的示例,

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/wait.h>int main(void) {pid_t pid;if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {exit(0);}printf("fork child process:%d\n", pid);if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {exit(0);}printf("fork child process:%d\n", pid);if ((pid = wait(NULL)) < 0) {printf("wait error: %s\n", strerror(errno));exit(-1);}printf("get child process(%d) termination status\n", pid);sleep(5);printf("parent process exit\n");exit(0); } zombiedemo.c

我們在父進程最后 sleep(5) 讓父進程睡眠 5 秒鐘是避免父進程太早退出,我們觀察不到僵尸進程。我們編譯該程序文件,生成并執行文件

lienhua34:demo$ ps -A -ostat,pid | grep -e '[Zz]' Z 1725 lienhua34:demo$ gcc -o zombiedemo zombiedemo.c lienhua34:demo$ ./zombiedemo & [1] 2961 lienhua34:demo$ fork child process:2962 fork child process:2963 get child process(2963) termination status ps -A -ostat,pid | grep -e '[Zz]' Z 1725 Z 2962 lienhua34:demo$ parent process exit ps -A -ostat,pid | grep -e '[Zz]' Z 1725 [1]+ 完成 ./zombiedemo

ps 命令打印的進程中,Z 表示僵尸進程。從上面的運行結果,我們看到父進程(ID:2961)fork 了兩個子進程(ID:2962 和 2963),然后調用了 wait 函數獲取了子進程 2963 的終止狀態,于是子進程 2962 便成為了僵尸進程。但是,當父進程也退出時,生成僵尸進程的子進程 2962 也被內核釋放。

2 waitpid 函數

只要有一個子進程終止,wait 函數就會返回。那么如果父進程希望等待特定的子進程終止,該怎么辦?UNIX 提供了提供這樣功能的 waitpid 函數。

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *statloc, int options);

返回值:若成功則返回終止進程ID或0;若出錯則返回-1

其中 statloc 參數跟 wait 函數一樣,獲取終止子進程的狀態信息。waitpid 函數通過 pid 參數來控制父進程希望獲取特定進程的終止狀態信息,

? pid==-1:等待任一子進程,與 wait 函數等效。

? pid>0:等待其進程 ID 與 pid 相等的子進程。

? pid==0:等待其組 ID 等于調用進程組 ID 的任一子進程。(我們這里不學習進程組)

? pid<-1:等待其組 ID 等于 pid 絕對值的任一子進程。

waitpid 函數返回終止子進程的進程 ID。如果指定的進程或進程組不存在,或者參數 pid 指定的進程不是調用進程的子進程則都將出錯。waitpid 函數跟 wait 函數的另一個不同之處在于,wait 函數可能會使調用進程阻塞,而 waitpid 函數可以通過第三個參數 options 來控制調用進程是否要阻塞。options 參數可以是 0,也可以是表 2 中各常量或運算的結果。

表 2: waitpid 的 options 常量
常量說明
WCONTINUED若實現支持作業控制,那么由 pid 指定的任一子進程在暫停后已經繼續,但其狀態尚未報告,則返回其狀態。
WNOHANG若由 pid 指定的子進程并不是立即可用的,則 waitpid 不阻塞,此時返回值為 0.
WUNTRACED
若某實現支持作業控制,而由 pid 指定的任一子進程已處于暫停狀態,并且其狀態自暫停以來還未報告過,則返回其狀態。

關于 options 用于作業控制 的兩個 常量 WCONTINUED 和 WUNTRACED,我們這里不學習,我們只關心常量 WNOHANG。我們來看一個例子。

#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/wait.h>int main(void) {pid_t pid1, pid2;pid_t waitpidRes;if ((pid1 = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid1 == 0) {sleep(3);printf("child process %d exit\n", getpid());exit(0);}if ((pid2 = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid2 == 0) {printf("child process %d exit\n", getpid());exit(0);}if ((waitpidRes = waitpid(pid1, NULL, 0)) == pid1) {printf("get terminated child process %d.\n", waitpidRes);} else if (waitpidRes < 0) {printf("waitpid error: %s\n", strerror(errno));exit(-1);} else {printf("waitpid return 0\n");}printf("parent process exit\n");exit(0); } waitpiddemo.c

在上面的程序中,我們在第一個子進程中 sleep(3) 讓該子進程睡眠 3秒,以便在父進程調用 waitpid 函數時該子進程尚未結束。編譯該程序,生成并執行 waitpiddemo 文件,

lienhua34:demo$ gcc -o waitpiddemo waitpiddemo.c lienhua34:demo$ ./waitpiddemo child process 2972 exit child process 2971 exit get terminated child process 2971. parent process exit

從上面的運行結果,我們可以看到父進程阻塞等待子進程 2971 終止。我們如果把上面程序的 waitpid 函數第三個參數 options 改為 WNOHANG,看一下其實際運行結果。

lienhua34:demo$ gcc -o waitpiddemo waitpiddemo.c lienhua34:demo$ ./waitpiddemo waitpid return 0 parent process exit child process 2982 exit lienhua34:demo$ child process 2981 exit

從上面的運行結果,我們可以看出父進程調用 waitpid 函數時,子進程2981 尚未終止,于是 waitpid 函數沒有阻塞父進程,直接返回 0.

3 避免調用大量WAIT函數來防止僵尸進程的技巧

前面講到僵尸進程時,我們提到要編寫一個長期運行的程序,要避免出現大量的僵尸情況,就需要每次 fork 出一個子進程時都需要調用 wait 函數來等待子進程的結束以便處理該子進程的終止狀態信息。但是,我們每次 fork 都要調用一個 wait 函數,實在是太麻煩了。

于是,我們就希望每次調用 fork 時不需要 wait 等待子進程終止,也不希望子進程處于僵死狀態直到程序結束。這里提供一個實現此要求的技巧:調用 fork 兩次。我們來看下面的例子:

#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/wait.h>int main(void) {pid_t pid;if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid == 0) {if ((pid = fork()) < 0) {printf("fork error: %s\n", strerror(errno));exit(-1);} else if (pid > 0) {printf("first child process: %d, parent process: %d\n", getpid(), getppid());exit(0);}sleep(2);printf("second child process: %d, parent process: %d\n", getpid(), getppid());exit(0);}if (wait(NULL) < 0) {printf("wait error: %s\n", strerror(errno));exit(-1);}printf("parent process %d exit\n", getpid());exit(0); } nozombiedemo.c

在上面程序中,在第一個子進程中 fork 處第二個子進程之后并終止第一個子進程。編譯該程序,生成并執行文件 nozombiedemo,

lienhua34:demo$ gcc -o nozombiedemo nozombiedemo.c lienhua34:demo$ ./nozombiedemo first child process: 2471, parent process: 2470 parent process 2470 exit lienhua34:demo$ second child process: 2472, parent process: 1

從上面的運行結果,我們看到第一個子進程 2471 終止后,其子進程2472 的父進程 ID 變成了 1(即 init 進程)。前面我們提到過,父進程為 init進程的所有進程在終止時都會被 init 進程獲取其終止狀態,從而不會變成僵尸進程。于是,通過上面的 fork 兩次的技巧,我們就可以實現創建一個新進程,不需要等待該新進程終止,也不擔心該新進程會變成僵尸進程。

(done)

轉載于:https://www.cnblogs.com/lienhua34/p/4021272.html

總結

以上是生活随笔為你收集整理的UNIX环境编程学习笔记(21)——进程管理之获取进程终止状态的 wait 和 waitpid 函数...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。