目录
[1. main 函数返回值](#1. main 函数返回值)
[2.调用 exit](#2.调用 exit)
[3.调用 _exit](#3.调用 _exit)
[<1>:wait --- 系统调用](#<1>:wait --- 系统调用)
进程退出场景:
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
一:进程常见的退出方法
正常终止(通过 echo $? 查看进程退出码):
<1>: 从 main 返回
<2>: 调用 exit
<3>: 调用_exit
异常退出:
ctrl + c , 信号终止
1. main 函数返回值
bash
echo $?
?:保存的是最近一个子进程执行完毕时的退出码。
测试代码:
cpp
#include<stdio.h>
int main()
{
printf("hahahagehe\n");
return 10;
}
编译并运行代码,然后查看进程的退出码:
上述现象表明:main 函数的退出码是可以被父进程获取的,用来判断子进程的运行结果。
在C语言中我们学习过错误码,那么错误码和退出码有什么区别呢???
错误码:通常是衡量一个库函数/一个系统调用一个函数的调用情况,当失败的时候,用来衡量函数进程出错的出错原因。
退出码:通常是一个进程退出的时候,它的退出结果。
2.调用 exit
通过 man 2 exit ,命令 exit 信息
通过下述示例对比进行分析:
在其他函数中进行 return ,表示的是函数调用结束(进程不结束)。
通过 echo $? 查看上述进程的退出码:
我们发现进程的退出码为 10,即函数中 exit 的返回值。由此,我们可以得出结论:
在任意地点调用 exit ,表示进程退出,不进行后续执行。
3.调用 _exit
那么, exit 和 _exit 的区别在哪里???
exit:终止进程的时候,会自动刷新缓冲区
_exit :终止进程的时候,不会自动刷新缓冲区
二:异常问题
进程出异常,本质是进程收到了对应的信号,自己终止了!!! --->
所以一个进程是否出异常,我们只要看有没有收到信号即可。
通过判断进程的退出码。
进程退出的三种场景中,有 :代码运行完毕,结果正确;代码运行完毕,结果不正确;代码异常终止 3 种,其中前两种中的结果是否正确是由我们判断的,那么这个我们指的是谁???
在多进程环境中,我们创建子进程的目的是为了让子进程帮我们做事,我们需要得知子进程把事情做的怎末样(这里的我们指的就是父进程) ---> main 函数的返回值叫做进程的退出码,0表示成功,非0表示失败。
三:进程等待
1.概念
通过 wait / waitpid 的方式,让父进程(一般)对子进程进行资源回收的等待过程。
2.进程等待的必要性
<1>:解决子进程僵尸问题带来的内存泄漏
<2>:父进程创建子进程,是要子进程来帮忙完成任务,子进程任务完成的怎末样,父进程要知道,通过进程等待的方式,获取子进程退出的信息 --- 两个数字! --- 不是必须的,但是系统需要提供这样的基础功能。
3.进程等待的方法
<1>:wait --- 系统调用
wait --- 等待任意一个子进程
返回值: 成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。
测试下述示例:
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int ret = 5;
while(ret)
{
printf("i am child\n");
sleep(1);
ret--;
}
exit(0);
}
else
{
sleep(10);
//父进程
pid_t rid = wait(NULL);
if(rid == id)
{
printf("wait success\n");
}
}
return 0;
}
监视命令行脚本:
bash
while :;do ps ajx|head -1 && ps ajx | grep myprocess | grep -v grep;sleep 1;echo "#############################################################";done
测试现象为:
QQ录屏20230110123134
wait :进程等待能回收子进程僵尸状态 Z ---> X,如果子进程根本就没有退出,父进程就必须在 wait 上进行堵塞等待,直到子进程僵尸, wait 自动回收,返回。
一般而言父子进程谁先运行不知道,但是最后一般都是父进程最后退出!!!
<2>:waitpid
waitpid --- 获取退出信息
返回值:
- 当正常返回的时候waitpid返回收集到的子进程的进程ID;
- 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
- 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
- Pid=-1,等待任一个子进程。与wait等效。
- Pid>0.等待其进程ID与pid相等的子进程。
status:
- WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
- WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
- WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
对 wait 示例中的代码稍作修改,观察现象:
获取子进程 status
- wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
- status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
测试示例(代码跑完结果不正确):
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int ret = 3;
while(ret)
{
printf("i am child\n");
sleep(1);
ret--;
}
exit(8);
}
else
{
printf("wait before\n");
int status = 0;
//父进程
pid_t rid = waitpid(id,&status,0);
printf("wait after\n");
if(rid == id)
{
printf("wait success: pid: %d,rid: %d,exit sig: %d,exit code: %d\n",getpid(),rid,status&0x7F,(status >> 8)&0xFF);
}
sleep(5);
}
return 0;
}
运行效果:
示例(代码跑完,结果正确):
cpp
if(id == 0)
{
//子进程
int ret = 3;
while(ret)
{
printf("i am child\n");
sleep(1);
ret--;
}
exit(0);
}
运行效果:
示例(代码异常):
cpp
if(id == 0)
{
//子进程
int ret = 3;
int a = 5;
while(ret)
{
printf("i am child\n");
sleep(1);
a /= 0;
ret--;
}
}
运行效果:
上述示例,出异常,但退出码为0!!!
说明一个进程异常了(收到信号),它的退出码就没有意义了。
那么如何判断适度收到了信号呢???通过 kill -l 查看,exit sig = 0; 表示正常。
父进程如何得知子进程的退出信息?
wait / waitpid (系统调用接口)
子进程退出的时候,要修改状态Z。并将子进程的退出信号和退出码写入 pcb 中,task_struct -->
exit_code,exit_signal ---> int * stausp 指向 int status。
options:
0:阻塞等待
WNOHANG:等待的时候,以非阻塞的方式等待
- rid > 0 ,等待成功 break
- rid = 0 ,等待是成功的,但是对方还没有退出 --> 做自己的事情。--- 循环
- rid < 0 ,等待失败 break
测试代码(正常退出):
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int ret = 3;
while(ret)
{
printf("i am child,pid: %d ,ppid: %d ,ret: %d\n",getpid(),getppid(),ret);
sleep(1);
ret--;
}
exit(0);
}
//父进程
while(1)
{
int status = 0;
pid_t rid = waitpid(-1,&status,WNOHANG);
if(rid > 0)
{
printf("child quit success,exit code:%d ,exit sig:%d\n",(status>>8)&0xFF,status&0x7F);
break;
}
else if(rid < 0)
{
printf("wait failed!!!\n");
break;
}
else
{
printf("--------------------------------------------------\n");
//等待成功,但子进程没有退出
printf("child is alive,wait again,father do other thing ....\n");
//父进程做自己的事情
printf("--------------------------------------------------\n");
}
sleep(1);
}
return 0;
}
运行效果为:
修改 waitpid 部分的内容:
cpp
pid_t rid = waitpid(100,&status,WNOHANG);
运行效果为: