一.进程退出
1.1进程退出的几种方式
有几种方式可以使进程退出:
-
正常退出(Normal Exit):进程在执行完毕后,可以调用
exit()
函数来正常退出。exit()
函数会执行一些清理操作,关闭文件描述符等,并将退出状态码传递给操作系统。通常,返回0表示进程成功退出,非零值表示进程出现错误。 -
异常退出(Abnormal Exit):进程在执行过程中,如果遇到无法继续运行的异常情况,可以选择异常退出。比如,通过调用
abort()
函数来终止程序,或者通过收到无法处理的信号而强行终止进程。异常退出会导致操作系统终止进程并产生一个称为核心转储(Core Dump)的调试文件,用于分析异常退出的原因。 -
子进程退出(Child Process Exit):进程可以使用
fork()
函数创建子进程,子进程完成工作后,通过调用exit()
函数退出。这样,在父进程中可以通过wait()
或waitpid()
函数等待子进程的退出并获取退出状态。父进程还可以通过WIFEXITED
和WEXITSTATUS
宏来判断子进程是正常退出还是异常退出,并获取退出状态码。 -
直接终止(Termination):进程可以调用
_exit()
函数或者使用系统调用exit_group()
来直接终止进程。与exit()
不同,直接终止不会执行清理操作,而是立即终止进程,不返回任何状态给操作系统。
需要注意的是,无论是正常退出、异常退出还是直接终止,进程退出后会释放其占用的系统资源,包括文件描述符、内存等。同时,父进程可以通过相关机制(如wait()
函数)获取子进程的退出状态,以便进行进程间的通信和资源回收。
1.2exit和 _exit和return的区别
exit()
、_exit()
和return
在进程退出方面有一些区别:
-
exit()
函数用于正常退出进程。当调用exit()
函数时,它会执行一些清理操作(如关闭打开的文件描述符)并终止进程。它还通过返回一个退出状态码将控制权返回给操作系统。exit()
函数可用于任意函数中,通过调用它可以使整个进程退出。 -
_exit()
系统调用是直接终止进程。当调用_exit()
函数时,进程立即终止,而不执行任何清理操作。与exit()
不同,_exit()
函数不返回任何状态给操作系统,而是直接终止进程。通常,_exit()
函数用于异常情况或在需要立即终止进程而不进行清理操作的情况下。 -
return
语句用于退出函数或方法,并将控制权返回到调用该函数或方法的位置。return
语句只能用于函数或方法的内部,而不能使整个进程退出。当函数或方法中的所有代码都执行完毕或遇到return
语句时,该函数或方法的执行将结束,控制权将返回给调用者。
需要注意的是,无论是exit()
、_exit()
还是return
,它们都会终止当前进程的执行。但是,exit()
和_exit()
之间还存在一些差别,主要体现在清理操作和操作系统状态码的返回。
另外,需要注意的是,exit()
和_exit()
都位于<unistd.h>
头文件中,需要通过包含该头文件来使用这两个函数。而return
是C和C++中的语言关键字,在函数或方法中直接使用即可。
1.3等待子进程退出
父进程回收子进程的主要原因有以下几点:
-
避免僵尸进程:当一个子进程先于父进程退出时,称为僵尸进程。僵尸进程不会被系统释放资源,占用系统的进程表项,如果产生大量的僵尸进程,可能会导致系统资源的浪费。父进程回收子进程可以避免僵尸进程的存在。
-
获取子进程的退出状态:通过回收子进程,父进程可以获取子进程的退出状态。这对于父进程来说可以根据子进程的状态来决定后续的操作,比如重新创建子进程、打印日志、发送通知等。
-
资源回收:当子进程创建了一些资源,如打开文件、分配内存等,在子进程退出后,这些资源可能需要被释放,以防止资源泄漏。父进程回收子进程时可以执行相应的资源回收操作,确保系统资源的合理利用。
为了回收子进程,父进程通常会使用wait()
或waitpid()
函数。这些函数会阻塞父进程,直到一个子进程退出为止,并返回子进程的退出状态。父进程可以通过这些函数获取子进程的退出状态,并采取相应的措施。
需要注意的是,父进程只能回收它自己创建的直接子进程,而不能回收其他进程创建的子进程。此外,如果父进程不回收子进程,子进程退出后会成为孤儿进程,由init
进程(进程ID为1)接管它们的回收工作。
wait函数
wait()
函数是一个系统调用,用于父进程等待子进程的状态改变并获取子进程的退出状态。它可以阻塞父进程,直到一个子进程退出或被终止,然后返回子进程的进程ID和退出状态。
wait()
函数的原型如下:
c
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
其中,status
是一个指向整型变量的指针,用于存储子进程的退出状态。
wait()
函数的工作原理如下:
-
如果父进程没有正在运行的子进程,则
wait()
会阻塞父进程,直到有一个子进程退出或被终止。 -
如果父进程有一个或多个正在运行的子进程,则
wait()
会挂起父进程的执行,直到其中一个子进程退出或被终止。 -
当一个子进程退出时,
wait()
会返回子进程的进程ID,并将子进程的退出状态通过status
指针传递给父进程。 -
如果父进程指定了
status
参数(即status
不为NULL
),则子进程的退出状态将被存储在status
指向的位置。
wait()
函数返回的进程ID可以用于进一步处理子进程的退出状态,比如进行资源回收、记录日志等。
需要注意的是,wait()
函数只能等待直接子进程的状态改变,即子进程必须是wait()
函数调用前的直接子进程。如果父进程希望等待任意子进程的状态改变,可以使用waitpid()
函数,并将其第一个参数设置为-1
。
此外,wait()
函数对于子进程的退出状态有一些宏定义,可以通过这些宏来判断子进程的退出状态,例如:
WIFEXITED(status)
:如果子进程正常退出,则返回真。WEXITSTATUS(status)
:如果WIFEXITED
为真,则返回子进程的退出状态码。WIFSIGNALED(status)
:如果子进程因收到信号而异常终止,则返回真。WTERMSIG(status)
:如果WIFSIGNALED
为真,则返回导致子进程终止的信号编号。
通过这些宏定义可以方便地判断子进程的退出状态,并进行相应的处理。
示例
为了等待子进程退出,可以使用wait()
或waitpid()
函数。下面是使用wait()
函数等待子进程退出的示例:
c
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
pid_t pid;
int status;
pid = fork(); // 创建子进程
if (pid < 0) {
// 创建子进程失败
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程逻辑
printf("Child process with PID %d\n", getpid());
sleep(3); // 模拟子进程执行一段时间
printf("Child process exiting\n");
exit(EXIT_SUCCESS);
} else {
// 父进程逻辑
printf("Parent process with PID %d\n", getpid());
printf("Waiting for child process with PID %d\n", pid);
wait(&status); // 等待子进程退出
if (WIFEXITED(status)) {
printf("Child process exited with status: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child process exited due to signal: %d\n", WTERMSIG(status));
}
printf("Parent process exiting\n");
exit(EXIT_SUCCESS);
}
}
在上面的示例中,父进程通过调用wait(&status)
来等待子进程退出。wait()
函数会阻塞父进程,直到一个子进程退出为止。status
参数是一个指向整型变量的指针,用于存储子进程的退出状态。
父进程通过判断status
来获取子进程的退出状态。使用WIFEXITED(status)
宏可以检查子进程是否正常退出,如果返回真,则可以使用WEXITSTATUS(status)
宏来获取子进程的退出状态码。如果子进程因接收到信号而异常终止,可以使用WIFSIGNALED(status)
宏来检查,并使用WTERMSIG(status)
宏来获取导致子进程终止的信号编号。
注意,在父进程等待子进程退出之前,子进程需要先执行适当的业务逻辑和退出步骤。在示例中,子进程会休眠3秒后退出。
waitpid函数
waitpid
是一个在操作系统中用来等待子进程结束的函数。它的原型如下:
c
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
waitpid
的作用是等待指定进程结束,并返回该子进程的进程ID。它可以用来阻塞当前进程,直到子进程结束为止。该函数的参数包括:
pid
:指定要等待的进程ID。pid > 0
:等待进程ID为pid
的子进程。pid = -1
:等待任意子进程。pid = 0
:等待和当前进程在同一个进程组中的任意子进程。pid < -1
:等待进程组ID为-pid
的任意子进程。
status
:用于保存子进程的终止状态,它是一个指向int
类型的指针。通过该指针,可以获取子进程的退出状态、终止信号等信息。options
:用于指定等待子进程结束的一些选项。- 指定为
0
,表示默认行为,即阻塞等待子进程结束。 - 指定为
WNOHANG
,表示非阻塞等待子进程结束,即如果指定的子进程还未结束,立即返回。 - 指定为
WUNTRACED
,表示在子进程进入暂停执行(停止)状态后立即返回。 - 可以使用
options
的位掩码组合多个选项。
- 指定为
waitpid
函数的返回值表示子进程的状态,它可以通过一些宏来判断子进程的状态,如:
WIFEXITED(status)
:如果子进程正常退出,返回真。WEXITSTATUS(status)
:如果子进程正常退出,返回子进程的退出状态。WIFSIGNALED(status)
:如果子进程是因为某个信号而终止的,返回真。WTERMSIG(status)
:如果子进程是因为某个信号而终止的,返回终止信号的编号。WIFSTOPPED(status)
:如果子进程暂停执行(停止),返回真。WSTOPSIG(status)
:如果子进程暂停执行(停止),返回停止信号的编号。
总之,waitpid
函数提供了一种方便的方式来等待子进程结束,并获取子进程的退出状态或终止信号等信息。