文章目录
进程等待
进程等待必要性
- 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
- 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
因此,进程等待是为了:
- 避免内存泄漏
- 获取子进程执行的结果
- 代码跑完,结果对->退出码
- 代码跑完,结果不对->退出码
- 代码运行异常->信号
等待就是通过系统调用,获取子进程退出码或者退出信号的方式,顺便释放内存
实验(见见猪跑)
cpp
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 10;
while(cnt)
{
printf("我是子进程:%d,父进程:%d,cnt:%d\n",getpid(),getppid(),cnt--);
}
exit(10);
//此时都是S状态
}
//此时子进程变成Z状态
sleep(15);
pid_t ret = wait(NULL);//回收子进程
if(id > 0)
{
printf("wait success:%d\n",ret);
}
sleep(5);//父进程退出
}
waitpid用法;
cpp
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)//子进程返回0,父进程返回子进程id
{
int cnt = 10;
while(cnt)
{
printf("我是子进程:%d,父进程:%d,cnt:%d\n",getpid(),getppid(),cnt--);
}
exit(10);
//退出的三种结果:
//1.代码跑完,结果是对的
//2.代码跑完,结果是错的
//3.代码没有跑完,出异常了
//此时都是S状态
}
//此时子进程变成Z状态
sleep(15);
int status = 0;
pid_t ret = waitpid(id,&status,0);//回收子进程
//错误观念:此时status的值变成了10
//因为status不是被整体使用的,有自己的位图结构
if(id > 0)
{
printf("wait success:%d,sig number:%d,child exit code:%d\n",ret,(status & 0x7F),(status>>8)&0xFF);
}
//sig number:0 child exit code:10
sleep(5);//父进程退出
}
监控脚本:
cpp
ps ajx | head -1 && ps axj | grep mytest | grep -v grep
第一句话是把标题拿出来
第二句话是把matest的进程信息拿出来
第三句话是把grep本身的进程信息去掉
之后写一个循环语句
while :; do ps ajx | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; done
(ctrl + z 退出)
进程等待的方法
wait方法
c
:!man 2 wait //可以查看到相关wait的用法信息
需要包含两个头文件:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
waitpid方法
c
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候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。
这里有一个问题:
status按理来说是会返回信号+退出码
的,但是一个整数怎么能返回两个整数呢?
所以我们不应该吧status当作一个完整的整数,而是应该看作位图结构(后面会详解)
宏的使用方法
cpp
int ret = waitpid(id,&status,0)
if(ret > 0)
{
//是否正常退出
if(WIFEXITED(status))
{
//判断子进程运行结果是否OK
printf("exit code :%d\n",WEXITSTATUS(status));
}
else//出异常了(比如kill -9)
{
//TODO
printf("child exit not normal\n");
}
}
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图 来看待,具体细节如下图(本身是32位,但是这里我们只研究status低16比特位):
1.代码跑完,结果是对的
2.代码跑完,结果是错的
3.代码没有跑完,出异常了
终止信号(即退出码)用来表示是否正常结束(为0则表示正常退出)
用退出状态表示结果是否正确 (通过kill -l进行对应数字的查看(比如发生段错误,除0操作等)),此时退出码是否为0或者是其他的,都无意义,我们不讨论它.
第七位是信号编号
获取退出状态:
(status>>8)&0xFF )
获取终止信号:
``(status & 0x7F))
cpp
#include<sys/types.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
int cnt = 10;
while (cnt)
{
printf("我是子进程:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
}
int status = 0;
//1.让OS释放子进程的僵尸状态
//2.获取子进程的退出结果
//3.在等待期间,子进程没有退出的时候,父进程只能阻塞等待.
pid_t ret = waitpid(id, &status, 0);
if (id > 0)
{
printf("wait success: %d,sig number : %d ,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF );
}
}
cpp
测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
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 );
}
}
}
测试结果:
[root@localhost linux]# ./a.out #等20秒退出
child exit code:10
[root@localhost linux]# ./a.out #在其他终端kill掉
sig code : 9
等待的本质:
监测子进程退出的信息
并且将子进程退出信息通过status拿回来
这个信息(exit code ,signal)在子进程的PCB中
再谈进程退出 :
1.进程退出会变成僵尸进程->会把自己的退出结果写到自己的task_struct
2.wait/waitpid是一个系统调用->OS->OS有资格也有能力去读取子进程的task_struct
阻塞VS非阻塞
概念对比
例子:
1.不挂电话,监测李四的状态->阻塞
2.张三->李四,本质是状态检测,如果没有就绪,直接返回->每一次都是一次非阻塞等待->多次非阻塞等待我们称为:轮询
打电话->系统调用wait/waitpid
张三->父进程
李四->子进程
我们上面写的代码都是阻塞等待
轮询等待:
cpp
#include<sys/types.h>
#include<stdio.h>
#include<sys/wait.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
int cnt = 10;
while (cnt)
{
printf("我是子进程:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);
sleep(1);
}
}
int status = 0;
while(1)//这是一个死循环,作为轮询等待的一个条件!
{
pid_t ret = waitpid(id, &status, WNOHANG);
//wnohang:非阻塞:子进程没有退出时,父进程监测之后立即返回
//如果没有设置WNOHANG,则会卡在这一步,除非子进程退出,或者你的id值设置得有问题.否则不会执行下面的代码
if(ret == 0)//设置了WNOHANG才会可能出现返回值为0的情况
{
//waitpid调用成功&&子进程没有退出,我的waitpid没有等待失败
//仅仅是监测到了子进程没有退出
printf("wait done,but child is running\n",);
}
else if(ret > 0)
{
//1.waitpid调用成功(等待成功)&&子进程退出了
printf("wait success: %d,sig number : %d ,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF );
break;
}
else
{
//waitpid调用失败,返回值为-1
//eg.传入的id参数传错了
printf("waitpid call failed\n");
break;
}
sleep(1);
}
}
非阻塞有什么好处
不会占用父进程的所有精力,可以在轮询期间干干其他的
[[指针进阶]]
[[typedef函数进阶]]
cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <stdlib.h>
#define TASK_NUM 10
typedef void (*func_t)();
func_t other_task[TASK_NUM] = {NULL};
void sync_disk()
{
printf("这是一个刷新磁盘的任务\n");
}
void sync_log()
{
printf("这是一个同步日志的任务\n");
}
void net_send()
{
printf("这是一个网络发送的任务\n");
}
void LoadTask(func_t func)
{
for (int i = 0; i < TASK_NUM; i++)
{
if (other_task[i] == NULL)
{
other_task[i] = func;
break;
}
}
}
void InitTask()
{
for (int i = 0; i < TASK_NUM; i++)
other_task[i] = NULL;
LoadTask(sync_disk);
LoadTask(sync_log);
LoadTask(net_send);
}
void RunTask()
{
for (int i = 0; i < TASK_NUM; i++)
{
if (other_task[i] == NULL)
continue;
other_task[i]();
}
}
int main()
{
pid_t id = fork();
assert(id >= 0);
if (id == 0)
{
int cnt = 5;
while (cnt)
{
printf("我是子进程,我的pid是:%d,我的ppid是%d,我还有%dS存活\n", getpid(), getppid(), cnt--);
sleep(1);
}
printf("我已经退出\n");
exit(11);
}
InitTask();
while (1)
{
sleep(1);
int status;
pid_t ret_id = waitpid(id, &status, WNOHANG);
if (ret_id > 0)
{
printf("我是父进程,我已经回收子进程,ret_id是%d,退出码为%d,退出信号为%d\n", ret_id, (status >> 8) & 0xFF, status & 0x7F);
exit(0);
}
else if (ret_id == 0)
{
RunTask();
continue;
}
else
{
printf("调用出错\n");
}
}
}
具体代码实现
进程的阻塞等待方式:
cpp
#define _CRT_SECURE_NO_WARNINGS 1
int main()
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
}
else if (pid == 0) { //child
printf("child is run, pid is : %d\n", getpid());
sleep(5);
exit(257);
}
else {
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\n");
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
}
else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
运行结果:
[root@localhost linux] # . / a.out
child is run, pid is : 45110
this is test for wait
wait child 5s success, child return code is : 1.
进程的非阻塞等待方式:
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("%s fork error\n", __FUNCTION__);
return 1;
}
else if (pid == 0) { //child
printf("child is run, pid is : %d\n", getpid());
sleep(5);
exit(1);
}
else {
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待->子进程没有退出,父进程检测时候,立即返回
if (ret == 0)
{
//子进程没有退出,我的waitpid没有等待失败
//仅仅是监测到了子进程没有退出
printf("child is running\n");
}
sleep(1);
} while (ret == 0);
if (WIFEXITED(status) && ret == pid) {
printf("wait child 5s success, child return code is :%d.\n", WEXITSTATUS(status));
}
else {
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
让父进程做其他任务
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define TASK_NUM 10
// 要保存的任务相关的
typedef void (*func_t)(); //?
func_t other_task[TASK_NUM] = {NULL}; //函数指针数组
// 预设一批任务
void sync_disk()
{
printf("这是一个刷新数据的任务!\n");
}
void sync_log()
{
printf("这是一个同步日志的任务!\n");
}
void net_send()
{
printf("这是一个进行网络发送的任务!\n");
}
//将任务加载进任务列表
int LoadTask(func_t func)
{
int i = 0;
for(; i < TASK_NUM; i++){
if(other_task[i] == NULL) break;
}
if(i == TASK_NUM) return -1;
else other_task[i] = func;
return 0;
}
void InitTask()
{
for(int i = 0; i < TASK_NUM; i++) other_task[i] = NULL;
LoadTask(sync_disk);
LoadTask(sync_log);
LoadTask(net_send);
}
void RunTask()
{
for(int i = 0; i < TASK_NUM; i++)
{
if(other_task[i] == NULL) continue;
other_task[i]();
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
int cnt = 50;
while(cnt)
{
printf("我是子进程,我还活着呢,我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());
sleep(1);
//int *p = NULL;
//*p = 100;
}
exit(111);
}
InitTask();
// 父进程
//pid_t ret_id = wait(NULL);
while(1)
{
int status = 0;
pid_t ret_id = waitpid(id, &status, WNOHANG); // 夯住了
if(ret_id < 0)
{
printf("waitpid error!\n");
exit(1);
}
else if(ret_id == 0)
{
RunTask();
sleep(1);
continue;
}
else{
if(WIFEXITED(status)) // 是否收到信号
{
printf("wait success, child exit code: %d\n", WEXITSTATUS(status));
}
else
{
printf("wait success, child exit signal: %d\n", status & 0x7F);
}
// printf("我是父进程,等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n",\
// getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);
break;
}
}