Linux 进程终止和等待

目录

一:进程常见的退出方法

[1. main 函数返回值](#1. main 函数返回值)

[2.调用 exit](#2.调用 exit)

[3.调用 _exit](#3.调用 _exit)

二:异常问题

三:进程等待

1.概念

2.进程等待的必要性

3.进程等待的方法

[<1>:wait --- 系统调用](#<1>:wait --- 系统调用)

<2>:waitpid


进程退出场景:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

一:进程常见的退出方法

正常终止(通过 echo $? 查看进程退出码):

<1>: 从 main 返回

<2>: 调用 exit

<3>: 调用_exit

异常退出:

ctrl + c , 信号终止

1. main 函数返回值


bash 复制代码
echo $?

?:保存的是最近一个子进程执行完毕时的退出码。


测试代码:

cpp 复制代码
#include<stdio.h>
int main()
{
    printf("hahahagehe\n");
    return 10;
}

编译并运行代码,然后查看进程的退出码:

上述现象表明:main 函数的退出码是可以被父进程获取的,用来判断子进程的运行结果。


在C语言中我们学习过错误码,那么错误码和退出码有什么区别呢???

错误码:通常是衡量一个库函数/一个系统调用一个函数的调用情况,当失败的时候,用来衡量函数进程出错的出错原因。

退出码:通常是一个进程退出的时候,它的退出结果。


2.调用 exit


通过 man 2 exit ,命令 exit 信息


通过下述示例对比进行分析:

在其他函数中进行 return ,表示的是函数调用结束(进程不结束)。

通过 echo $? 查看上述进程的退出码:

我们发现进程的退出码为 10,即函数中 exit 的返回值。由此,我们可以得出结论:

在任意地点调用 exit ,表示进程退出,不进行后续执行。

3.调用 _exit


那么, exit 和 _exit 的区别在哪里???

exit:终止进程的时候,会自动刷新缓冲区

_exit :终止进程的时候,不会自动刷新缓冲区


二:异常问题

进程出异常,本质是进程收到了对应的信号,自己终止了!!! --->

所以一个进程是否出异常,我们只要看有没有收到信号即可。

通过判断进程的退出码。

进程退出的三种场景中,有 :代码运行完毕,结果正确;代码运行完毕,结果不正确;代码异常终止 3 种,其中前两种中的结果是否正确是由我们判断的,那么这个我们指的是谁???

在多进程环境中,我们创建子进程的目的是为了让子进程帮我们做事,我们需要得知子进程把事情做的怎末样(这里的我们指的就是父进程) ---> main 函数的返回值叫做进程的退出码,0表示成功,非0表示失败。

三:进程等待

1.概念

通过 wait / waitpid 的方式,让父进程(一般)对子进程进行资源回收的等待过程。

2.进程等待的必要性

<1>:解决子进程僵尸问题带来的内存泄漏

<2>:父进程创建子进程,是要子进程来帮忙完成任务,子进程任务完成的怎末样,父进程要知道,通过进程等待的方式,获取子进程退出的信息 --- 两个数字! --- 不是必须的,但是系统需要提供这样的基础功能。

3.进程等待的方法

<1>:wait --- 系统调用

wait --- 等待任意一个子进程

返回值: 成功返回被等待进程pid,失败返回-1。

参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

测试下述示例:

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int ret = 5;
        while(ret)
        {
            printf("i am child\n");
            sleep(1);
            ret--;
        }
         exit(0);
    }
    else 
    {
        sleep(10);
        //父进程
        pid_t rid = wait(NULL);
        if(rid == id)
        {
            printf("wait success\n");
        }
    }
    return 0;
}

监视命令行脚本:

bash 复制代码
while :;do ps ajx|head -1 && ps ajx | grep myprocess | grep -v grep;sleep 1;echo "#############################################################";done

测试现象为:

QQ录屏20230110123134


wait :进程等待能回收子进程僵尸状态 Z ---> X,如果子进程根本就没有退出,父进程就必须在 wait 上进行堵塞等待,直到子进程僵尸, wait 自动回收,返回。

一般而言父子进程谁先运行不知道,但是最后一般都是父进程最后退出!!!


<2>:waitpid

waitpid --- 获取退出信息

返回值:

  • 当正常返回的时候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 示例中的代码稍作修改,观察现象:

获取子进程 status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

测试示例(代码跑完结果不正确):

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int ret = 3;
        while(ret)
        {
            printf("i am child\n");
            sleep(1);
            ret--;
        }
        exit(8);
    }
    else 
    {
        printf("wait before\n");
        int status = 0;
        //父进程
        pid_t rid = waitpid(id,&status,0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success: pid: %d,rid: %d,exit sig: %d,exit code: %d\n",getpid(),rid,status&0x7F,(status >> 8)&0xFF);
        }
        sleep(5);
    }
    return 0;
}

运行效果:

示例(代码跑完,结果正确):

cpp 复制代码
    if(id == 0)
    {
        //子进程
        int ret = 3;
        while(ret)
        {
            printf("i am child\n");
            sleep(1);
            ret--;
        }
        exit(0);
    }

运行效果:

示例(代码异常):

cpp 复制代码
    if(id == 0)
    {
        //子进程
        int ret = 3;
        int a = 5;
        while(ret)
        {
            printf("i am child\n");
            sleep(1);
            a /= 0;
            ret--;
        }
        
    }

运行效果:

上述示例,出异常,但退出码为0!!!

说明一个进程异常了(收到信号),它的退出码就没有意义了。

那么如何判断适度收到了信号呢???通过 kill -l 查看,exit sig = 0; 表示正常。


父进程如何得知子进程的退出信息?

wait / waitpid (系统调用接口)

子进程退出的时候,要修改状态Z。并将子进程的退出信号和退出码写入 pcb 中,task_struct -->

exit_code,exit_signal ---> int * stausp 指向 int status。


options:

0:阻塞等待

WNOHANG:等待的时候,以非阻塞的方式等待

  • rid > 0 ,等待成功 break
  • rid = 0 ,等待是成功的,但是对方还没有退出 --> 做自己的事情。--- 循环
  • rid < 0 ,等待失败 break

测试代码(正常退出):

cpp 复制代码
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        int ret = 3;
        while(ret)
        {
            printf("i am child,pid: %d ,ppid: %d ,ret: %d\n",getpid(),getppid(),ret);
            sleep(1);
            ret--;
        }
        exit(0);
    }
    //父进程
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(-1,&status,WNOHANG);
        if(rid > 0)
        {
            printf("child quit success,exit code:%d ,exit sig:%d\n",(status>>8)&0xFF,status&0x7F);
            break;
        }
        else if(rid < 0)
        {
            printf("wait failed!!!\n");
            break;
        }
        else 
        {
            printf("--------------------------------------------------\n");
            //等待成功,但子进程没有退出
            printf("child is alive,wait again,father do other thing ....\n");
            //父进程做自己的事情
            printf("--------------------------------------------------\n");
        }
        sleep(1);
    }
    return 0;
}

运行效果为:

修改 waitpid 部分的内容:

cpp 复制代码
pid_t rid = waitpid(100,&status,WNOHANG);

运行效果为:


相关推荐
这儿有一堆花1 分钟前
Kali Linux:探测存活到挖掘漏洞
linux·运维·服务器
松涛和鸣7 分钟前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法
Running_slave27 分钟前
你应该了解的TCP滑窗
前端·网络协议·tcp/ip
程序员小寒1 小时前
前端高频面试题之CSS篇(一)
前端·css·面试·css3
皮小白1 小时前
ubuntu开机检查磁盘失败进入应急模式如何修复
linux·运维·ubuntu
颜酱1 小时前
Monorepo 架构以及工具选型、搭建
前端·javascript·node.js
oden1 小时前
ChatGPT不推荐你?7个GEO技巧让AI主动引用你的内容
前端
邂逅星河浪漫1 小时前
【CentOS】虚拟机网卡IP地址修改步骤
linux·运维·centos
hhwyqwqhhwy1 小时前
linux 驱动开发相关
linux·驱动开发
IT逆夜2 小时前
实现Yum本地仓库自动同步的完整方案(CentOS 7)
linux·运维·windows