【Linux进程控制】进程等待

目录

进程等待

进程等待是什么?

为什么?

怎么办?

wait方法

获取子进程status

多进程的等待问题

waitpid方法

什么是阻塞等待?什么是非阻塞等待?

wait/waitpid获取子进程信息原理


进程等待

进程等待是什么?

任何子进程,在退出的情况下,一般必须要被父进程进行等待。在退出的时候,如果父进程不管不顾,退出进程,则子进程就变成僵尸状态(Z状态),kill -9 都无法杀死,因为谁也没有办法杀死一个已经死去的进程,最终会造成内存泄漏

子进程的僵尸只能由父进程解决,父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息(因为父进程需要知道自己派给子进程运行结果是否正确。子进程是否正常退出等)

为什么?

1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(一定要考虑的)

2.获取子进程的退出信息,要知道子进程是因为什么原因退出的(可选的功能)

正是因为这两点,我们需要进程等待

怎么办?

wait方法

当子进程等待成功的时候,会返回等待成功的子进程pid,失败则返回-1。而传入的参数status为输出型参数,如果需要回去子进程的退出状态,则需要传入status;若不关心,则可以填写NULL。

【示例1】

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0)
	{
		printf("wait %d success! status = %d\n", ret, status);
	}
	return 0;
}
cpp 复制代码
[wuxu@Nanyi lesson17]$ gcc -o test wait1.c -std=c99
[wuxu@Nanyi lesson17]$ ./test
[5]->I am child process! pid = 4593, ppid = 4592
[4]->I am child process! pid = 4593, ppid = 4592
[3]->I am child process! pid = 4593, ppid = 4592
[2]->I am child process! pid = 4593, ppid = 4592
[1]->I am child process! pid = 4593, ppid = 4592
wait 4593 success! status = 0

从上面代码可以发现,当子进程在执行时,父进程并不会执行wait之后的if判断语句,而是阻塞在pid_t ret = wait(&status);处等待子进程退出。故wait是阻塞等待的。

获取子进程status

❍ wait 和 waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充

❍ 如果传递NULL,表示不关心子进程的退出状态信息

❍ 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程

❍ status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16位的比特位)

①正常终止时,第8到第15位保存进程的退出状态 ②异常终止时,第8到第15位保存进程的退出状态(但异常终止的退出状态不一定能正确标识进程的退出状态,这里的退出状态是无效的,直接忽略),第7位为coredump标志位,这里先忽略这一位,将于后面的文章中介绍,第0到7位存储的是当前进程收到的信号。

status参数中可能包含的信息:

  • 退出码 :如果子进程是正常退出,status将包含子进程的退出码。

  • 信号编号 :如果子进程是因信号而终止,status将包含导致子进程终止的信号编号。

  • 暂停状态 :如果子进程被暂停(例如,由信号SIGSTOPSIGTSTPSIGTTIN暂停),status将包含导致子进程暂停的信号编号。

  • 退出状态 :如果子进程是正常退出,status的低8位(WIFEXITED(status))将设置为1,并且WEXITSTATUS(status)将包含退出码。

  • 信号状态 :如果子进程是因信号而终止,status的低8位(WIFSIGNALED(status))将设置为1,并且WTERMSIG(status)将包含信号编号。

  • 暂停状态 :如果子进程被暂停,status的低8位(WIFSTOPPED(status))将设置为1,并且WSTOPSIG(status)将包含信号编号。

如果我们想获取子进程的退出码和退出信号,则上面代码应修改成下面这样

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
			cnt--;
			sleep(1);
		}
		exit(0);
	}
	int status = 0;
	pid_t ret = wait(&status);
	if(ret > 0)
	{
		printf("wait %d success! exitcode = %d sig = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));
	}
	return 0;
}
cpp 复制代码
[wuxu@Nanyi lesson17]$ gcc -o test wait1.c -std=c99
[wuxu@Nanyi lesson17]$ ./test
[5]->I am child process! pid = 4928, ppid = 4927
[4]->I am child process! pid = 4928, ppid = 4927
[3]->I am child process! pid = 4928, ppid = 4927
[2]->I am child process! pid = 4928, ppid = 4927
[1]->I am child process! pid = 4928, ppid = 4927
wait 4928 success! exitcode = 0 sign = 0
多进程的等待问题
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	int i = 0;
	for(; i < 5; i++)
	{
		pid_t id = fork();
		if(id == 0)
		{
			int cnt = 5;
			while(cnt)
			{
				printf("[%d]->I am child process! pid = %d, ppid = %d\n", cnt, getpid(), getppid());
				cnt--;
				sleep(1);
			}
			exit(0);
		}
	}
	i = 0;
	for(; i < 5; i++)
	{
		int status = 0;
		pid_t ret = wait(&status);
		if(ret > 0)
		{
			printf("wait %d success! exitcode = %d sign = %d\n", ret, ((status >> 8) & 0xFF), (status & 0x7F));
		}
	}
	return 0;
}
waitpid方法

waitpid与wait方法相同,如果子进程等待成功,则返回该子进程的pid,如果返回失败则返回1;并且一样可以传入status获取子进程的退出信息

waitpid()函数比wait()函数多两个参数:pidoptions。这两个参数允许用户更加精确地控制进程的等待行为

pid参数

  • 含义pid参数是一个整数,它指定等待哪个子进程的结束。如果pid-1,则waitpid()会等待任意一个子进程的结束。如果pid为0,则waitpid()会等待调用fork()创建的所有子进程的结束。如果pid大于0,则waitpid()会等待指定PID的子进程的结束。

  • 返回值 :如果pid-1waitpid()返回最后一个子进程的PID;如果pid为其他值,则返回指定PID的子进程的PID。

options参数

  • 含义:options参数是一个整数,它定义了waitpid()的行为。这个参数可以是WNOHANGWUNTRACED或它们的组合。

  • WNOHANG:如果指定的子进程尚未结束,waitpid()不会阻塞调用进程,而是立即返回。

  • WUNTRACED:如果指定的子进程暂停执行(如被信号暂停),waitpid()也会返回,并且不会返回WNOHANG

  • WNOHANGWUNTRACED不能同时使用。

status参数

  1. 如果子进程是正常终止的

    • WIFEXITED(status) 宏可以用来检查子进程是否正常退出。如果是,WEXITSTATUS(status) 宏可以提取子进程的退出状态(即 exit()_exit() 的参数)。
  2. 如果子进程是因信号而终止的

    • WIFSIGNALED(status) 宏可以用来检查子进程是否因为捕获到的信号而终止。如果是,WTERMSIG(status) 宏可以提取导致子进程终止的信号的编号。

    • WCOREDUMP(status) 宏可以用来检查子进程是否产生了核心转储文件。

  3. 如果子进程是暂停状态 (由于接收到 SIGSTOP, SIGTSTP, SIGTTIN, 或 SIGTTOU 信号):

    • WIFSTOPPED(status) 宏可以用来检查子进程是否已经停止。如果是,WSTOPSIG(status) 宏可以提取导致子进程暂停的信号的编号。
  4. 如果子进程是从暂停状态继续运行的

    • WIFCONTINUED(status) 宏可以用来检查子进程是否继续执行。
cpp 复制代码
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t cpid, w;
    int status;

    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (cpid == 0) {    /* 子进程 */
        printf("子进程: 我将退出并返回状态 %d\n", 42);
        _exit(42); // 子进程退出,返回42
    } else {            /* 父进程 */
        do {
            w = waitpid(cpid, &status, WUNTRACED | WCONTINUED);
            if (w == -1) {
                perror("waitpid");
                exit(EXIT_FAILURE);
            }

            if (WIFEXITED(status)) {
                printf("子进程正常终止,返回状态=%d\n", WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("子进程因信号 %d 终止\n", WTERMSIG(status));
            } else if (WIFSTOPPED(status)) {
                printf("子进程被信号 %d 暂停\n", WSTOPSIG(status));
            } else if (WIFCONTINUED(status)) {
                printf("子进程继续执行\n");
            }
        } while (!WIFEXITED(status) && !WIFSIGNALED(status));
        exit(EXIT_SUCCESS);
    }
}

【示例】

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t id = fork();
	if(id == 0)
	{
		int cnt = 5;
		while(cnt)
		{
			printf("[%d]->child process, pid = %d\n", cnt, getpid());
			cnt--;
			sleep(1);
		}
	}
	int status = 0;
	pid_t ret = waitpid(id, &status, 0);
	if(ret == id)
	{
		printf("wait %d success! exitcode = %d, sig =%d\n", ret ,((status >> 8) & 0xFF), (status & 0x7F));
	}
	return 0;
}
cpp 复制代码
[wuxu@Nanyi lesson17]$ gcc -o test wait3.c -std=c99
[wuxu@Nanyi lesson17]$ ./test
[5]->child process, pid = 5313
[4]->child process, pid = 5313
[3]->child process, pid = 5313
[2]->child process, pid = 5313
[1]->child process, pid = 5313
wait 5313 success! exitcode = 0, sign =0
什么是阻塞等待?什么是非阻塞等待?

【例子】阻塞等待

张三在大学是个学渣,上课时间天天出去玩,以致于明天就要期末考试了,于是找到学霸李四,要求李四帮忙辅导一下。于是张三打电话告诉李四"李四,你帮我辅导一下明天操作系统期末考试呗,顺便请你吃个饭,就当报答你了",李四一听,说"可以,但是你要先等我一小时,我先把计算机组成原理看完",于是张三就来到李四家楼下,等待着李四。张三什么事也不做,就在干巴巴的等着,每过一分钟就给李四打电话,问李四好了没,直到李四说好了,下楼了,他才停止

这种循环等待,其他事也不做,就在干巴巴等着等反馈,这就是阻塞式等待,上面的wait和waitpid代码均是阻塞等待,父进程在wait/waitpid函数上阻塞等待着子进程,而不做其他事,等子进程执行结束才执行下面的if判断语句。

阻塞式等待可以及时回收子进程,但父进程花费大量时间阻塞等待子进程,只要CPU时间片轮到父进程,父进程就一直循环等待子进程,这样效率明显不高

【例子】非阻塞等待

男女朋友约好一起去逛街,女朋友对男友说,你先等我1小时,我画个妆。于是男友开始等待女友,在等待的过程中,男友打打游戏,每打完一把游戏问女朋友好了没。像这种在等待时,顺便做一下其它任务,这种称为非阻塞等待

像这种再等待时,顺带做一下其他任务,这种称为非阻塞等待。非阻塞等待执行的其他任务不应花费过多时间,以免影响父进程对子进程的等待与回收(因为父进程等待子进程才是主要任务,执行其他代码只是顺带执行,不是重点)。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

void PrintLog()
{
    printf("begin PrintLog...\n");
}
void Download()
{
    printf("begin Download...\n");
}
void MysqlDataSync()
{
    printf("begin MySQLDataSync...\n");
}

typedef void(*func_t)();

#define N 3
func_t tasks[N] = {NULL};

void LoadTask()
{
    tasks[0] = PrintLog;
    tasks[1] = Download;
    tasks[2] = MysqlDataSync;
}
void HandlerTask()
{
    for(int i = 0; i < N; i++)
    {
        tasks[i](); // 回调方式
    }
}

// fahter
void DoOtherThing()
{
    HandlerTask();
}


void ChildRun()
{
    //int *p = NULL;
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt);
        sleep(1);
        cnt--;
        //*p = 100;
    }
}

int main()
{
    printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());

    pid_t id = fork();
    if(id == 0)
    {
        // child
        ChildRun();
        printf("child quit ...\n");
        exit(123);
    }
    LoadTask();
    // father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, WNOHANG); // non block
        if(rid == 0)
        {
            sleep(1);
            printf("child is running, father check next time!\n");
            DoOtherThing();
        }
        else if(rid > 0)
        {
            if(WIFEXITED(status))
            {
                printf("child quit success, child exit code : %d\n", WEXITSTATUS(status));
            }
            else
            {
                printf("child quit unnormal!\n");
            }
            break;
        }
        else
        {
            printf("waitpid failed!\n");
            break;
        }
    }
      return 0;
    }
wait/waitpid获取子进程信息原理

当子进程处于僵尸状态时,系统至少要保留子进程的PCB,PCB中保存着子进程的退出码、收到的信号等退出信息。

当父进程在CPU上执行waitpid时,则进入内核态。处于内核态的执行语句有操作系统的权限,因此它可以读取子进程的退出信息,并将该退出信息填写入status。

相关推荐
内核程序员kevin1 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
朝九晚五ฺ5 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream6 小时前
Linux的桌面
linux
xiaozhiwise6 小时前
Makefile 之 自动化变量
linux
意疏8 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu8 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
一只爱撸猫的程序猿8 小时前
一个简单的Linux 服务器性能优化案例
linux·mysql·nginx
我的K840910 小时前
Flink整合Hudi及使用
linux·服务器·flink
19004310 小时前
linux6:常见命令介绍
linux·运维·服务器