退出进程
在 Linux 中,共有 8 种进程的退出方法,包括 5 种正常的方法和 3 种异常的方法。
通常来说 Linux 的应用代码会调用 exit 系列函数来退出一个进程。
#include<stdlib.h>
#include<unist.h>
void exit(int status);
void _exit(int status);
void _Exit(int status);
exit 系列函数没有返回值,使用一个终止状态的整型变量作为参数,Linux 内核会对这个终止状态进行检查:当异常终止时,Linux 内核会直接产生一个终止状态字,描述异常终止的原因,可以通过 wait 或者 waitpid 函数来获得终止状态字;父进程也可以通过检查终止状态来获得子进程的状态。
如果是以下三种状态:
- 在调用 exit 系列函数的时候不带终止状态。
- main 函数执行了一个无返回值的 return。
- main 函数的返回值不是一个整型。
则 Linux 会认为该进程的终止状态是未定义的。
如果 main 函数的返回值定义为整型并且 main 函数是执行到最后一条语句返回, 则该进程的终止状态是0。
在 main 函数中调用 return 语句返回,绝大多数等效于调用 exit 系列函数。
这两个函数的调用过程如图
- _exit 函数:直接使进程停止运行,清除其使用的内存空间,清除其在内核中的各种数据结构。
- exit 函数:在 _exit 函数的基础上做了一些包装,在执行退出之前加了若干道工序。
这两个函数的最大区别在于:前者在调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件;后者直接使进程停止运行,清除其使用的内存空间,销毁其在内核中的各种数据结构,即图中的 “清理 I/O 缓冲” 一项。
销毁进程
当一个进程使用 exit 系列函数退出时,会在内存中保留部分数据以供父进程查询,同时也会产生一个终止状态字,然后 Linux 内核会发出一个SIGCHLD 信号通知父进程,因为子进程的结束对于父进程是异步的,所以这个 SIGCHLD 信号对于父进程也是异步的,父进程可以不响应。
父进程对于退出之后的子进程的默认状态是不处理的,但是这样会导致系统中的僵尸进程浪费了系统资源,此时应该调用 wait 函数或 waitpid 函数对这些僵尸进程进行处理。
在调用 wait 或者 waitpid 函数之后可能存在如下三种情况:
- 如果该父进程的所有子进程都还在运行,则阻塞父进程自身以等待子进程的运行结束。.
- 如果有一个子进程已经结束, 则父进程取得该子进程的终止状态,并且立即返回。
- 如果该父进程没有任何子进程,则立即出错返回。
对 wait 和 waitpid 函数的标准调用格式说明如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
1. wait函数
- 如果 wait 函数调用成功则返回子进程的标识符,如果失败则返回-1。
- 其中参数 status 是一个整型指针,可以用于存放子进程的终止状态,也可以定义为一个空指针。
- wait 函数和 waitpid 函数不同,在有一个子进程终止之前,wait 函数让父进程阻塞以等待子进程退出,而 waitpid 有一个参数可以让父进程不阻塞,并且在一个父进程有多个子进程的情况下,如果其中有一个子进程退出则会返回该子进程的进程标识符。
wait 函数返回的终止状态的宏
宏 | 说明 |
---|---|
WIFEXITED(status) | 当子进程正常结束时返回为真 |
WIFSIGNALED(status) | 当子进程异常结束时返回为真 |
WEXITSTATUS(status) | 当WIFEXITED(status)为真时调用,返回状态字的低8位 |
WTERMSIG(status) | 当WIFSIGNALED(status)为真时调用,返回引起终止的信号代号 |
2. waitpid 函数
在使用 wait 函数的时候,如果父进程的任何一个子进程返回则 wait 函数返回,而 waitpid 函数可以通过参数来指定需要等待的子进程。
waitpid 函数的参数 pid 用于对子进程进行相应的筛选。
- pid > 0:只等待进程 ID 为 pid 的子进程,不管其他已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid 就一直等待下去。
- pid = -1:等待任何一个子进程退出,没有任何限制,此时 waitpid 等价于 wait。
- pid = 0:等待同一个进程组中的任何子进程,如果某一子进程已经加入了其他进程组,则 waitpid 不会对它做任何理睬。
- pid < -1:等待一个指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。
waitpid 函数的参数 options 用于进一步控制 waitpid 函数的操作,其可以是0,也可以是 WNOHANG 和 WUNTRACED 两个选项之一,或者是使用 “I” 符号连接的“或”操作。
- WNOHANG:如果由 pid 指定的子进程并不是立即可用的,则 waitpid 函函数不阻塞,此时返回“0”。
- WUNTRACED:如果其实现支持作业控制,而由 pid 指定的任意子进程已经处于暂停状态,并且未报告过,则返回其状态。
对于 waitpid 函数而言,如果指定的进程或者进程组不存在,或者参数 pid 指定的进程不是父进程所调用的子进程,都会出错。
总体而言,waitid 函数提供了 wait 函数所没有的如下三个功能:
- 能够等待指定的一个进程结束。
- 能够不阻塞父进程获得子进程的状态。
- 支持作业控制。
使用 waitpid 函数退出进程
#include <sys/types.h> #include <sys/wait.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
if((pid=fork())<0) //创建子进程失败
{
perror("创建子进程失败!\n");
exit(0);
}
else if(pid==0) //进入子进程
{
if((pid=fork())<0)
{
perror("创建子进程失败!\n");
exit(0);
}
else if(pid>0)
{
exit(0);
}
else
{
sleep(2);
printf("这是第二个子进程,parent pid=%d\n",getppid());
exit(0);
}
}
if(waitpid(pid,NULL,0)!=pid)
{
perror("waitpid 销毁进程失败!\n");
exit(0);
}
exit(0);
}
编译运行,可以看到在退出了第2个子进程后会停止运行一直等待,此时可以使用 ctrl + c 退出。
[cassie@localhost 练习]$ gcc waitpid.c -o waitpid
[cassie@localhost 练习]$ ./waitpid
[cassie@localhost 练习]$ 这是第二个子进程,parent pid=1
^C