我与Linux的爱恋:进程等待

发布于:2024-10-17 ⋅ 阅读:(12) ⋅ 点赞:(0)


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🔥个人主页guoguoqiang. 🔥专栏Linux的学习

Alt

进程等待

进程等待是什么?

任何一个子进程,在退出的情况下,一般必须要被父进程等待。在退出的时候,如果父进程不管不顾,退出进程,那么子进程就会变成僵尸状态(Z状态),kill-9都无法杀死,因为谁也没有办法杀死一个已经死去的进程。以至于造成内存泄漏。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。(因为父进程需要知道自己派给子进程运行结果对还是不对,或者是否正常退出。)

为什么?

1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(一定要考虑的)
2.获取子进程的退出信息,要知道子进程是因为什么原因退出的(可选的功能)

怎么办?

wait方法
在这里插入图片描述
当子进程等待成功时,会返回等待成功的子进程的pid,失败则返回-1.传入的参数status为输出型参数,如果需要返回子进程的退出状态,则需要传入;若不关心,则可以填写NULL

#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;
}

在这里插入图片描述
从上面代码可以看出,子进程在执行时,父进程不会执行wait之后的语句,而是阻塞在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将包含导致子进程终止的信号编号。

暂停状态:如果子进程被暂停(例如,由信号SIGSTOP、SIGTSTP或SIGTTIN暂停),status将包含导致子进程暂停的信号编号。

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

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

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

在这里插入图片描述
多进程的等待问题

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    for (int i = 0; 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);
        }
    }
    for (int i = 0; i < 5; i++)
    {
        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;
}

在这里插入图片描述
waitpid方法
waitpid与wait方法相同,如果子进程等待成功则返回该子进程的pid,如果返回失败则返回1;并且一样可以传入status获取子进程的退出信息。
在这里插入图片描述
waitpid()函数比wait()函数多两个参数:pid和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。
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。

在这里插入图片描述
什么是阻塞等待?什么是非阻塞等待?
【例子】阻塞等待

张三在大学是个学渣,上课时间天天出去玩,以致于明天就要期末考试了,于是找到学霸李四,要求李四帮忙辅导一下。于是张三打电话告诉李四“李四,你帮我辅导一下明天操作系统期末考试呗,顺便请你吃个饭,就当报答你了”,李四一听,说“可以,但是你要先等我一小时,我先把计算机组成原理看完”,于是张三就来到李四家楼下,等待着李四。张三什么事也不做,就在干巴巴的等着,每过一分钟就给李四打电话,问李四好了没,直到李四说好了,下楼了,他才停止。
这种循环等待,其他事也不做,干巴巴的等着就叫做阻塞等待,上面的wait和waitpid代码都是阻塞等待,父进程在wait/waitpid函数上阻塞等待子进程,而不做其他事,等子进程结束才执行下面的if判断语句。
阻塞等待可以及时回收子进程,但父进程花费大量时间阻塞等待子进程,只要CPU时间片轮到子进程,父进程就一直循环等待子进程,效率不高。
【例子】非阻塞等待
过了一阵时间,然后张三又要求助李四了,但是这次他和李四决定先让张三等一个小时再告他。这个期间张三打打游戏,看看书。等他每打完一局游戏问李四好了没。像这种在等待时还能做别的事情的就叫非阻塞等待
像这种等待还会顺带做一些别的事情(父进程等待子进程才是主要任务)。

#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]();
    }
}
//father
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);
        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 sucess,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。
在这里插入图片描述