Linux 等待程式結束 wait() 和 waitpid()

weixin_34292959發表於2016-07-19

若子程式先於父程式結束時,父程式呼叫wait()函式和不呼叫wait()函式會產生兩種不同的結果:

--> 如果父程式沒有呼叫wait()和waitpid()函式,子程式就會進入僵死狀態。

--> 如果父程式呼叫了wait()和waitpid()函式,就不會使子程式變為殭屍程式。

這是為什麼呢?現在我們來深入學習wait()函式和waitpid()函式。

 

wait() 和 waitpid() 學習

1、首先我們先看一下它們的函式原型:

在終端輸入命令:man 2 wait

就會看到它的函式原型:

NAME

       wait, waitpid, waitid - wait for process to change state

SYNOPSIS

       #include <sys/types.h>

       #include <sys/wait.h>

       pid_t wait(int *status);

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

       int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

我們可以看到在2.6版本中新增叫了 waitid() 函式。

 

2、wait() 和 waitpid() 的功能:

1> wait()函式使父程式暫停執行,直到它的一個子程式結束為止,該函式的返回值是終止執行的子程式的PID,引數status所指向的變數存放子程式的退出碼,即從子程式的main函式返回的值或子程式中exit()函式的引數。如果status不是一個空指標,狀態資訊將被寫入它指向的變數。

注意:程式一旦呼叫了wait,就立即阻塞自己,由wait自動分析是否當前程式的某個子程式已經退出,如果讓它找到了這樣一個已經變成殭屍的子程式,wait 就會收集這個子程式的資訊, 並把它徹底銷燬後返回;如果沒有找到這樣一個子程式,wait就會一直阻塞在這裡,直到有一個出現為止。

2> 標頭檔案sys/wait.h中定義了程式退出狀態的巨集。

我們首先看下官方的解釋

a.WIFEXITED(status)     returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main() .

翻譯:

WIFEXITED(status)  若子程式是正常結束時則返回一個非零值。即呼叫exit(3),_exit(3) 或從main()函式返回的值。

 

b. WEXITSTATUS(status)   returns the exit status of the  child.   This  consists  of  the least  significant  8 bits of the status argument that the child specified in a call to exit(3) or _exit(2) or  as  the  argument for  a return  statement  in main().  This macro should only be employed if WIFEXITED returned true.

翻譯:

WEXITSTATUS(status)    如果巨集WIFEXIED返回值為非零值時,它返回子程式中exit或_exit引數中的低8位。

 

c.WIFSIGNALED(status)  returns true if the child process was terminated by a signal.

翻譯:

WIFSIGNALED(status)  若子程式異常終止則返回一個非零值。

 

d. WTERMSIG(status)   returns the number of the signal that caused the  child  process  to terminate.  This macro should only be employed if WIFSIGNALED returned true.

翻譯:

WTERMSIG(status)      如果巨集WIFSIGNALED的返回值非零,則返回使子程式異常終止的訊號編號。

 

e.WIFSTOPPED(status)   returns true if the child process was stopped by delivery  of a signal;  this  is  only possible if the call was done using WUN‐TRACED or when the child is being traced (see ptrace(2)).

翻譯:

WIFSTOPPED(status)  若子程式由於異常暫停,則返回一個非零值。當呼叫WUN‐TRACED或子程式被跟蹤時這才時可能的。

 

f. WSTOPSIG(status)    returns the number of the signal which caused the child to stop.This macro should only be employed if WIFSTOPPED returned true.

翻譯:

WSTOPSIG(status)      如果巨集WIFSTOPPED返回值非零,則返回使子程式暫停的訊號編號。

 

g.WIFCONTINUED(status)     (since  Linux  2.6.10)  returns  true  if  the child process wasresumed by delivery of SIGCONT.

翻譯:

WIFCONTINUED(status)     (從2.6版本後)如果孩子程式通過SIGCONT恢復則返回一個非零值。

 

3>waitpid() 函式

(1).我們先來看一個waitpid()的經典例子:當我們下載了A軟體的安裝程式後,在安裝快結束時它又啟動了另外一個流氓軟體安裝程式B,當B也安裝結束後,才告訴你所有安裝都完成了。A和B分別在不同的程式中,A如何啟動B並知道B安裝完成了呢?可以很簡單地在A中用fork啟動B,然後用waitpid()來等待B的結束。

(2).waitpid()也用來等待子程式的結束,但它用於等待某個特定程式結束。引數pid指明要等待的子程式的PID,引數status的含義與wait()函式中的status相同。options引數可以用來改變waitpid的行為,若將該引數賦值為WNOHANG,則使父程式不被掛起而立即返回執行其後的程式碼。

(3).waitpid()函式中引數pid的取值

還是先看下官方解釋:

 The value of pid can be:

       < -1   meaning  wait  for  any  child process whose process group ID is equal to the absolute value of pid.

       -1     meaning wait for any child process.

       0      meaning wait for any child process whose  process  group  ID  is equal to that of the calling process.

       > 0    meaning  wait  for  the  child  whose process ID is equal to the value  of pid.

翻譯:

pid的值可以為下己中情況:

  < -1  等待其組ID等於pid絕對值的任一子程式。

  =-1  等待任一子程式

  =0 等待其組ID等於呼叫程式的組ID的任一程式

  > 0  等待其程式ID等於pid的子程式退出

(4).waitpid()函式的一個應用:

如果想讓父程式週期性地檢查某個特定的子程式是否已經退出,可以用下面的方法:

waitpid(child_pid, (int *) 0, WNOHANG);

如果子程式尚未退出,它將返回0;如果子程式已經結束,則返回child_pid。呼叫失敗時返回-1。失敗的原因包括沒有該子程式,引數不合法等。

 

3、wait()和waitpid() 函式的區別

(1).在一個子程式終止前,wait()使其呼叫者阻塞,而waitpid()有一個選項,可使呼叫者不阻塞。

(2).waitpid()並不等待在其呼叫之後的第一個終止子程式,它有若干個選項,可以控制它所等待的程式。

(3).對於wait(),其唯一的出錯是呼叫程式沒有子程式;對於waitpid(),若指定的程式或程式組不存在,或者引數pid指定的程式不是呼叫程式的子程式都可能出錯。

(4).waitpid()提供了wait()沒有的三個功能:一是waitpid()可等待一個特定的程式;二是waitpid()提供了一個wait()的非阻塞版本(有時希望取的一個子程式的狀態,但不想使父程式阻塞,waitpid() 提供了一個這樣的選擇:WNOHANG,它可以使呼叫者不阻塞);三是waitpid()支援作業控制。

(5).wait(&status) 的功能就等於 waitpid(-1, &status, 0);

 

函式例項: 有時希望取的一個子程式的狀態,但不想使父程式阻塞,waitpid() 提供了一個這樣的選擇:WNOHANG,它可以使呼叫者不阻塞

#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main()
{
	pid_t pr, pc;

	do
	{
		pr = waitpid(pc, NULL, WNOHANG);

		if (pr == 0)
		{
			printf("No child exited\n");
			sleep(1);
		}

	}
	while (pr == 0);

	if (pr == pc)
	{
		printf("successfully get child %d\n", pr);
	}
	else
	{
		printf("some error occured\n");
	}

	return 0;
}

 

總結

無論程式是否正常終止,核心都會向其父程式傳送SIGCHLD 訊號,當呼叫wait或waitpid函式時

(a) 如果所有的子程式都在run, 可以阻塞父程式。

(b) 如果子程式終止,則wait立即返回子程式終止狀態。

(c) 如果沒有子程式在執行, 立即返回error。

 

4、函式實現:

函式例項1.(先看一個簡單的例項,看看程式呼叫wait()函式後是如何執行的?)

#include<stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	pid_t child;
	int i;

	child = fork();

	if (child < 0)
	{
		printf("create failed!\n");
		exit(1);
	}
	else if (0 == child)
	{
		printf("this is the child process pid= %d\n", getpid());

		for (i = 0; i < 5; i++)
		{
			printf("this is the child process print %d !\n", i + 1);
		}

		printf("the child end\n");
	}
	else
	{
		printf("this is the father process, ppid=%d\n", getppid());
		printf("father wait the child end\n");
		wait(&child);
		printf("father end\n");
	}
}

函式經過編譯:

gcc wait.c -o wait
./wait

函式執行結果:

this is the father process, ppid=3303

father wait the child end

this is the child process pid= 3356

this is the child process print 1 !

this is the child process print 2 !

this is the child process print 3 !

this is the child process print 4 !

this is the child process print 5 !

the child end

father end

 

說明:

從上面的程式我們可以深入的瞭解wait() 函式的執行過程:

當父程式呼叫wait()函式後被掛起等待,直到子程式結束為止。

 

函式例項2(現在我們在通過一個例項,來深入瞭解wait()函式的執行過程)

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	pid_t pid;
	char *msg;
	int i;
	int exit_code;

	printf("tiger study how to get exit code\n");
	pid = fork();

	if (pid == 0)		/* 子程式 */
	{
		msg = "child process is running";
		i = 5;
		exit_code = 37;
	}
	else if (pid > 0)	/* 父程式 */
	{
		exit_code = 0;
	}
	else
	{
		perror("process creation failed\n");
		exit(1);
	}

	if (pid > 0) /* 父程式 */
	{
		int status;
		pid_t child_pid;
		child_pid = wait(&status);

		printf("child process has exited, pid=%d\n", child_pid);
		if (WIFEXITED(status))
		{
			printf("child exited with code %d\n", WEXITSTATUS(status));
		}
		else
		{
			printf("child exited abnormally\n");
		}
	}
	else /* 子程式 */
	{
		while (i-- > 0)
		{
			puts(msg);
			sleep(1);
		}
	}

}

 

函式進過編譯後:

$ gcc wait1.c -o wait1
$ ./wait1

函式執行結果 :

tiger study how to get exit code

 child process is running

 child process is running

 child process is running

 child process is running

 child process is running

child process has exited,pid = 3816

child exited with code 0

 

說明:

父程式呼叫wait()函式後被掛起(我們可以再開一個終端,輸入命令:ps aux,可以看到父程式的執行結果為S)直到子程式結束。子程式結束後,wait()函式返回剛剛結束執行的子程式的pid,巨集WEXITSTATUS獲取子程式的退出碼。

 


 

注意

如果呼叫 wait() 的程式沒有已終止的子程式,不過有一個或多個子程式仍在執行,那麼 wait 將阻塞到現有子程式第一個終止為止。

waitpid 函式就等待哪個程式以及是否阻塞給了我們更多控制。首先,pid 引數允許我們指定想等待的程式ID,值-1表示等待第一個終止的子程式。其次,options 引數允許我們指定附加選項。最常用的選項是 WNOHANG,它告知核心在沒有已終止子程式時不要阻塞。

 

 

參考:

http://www.tc5u.com/linux_unix/1635564.htm

http://blog.163.com/libo_5/blog/static/15696852010324287748/

相關文章