#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
僵尸进程。进程结束后放弃了几乎所有的内存空间,没有任何可以执行的代码,也不能被调度,仅仅在进程列表中保留着一个位置,记载着该进程的退出状态等信息供其他进程收集,除此之外僵尸进程不再占有任何内存空间。
当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态,此时是一个僵尸进程。(正常终止→退出值;异常终止→终止信号)
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。
在shell终端执行的命令和程序,都是shell通过fork产生的子进程来完成的,因此在命令和程序结束后,shell终端进程也可以获取子进程的结束状态,通过特殊命令$?查看,如果为0,表示正常结束,如果为非0值,表示出现了错误。Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:1.阻塞等待子进程退出(父进程调用该函数后,如果子进程没死,则父进程阻塞并等待子进程结束);2. 获取子进程结束状态(退出原因);3.回收子进程残留资源 (回收子进程结束后残留在内核中的PCB资源),然后彻底清除掉这个进程。
进程的一生。随着fork的成功执行,一个新的子进程产生,但此时它还只是父进程的克隆。然后随着exec函数族,新进程脱胎换骨,开始独立执行一个全新的程序,并完全替代了原有的父进程。进程结束,可以是自杀,也可以是他杀。自杀:遇到main函数的最后一个“}”、在main函数中使用return或者调用exit和_exit;他杀:被其它进程通过另外一种方式杀掉。进程自杀(属于正常死亡),即使程序出错,但是进程自身结束了自己,可以通过return和exit赋给进程结束后一个值,通过wait或waitpid函数来获取该值,从而知道该进程自杀的原因,是否出错(一般0代表正常,非0代表出错),当然可以返回任何值,由wait函数可以获取到。进程如果属于他杀,则为异常结束,在linux操作系统中,所有的异常终止都是由于某一个信号导致的(通过kill –l参数可以查看所有的信号及信号编号),如在程序中给一个常量赋值,会发生段错误,此时进程会收到段错误相应的信号,从而被终止;除数为0时,进程也会收到一个信号(浮点数例外),从而被终止。而终止进程的信号会记录在终止信息中,供父进程调用wait函数获取。进程死掉后,会留下一具僵尸,wait和waitpid则去收集信息,清理尸体。
exit和_exit这两个函数(都是系统调用)都有一个整型形参,用于传递进程结束时的状态(正常结束还是出错结束)。一般,0表示正常结束,非0值(如1)表示出现了错误,非正常结束。可以利用wait系统调用接收子进程的返回值,从而针对不同的情况进行不同的处理。
pid_t wait(int *status);
status为传出参数,用于获取子进程结束时的状态(原因)。函数执行成功,则返回清理掉的子进程ID,如果失败则返回-1。进程一旦调用了wait,就立即阻塞自己,由wait自动分析该进程的某个子进程是否已经退出,如果找到了一个已经结束的子进程,则收集其信息,并将其清除掉并返回该子进程ID;如果没有找到这样的一个子进程,则会一直阻塞到那里,直到有一个出现为止。如果父进程没有子进程,则直接返回-1,表示出错。
如果对子进程如何死掉的不关心(不需要收集其残留的信息),只想把僵尸进程消灭掉,可以将函数形参设置为NULL,即pid=wait(NULL);
可使用wait函数传出参数status(整型值)来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非0 → 表示进程正常结束
WEXITSTATUS(status) 如上宏为真(进程正常结束),使用此宏 → 获取进程退出状态 (exit/_exit/return的参数)
2. WIFSIGNALED(status) 为非0 → 表示进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。可以通过kill –l命令查看编号的详细含义。
3. WIFSTOPPED(status) 为非0 → 表示进程处于暂停状态(进程没有终止,只是暂停运行,如挂起或阻塞)。
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
//代码举例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(void)
{pid_t pid, wpid;int status;pid = fork( );if(pid == 0){printf("---child, my parent= %d, going to sleep 10s\n",getppid( ));sleep(10);printf("-------------child die--------------\n");exit(77); //return 77; _exit(77); //子进程正常结束,置其结束状态为77}else if(pid > 0){wpid = wait(&status); //父进程阻塞等待子进程结束if(wpid==-1){perror("wait");exit(1); //置结束状态为1}if( WIFEXITED(status) ) //如果子进程正常结束{printf( "The child process exit with %d\n",WEXITSTATUS(status));}else if( WIFSIGNALED(status) ) //如果子进程异常结束{printf( "The child process was killed by %dth signal\n",WTERMSIG(status));}while (1){printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);sleep(1);} //父进程一直运行}else{perror("fork");exit(1);}return 0;
}
[root@localhost wait]# ./wait_test
---child, my parent= 30770, going to sleep 10s
-------------child die--------------
The child process exit with 77 //获取了结束状态为77
I am parent, pid = 30770, myson = 30771
I am parent, pid = 30770, myson = 30771
I am parent, pid = 30770, myson = 30771
[root@localhost wait]#ps aux
root 30770 0.0 0.0 4164 356 pts/0 S+ 18:11 0:00 ./wait_test
root 30791 0.0 0.0 123360 1380 pts/2 R+ 18:12 0:00 ps aux
可见,只有父进程在一直运行,子进程不存在了,也不存在僵尸进程,子进程痕迹被完全清除。
在子进程运行期间,利用kill -9 将其杀掉,则为异常结束:
[root@localhost wait]#ps aux
[root@localhost wait]# kill 30830
[root@localhost wait]# ./wait_test
---child, my parent= 30829, going to sleep 60s
The child process was killed by 15th signal
I am parent, pid = 30829, myson = 30830
I am parent, pid = 30829, myson = 30830
I am parent, pid = 30829, myson = 30830
I am parent, pid = 30829, myson = 30830
利用kill -l 命令可以查看编号为15的信号为 15) SIGTERM
总结,进程正常结束,父进程利用wait函数回收其结束状态,状态值通过return、exit和_exit返回(一般,如果程序正常则为0,出错设非0,但不是必须的),如果进程结束没有指明其值,如main函数中没有return语句且正常结束,则会有默认值(正常0,出错非0)。注意,return、exit和_exit返回的值必须不超过128,可通过wait函数获取。如果函数异常结束,wait函数回收其终止原因(信号编号)。