引入:
为什么?是什么?怎么办
是什么?
进程等待是指父进程暂停自己的执行,直到某个特定的子进程结束或发生某些特定的事件。
为什么?
- 僵尸进程刀枪不入,不可被杀死,存在内存泄露
- 获取进程的执行情况,知道我布置给子进程的任务,它完成的怎么样了–可选
怎么办
wait 函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
测试:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main (
void
)
{
pid_t id = fork();
if (id == 0) {
// child
while (1) {
printf("我是子进程,我正在运行... Pid: %d\n", getpid());
sleep(1);
}
}
else {
printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
sleep(20); // 为了便于观察,我们让父进程休眠20s
// 苏醒后,父进程执行 wait,耐心地等待子进程
pid_t ret = wait(NULL); // 暂且将status参数设置为NULL
if (ret < 0) {
printf("等待失败!\n");
}
else {
printf("等待成功!\n"); // 此时 Z → X
}
sleep(20); // 子进程退出后,再让父进程存在一段时间
}
}
为什么是交替型,不是一个执行完?//了解循环的细节
在 fork
创建子进程后,父进程会首先执行并打印这条信息,其中 <父进程的pid>
是父进程的进程ID。截不下就没截了
- 父进程打印一次消息后休眠20秒:
- 父进程仅在创建子进程后打印一次消息,然后休眠20秒等待观察子进程的活动,不再进行其他操作。
通过这种方式,父进程和子进程能够并发地执行各自的任务,并且我们通过延迟父进程的退出能观察到子进程的连续活动。
整个程序执行流程如下:
-
父进程打印:
yamlCopy code 我是父进程: pid: 1234,我将耐心地等待子进程!
-
子进程每秒打印一次:
yamlCopy code 我是子进程,我正在运行... Pid: 5678
(此循环持续到子进程被终止)
-
通过终端输入
kill 5678
终止子进程。 -
父进程20秒后苏醒并等待子进程结束后,打印:
textCopy code 等待成功!
之后,父进程再休眠20秒并继续存在一段时间后退出
waitpid
刚才讲的 wait 并不是主角,因为其功能比较简单,在进程等待时用的更多的是 waitpid
waitpid 可以把 wait 完全包含,wait 是 waitpid 的一个子功能。
参数:
-
pid
:要等待的子进程的进程ID。根据传入的值可以指定等待任何子进程、特定进程ID的子进程、任何同一进程组的子进程或者任何同一会话的子进程。
- 如果
pid
大于零,waitpid
将等待指定进程ID的子进程结束。 - 如果
pid
等于 -1,waitpid
将等待任意子进程结束,等同于wait
函数。
- 如果
-
status
:一个指向整型的指针,是一个输出型参数,它将用于存储子进程的终止状态。 -
options
:用于指定等待行为的附加选项。
- 传入0表示以默认行为等待子进程。(阻塞等待中再讲)
返回值:
- 如果
waitpid
成功,返回值是已终止子进程的进程ID。 - 如果出现错误,返回-1,并且会设置
errno
变量来指示具体错误原因。
Z状态,其本质上就是将自己的 task_struct 维护起来(代码可以释放,但是 task_struct 必须维护)。所谓的 wait/waitpid 的退出信息,实际上就是从子进程的 task_struct 中拿出来的,即 从子进程的 task_struct 中拿出子进程退出的退出码,拷贝到父进程中
status
该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。整数的低 16 位,其中又可以分为 最低八位 和 次低八位(具体细节看图):
1.次低八位:拿子进程退出码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main (
void
)
{
pid_t id = fork();
if (id == 0) {
int cnt = 5; // 循环5次
// child
while (1) {
// 五秒之内运行状态
printf("我是子进程,我正在运行... Pid: %d\n", getpid());
sleep(1);
// 五秒之后子进程终止
cnt--;
if (cnt == 0) {
break;
}
}
exit(233); // 方便辨识,退出码我们设置为233,这是我们的预期结果
}
else {
printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
// ***** 使用waitpid进行进程等待
int status = 0; // 接收 waitpid 的 status 参数
pid_t ret = waitpid(id, &status, 0);
if (ret > 0) { // 等待成功
printf (
"等待成功,ret: %d, 我所等待的子进程退出码: %d\n",
ret,
(status>>8)&0xFF
);
}
}
}
status 并不是整体使用的,而是区域性使用的,我们要取其次低八位。我们可以用 位操作 来完成,将 status右移八位再按位与上 0XFF,即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。
waitpid 经过系统调用,来读取子进程的pcb(eg. task_st…),这是为什么呢
操作系统不相信任何人,父进程用户无法直接读取子进程的 pcb ,要通过系统调用的接口
2.初识 core dump(核心转储)
它是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。目前只需要知道,该信息是用于调试的。
3.最低七位:提取子进程的退出信号
刚才我们讲的 wait/waitpid 和次低八位的时侯,都是关于进程(exit) 的 正常退出。
如果进程 异常退出 呢?我们来模拟一下进程的异常退出。
💬 模拟异常退出的情况,让子进程一直跑,父进程一直等。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main (
void
)
{
pid_t id = fork();
if (id == 0) {
// 子进程一直不退出,父进程会一直等待。
// child
while (1) {
printf("我是子进程,我正在运行... Pid: %d\n", getpid());
sleep(1);
}
exit(13);
}
else {
printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret > 0) { // 等待成功
printf(
"等待成功,ret: %d, 我所等待的子进程退出码: %d\n, 退出信号是: %d",
ret, (status>>8)&0xFF,
status&0x7F
);
}
}
}
现在我们直接 while(1) 死循环让子进程往死里跑,此时父进程由于调用了 waitpid,就会一直等待子进程,父进程就会持续阻塞。
父进程看到子进程kill了,终于可以不用等了,可以给子进程收尸了
可以发现,使用-9号信号kill掉进程时,进程的退出信号就是9,然而当进程由于信号异常终止时,此时进程退出码是无意义的!
所以进程的等待可以理解为是 父进程在等给子进程退出记录
非阻塞轮询
options ->阻塞方式
waitpid(pid,&status,WNOHANG);
WNOHANG
就是wait no hang,hang也就是悬挂,也就是非阻塞,等待子进程死亡,若父进程执行到waitpid
时,子进程还没退出,则函数返回0后接着运行下面的代码,若执行到waitpid
后子进程已经退出则返回退出子进程的pid
假如要期末考试了,我想找小张去自习室帮我复习,小张自己也在复习,我打电话问他什么时候复习完了,可以出门,情况解释如下
WNOHANG
夯住,非阻塞+循环
我给小张间隔打电话寻求帮忙,自己干等在楼下
阻塞式调用
打电话一直不挂
非阻塞轮询+自己的事情(最高效)
间隔打电话,自己也在复习
返回值
pid_t
ret_pid=0–所等待的条件还没有就绪,>0成功返回退出码,<0失败
代码验证
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id<0)
{
perror("fork");
exit(1);
}
if(id==0)//子进程代码
{
int count = 5;
while(count)
{
printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());
sleep(1);
count--;
}
exit(55);//子进程执行完代码后退出
}
//父进程代码
while(1)//循环访问子进程退出情况
{
int wait = waitpid(id,NULL,WNOHANG);
if(wait>0)//子进程退出成功
{
printf("子进程退出成功,子进程pid: %d\n",wait);
break;
}
else if(wait==0)//子进程还没退出,父进程干自己的事情
{
//此处简单模拟父进程干的事情
printf("我是父进程,我现在要干一些别的事情\n");
}
else //等待子进程退出失败
{
perror("waitpid");
exit(1);
}
sleep(1);
}
return 0;
}
⚠️注:这里父进程可以执行任一任务,我使用printf打印,只是为了明显的看到父进程是没有阻塞等待的!
进程退出的宏
为了增加可读性,定义了接口宏,来查找退出码
WEXITSTATUS
和WIFEXITED
,在这之前,我们再思考一个问题:
❓ **思考:**一个进程退出时,可以拿到退出码和推出信号,我们先看谁?
一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。
所以,我们先关注退出信号,如果有异常了我们再去关注退出码。
WEXITSTATUS 宏用于查看进程的退出码,若非 0,提取子进程退出码。
WEXITSTATUS(status)
WIFEXITED 宏用于查看进程是否正常退出,如果是正常终止的子进程返回状态,则为真。
WIFEXITED(status)
waitpid
意义:
-
返回记录子进程内核的数据结构
-
Z->X