【Linux】进程等待

文章目录

进程等待

进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成'僵尸进程'的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,"杀人不眨眼"的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
    因此,进程等待是为了:
  1. 避免内存泄漏
  2. 获取子进程执行的结果
    1. 代码跑完,结果对->退出码
    2. 代码跑完,结果不对->退出码
    3. 代码运行异常->信号

等待就是通过系统调用,获取子进程退出码或者退出信号的方式,顺便释放内存

实验(见见猪跑)

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;
        }
    }
相关推荐
耶啵奶膘1 小时前
uniapp-是否删除
linux·前端·uni-app
_.Switch2 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_850410832 小时前
文件系统和日志管理
linux·运维·服务器
JokerSZ.2 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
XMYX-03 小时前
使用 SSH 蜜罐提升安全性和记录攻击活动
linux·ssh
芯盾时代3 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
心灵彼岸-诗和远方4 小时前
DevOps业务价值流:架构设计最佳实践
运维·产品经理·devops
一只哒布刘4 小时前
NFS服务器
运维·服务器
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
二十雨辰5 小时前
[linux]docker基础
linux·运维·docker