【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。

相关推荐
A小辣椒9 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒13 小时前
TShark:基础知识
linux
AlfredZhao15 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩2 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言