目录
1. 引入进程终止
当进程终止时,操作系统做了什么?释放进程申请的相关内核数据结构和对应的数据和代码.本质就是释放系统资源.
1.2 进程终止的常见方式
- 代码跑完,结果正确.
- 代码跑完,结果不正确.
- 代码没有跑完,程序崩溃.
2. 如何终止进程
2.1 main
main函数的返回值是给上一级进程的,用来判断执行结果用的.return 0 这里的0其实一种退出码.
0代表运行的结果正确.非零值代表运行的结果不正确.不同的退出码对应着不同的错误原因.
2.1.1 查看退出码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
for(int number=0;number<150;number++)
{
printf("%d:%s\n",member,strerror(number));
}
return 0;
}
linux下使用 echo $? 以获取最近一个进程,执行完毕的退出码
2.2 exit
需要包含头文件 #include <stdlib.h>
exit在代码的任何地方被调用,都表示直接终止进程.exit是库函数,_exit是系统接口.
调用exit时会调用_exit在内的一系列函数.会执行用户定义的清理函数,冲刷缓冲,关闭流等.
_exit调用时直接终止程序
3. 进程等待
3.1 引入
当子进程退出,父进程不管子进程,子进程就要出于僵尸模式,导致内存泄漏.
父进程创建子进程是用来执行操作的,父进程如何知道执行情况?
3.2 进程等待的方法
等待可以理解成父进程等待子进程进入僵尸状态,一旦子进程进入Z状态,父进程自动回收资源.
如果子进程先执行完毕,只要当父进程执行wait/waitpid函数就可以回收子进程.
3.2.1 wait
#include<sys/types.h>
#include<sys/wait.h>
int status=0;
pid_t ret= wait(&status);
pit_t ret=wait(NULL);
//这里的参数是输出型参数,需要先创建
//用来让上一级获取子进程的退出状态,不关心可用NULL
3.2.2 waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
给的pid>0 该值为需等待的程序的pid, pid=-1 表示等待任意一个子进程,与wait等效.
status 是输出型参数,需要先定义. 当status设置为NULL时 waitpid(pid,NULL,0)等价于wait(NULL)
options 默认为0,表示阻塞等待.
其中status是按照比特位的方式,将32位比特位进行划分使用的。次低八位表示子进程退出的退出码(15~8位),最低七位表示进程接受到的信号。
3.2.3 waitpid 测试代码实现1
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
exit(1); //标识进程运行完毕,结果不正确
}
else if(id == 0)
{
//子进程
int cnt = 5;
while(cnt)
{
printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());
sleep(1);
cnt--;
}
exit(15);
}
else
{
//父进程
printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
int status = 0;
pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!
if(ret > 0)
{
// 0x7F -> 0000.000 111 1111
printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\
ret, status & 0x7F ,(status >> 8)&0xFF);
//0x7f --> 0000..0000 0111 1111
//0xff --> 0000...000 1111 1111
}
}
}
进程异常退出,或者崩溃(野指针、越界等),本质是操作系统杀掉了进程(kill命令等),通过发送信号的方式。
信号如果不是零,证明不正常跑完,退出码无意义。
退出码也是子进程的数据,当进程进入僵尸状态时,会至少保留该进程的PCB信息。task_struct里面保留了任何进程退出时的结果信息。wait/waitpid本质是读取了子进程的task_struct结构体。
而wait和waitpid是系统调用,系统有权限去访问内核数据结构对象(task_struct)。
3.2.4waitpid 测试代码实现2
父进程视角获得子进程的有退出结果信息可以用相关宏实现。
int main()
{ //..
//..
//父进程
int status=0;
pid_t result=waitpid(id,&status,0);//等待成功返回子进程pid
if(result>0)
{
if(WIFEXITED(status))
{
printf("子进程执行完毕,子进程的退出码是:%d\n",WEXITSTATUS(status));
}
else
{
printf("子进程异常退出:%d\n ",WIFEXITED(status));
}
}
}
3.3 进程等待的过程
3.3.1 阻塞等待
options=0时为阻塞等待。意思是只有子进程退出的时候,父进程才能够调用waitpid函数。阻塞等待的好处是可以让进程退出具有一定的顺序,可在父进程中进行更多的收尾工作。
3.3.2 非阻塞等待
options=WNOHANG时为非阻塞等待。意思是子进程还未退出时,父进程可以等一等,先处理其他事情。
3.3.3 HANG
#define WNOHANG 1
HANG一般是指运行停止的状态,即没有被CPU调度。HANG状态要么是在阻塞状态,要么就是等待被调度。WNOHANG 意思为wait调用时不进入阻塞状态。
模拟父进程调用wait的过程
waitpid(child_pid, &status, options)
{ if(status中的信息是 已退出)
{
返回子进程的pid
}
else if(status中的信息是没退出)
{
if(options==0)
挂起父进程 //将父进程pcb结构体放入等待队列 进入阻塞状态
//后面的代码不执行了,当条件满足的时候,父进程在之前运行到的位置被唤醒
else if(options==WNOHANG)
//这里可以继续执行代码 不阻塞进程
return 0;//正常退出 waitpid这个系统调用立马返回。
//但会持续调用 直到操作系统允许访问(子进程退出)
}
else //出错了
return -1;
}