[操作系统] 进程等待
进程等待(Process Waiting)是操作系统中父进程用于管理和同步子进程的重要机制。根据你的图片内容,我们可以分几个部分来讲解进程等待的相关知识。
为什么需要进程等待?
当一个进程创建了子进程后,子进程会独立运行。如果父进程不进行等待,它可能会比子进程更早终止,导致子进程变成僵尸进程(Zombie Process),即进程已经终止但父进程没有回收它的资源,造成内存泄漏。
另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,“杀⼈不眨眼”的kill -9 也⽆能为⼒,因为谁也没有办法杀死⼀个已经死去的进程。
我们需要知道⽗进程派给⼦进程的任务完成的如何。如,子进程是否完成,结果是否正确。
因此,父进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息
进程等待的方式
进程等待主要有两种方式:
pid_t **wait**(int *status);
** 函数**pid_t **waitpid**(pid_t pid, int *status, int options);
** 函数**
wait
函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
wait
会阻塞父进程,直到任意一个子进程退出。- 返回退出的子进程的 PID。
status
指向一个整数变量,用于存储子进程的退出状态。
示例代码
int status;
pid_t pid = wait(&status);
这行代码会让父进程暂停执行,直到有一个子进程结束,并将其退出状态存储到 status
变量。
waitpid
函数
pid_t waitpid(pid_t pid, int *status, int options);
waitpid
提供比wait
更灵活的进程等待方式。pid
参数决定了要等待哪个子进程:pid > 0
:等待指定的子进程。pid == -1
:等待任意子进程(等价于wait
)。pid == 0
:等待与当前进程同组的任何子进程。pid < -1
:等待特定进程组的子进程。
options
参数:0
:默认阻塞等待子进程退出。WNOHANG
:非阻塞模式,如果没有子进程退出,则立即返回0
。WUNTRACED
:等待子进程停止(如Ctrl+Z
)。WCONTINUED
:等待已经继续执行的子进程(如SIGCONT
信号)。
示例代码
int status;
pid_t pid = waitpid(-1, &status, 0);
此代码与 wait
类似,等待任意子进程结束。
如何获取子进程的退出状态?
在wait
和waitpid
参数中的int *status
是输出型参数,当做位图来看待,通过传入变量地址然后经过函数处理后得到对应结果,传入NULL
则表示不关心子进程退出状态信息,否则操作系统会通过该参数将子进程的退出信息返回给父进程。
int
有四字节,32比特位, waitpid
返回的 status
值只使用了 低 16 位(2 字节) 来存储进程的退出状态或终止信号信息。
具体的信息存储如图所示。
- 正常终止:如果进程是
**exit(n)**
退出的,- 高 8 位存储
exit(n)
返回的值n
。 - 低 7 位全 0,表示不是被信号终止的。
- 高 8 位存储
- 如果进程是被信号终止的:
- 低 7 位存储终止信号编号(如
SIGKILL=9
)。 - 第 7 位(bit 7)表示是否生成
core dump
。(后续讲解) - 高 8 位无意义。
- 低 7 位存储终止信号编号(如
也就是说:
- 正常退出:status =
(exit_code << 8)
- 信号终止:status =
signal_number | (core_dump_flag << 7)
代码示例:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{
pid_t pid;
if ( (pid=fork()) == -1 )
perror("fork"),exit(1);
if ( pid == 0 )
{
sleep(20);
exit(10);
}
else
{
int st;
int ret = wait(&st);
if ( ret > 0 && ( st & 0X7F ) == 0 )
{
// 正常退出
printf("child exit code:%d\n", (st>>8)&0XFF);
}
else if( ret > 0 ) {
// 异常退出
printf("sig code : %d\n", st&0X7F );
}
}
}
测试结果:
# ./a.out #等20秒退出
child exit code:10
# ./a.out #在其他终端kill掉
sig code : 9
信息的宏解析
子进程退出时,status
变量会存储一些信息,可以使用以下宏解析:
WIFEXITED(status)
:如果子进程正常退出,返回非零值。WEXITSTATUS(status)
:获取子进程的退出码。
WIFSIGNALED(status)
:如果子进程是被信号终止的,返回非零值。WTERMSIG(status)
:获取终止子进程的信号编号。
WIFSTOPPED(status)
:如果子进程因SIGSTOP
进入暂停状态,返回非零值。WSTOPSIG(status)
:获取导致暂停的信号编号。
示例代码
if (WIFEXITED(status))
{
printf("Child exited with status %d\n", WEXITSTATUS(status));
}
else if (WIFSIGNALED(status))
{
printf("Child terminated by signal %d\n", WTERMSIG(status));
}
非阻塞等待
waitpid()
参数中WNOHANG
选项可用于非阻塞模式,即如果没有子进程退出,则 waitpid
立即返回 0
,不会阻塞父进程。
示例代码
while (1)
{
int status;
pid_t pid = waitpid(-1, &status, WNOHANG);
if (pid > 0)
{
printf("Child %d exited\n", pid);
}
else if (pid == 0)
{
printf("No child exited yet\n");
}
sleep(1); // 避免 CPU 过载
}
进程等待的示意图
- 父进程调用
wait/waitpid
等待子进程结束 - 子进程执行任务后终止
- 父进程回收子进程资源,避免僵尸进程
进程等待的应用场景
- 防止僵尸进程:确保子进程结束后,父进程回收其资源。
- 管理多个子进程:
waitpid
可以指定等待特定的子进程,适用于多子进程管理。 - 避免阻塞主进程:使用
WNOHANG
选项,使主进程可以继续执行其他任务。
总结
wait
适用于简单场景,等待任意子进程结束。waitpid
提供更精细的控制,支持非阻塞模式。WIFEXITED
等宏可以解析子进程的退出状态。WNOHANG
选项可以实现非阻塞等待,防止父进程卡死。