[Linux] 进程等待 | 进程替换

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:青果大战linux

总有光环在陨落,总有新星在闪烁

我有一个朋友,拿了个国二,还找了个小学妹,被上压力了啦, 

        我们在上一篇进程退出码提到了退出码,但其实他的相关知识还有一半没讲,因为这个要结合进程阻塞才可以。

进程等待

我们在讲进程状态时就提到了,当子进程结束,如果父进程不对子进程进行回收,那 子进程就会一直处于僵尸状态,现在我们就要开始讲父进程是如何对子进程进行回收的了。

进程回收的必要性

  1. 子进程结束后,如果不回收,就会进入僵尸状态,那么他的一部分内存就无法回收,造成内存泄漏,即便是kill命令也不行,因为你无法杀死一个死掉的进程
  2. 子进程结束后,父进程需要知道子进程是否完成任务,如果失败了,失败原因是什么,这些可以通过回收进程来获取相关信息。

wait函数

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

 函数返回值

  • 成功:返回被终止子进程的进程ID
  • 失败:返回 -1

我愿称之为最朴实无华的回收函数

#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
pid_t p=fork();
if(p>0){
    pid_t k=wait(NULL);
    if(k<0)
        printf("回收失败了\n");
    else
        printf("回收成功了\n");
    while(1){
        sleep(1);
    printf("我是父进程%d\n",getpid());
    }
}
else if(p==0)
{
    int cnt=5;
    while(cnt--)
    {
          printf("我是子进程:%d 我还在运行\n",getpid());
    sleep(1);
}
}
}

可以看出,wait确实回收了子进程,子进程在结束后不再像之前一样以僵尸状态继续保留 ,而是立刻消失了

但是wait的功能实在是太少了,所以我们不打算对它细讲


waitpid

wait算是waitpid的一个子功能,即回收一个进程,并获取其退出码

waitpid有三个参数,我们一个个说

【pid】

  • pid>0,则表示指定waitpid回收该pid的进程
  • pid<0:回收任意一个子进程,就像wait
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
pid_t p=fork();
if(p>0){
    pid_t k=waitpid(p,NULL,0);
    printf("我是父进程\n");
    if(k<0)
        printf("回收失败了\n");
    else
        printf("回收成功了\n");
    while(1){
        sleep(1);
    printf("我是父进程%d\n",getpid());
    }
}
else if(p==0)
{
    int cnt=5;
    while(cnt--)
    {
          printf("我是子进程:%d 我还在运行\n",getpid());
    sleep(1);
}
}
}

【返回值】

  • 回收成功,他会返回回收的进程的PID
  • 回收失败则返回-1

我们现在把要回收的子进程从p改为了p+1,就会导致回收失败。

#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
pid_t p=fork();
if(p>0){
    pid_t k=waitpid(p+1,NULL,0);
    printf("我是父进程\n");
    if(k==-1)
        printf("回收失败了\n");
    else
        printf("回收成功了\n");
    while(1){
        sleep(1);
    printf("我是父进程%d\n",getpid());
    }
}
else if(p==0)
{
    int cnt=5;
    while(cnt--)
    {
          printf("我是子进程:%d 我还在运行\n",getpid());
    sleep(1);
}
}
}

【status】

输出型参数,他将存储子进程的退出信息

如果不想接收该信息,就传入空指针即可

我们要用位图的思想去看status,

为什么要这样设置?

进程结束有两种情况

  • 正常终止(但最后结果可能不对),退出信息为0,表示无信号异常

  • 被信号杀死(没跑到return就挂了),退出码不确定,此时研究它没有意义

为了研究进程到底是处于什么原因结束,我们需要存储这两种信息

同时我们也可以得出status>>8即是退出码,status&07F就是终止信号

代码展示

#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){
    pid_t p=fork();
    if(p==0)
    {
        int cnt=5;
        while(cnt--){
            sleep(1);
            printf("子进程PID:%d\n",getpid());
        }
        exit(1);
    }
    int st=0;
    pid_t k=waitpid(p,&st,0);
    printf("回收子进程:%d status:%d :退出码:%d 退出信息 %d\n",k,st,st>>8,st&0x7F);
    return 1;
}

我们的退出码是return的1,且运行时没有异常所以退出信息为0,与打印结果一致

对于退出信息,我们可以使用kill -l指令查看

 例如kill -8表示因为浮点错误(除零)而导致进程被终止

这里我们用kill -8 +PID终止了进程,可以看到退出信息变成了8(因为是8号信息终止),退出码则变成了0,这也说明了当进程因为异常提前终止,退出码就没有意义了

linux给用户提供了宏去检测status

  1. WIFEXITED:检测进程是否正常退出,返回一个布尔值,如果进从正常退出,返回真

  2. WEXITSTATUS:提取子进程的退出码

但是我想吐槽一下,对于英格力士不好的人来说,那些宏的英文真不好记,还不如写个st>>8查看退出码,st&0x7F查看退出信息来的好


非阻塞轮询

waitpid的第三个参数表示等待回收的方法,

  • 0表示阻塞等待
  • WNOHANG(wait no hang)表示非阻塞等待

阻塞等待:当父进程执行到该语句时,就会检测要等待回收的子进程是否结束了,如果结束了就回收,如果没有就会卡在该语句,一直等待直到等待失败(返回-1),或者子进程结束对其进行回收。

非阻塞等待:当父进程执行到该语句时,也会检测要等待回收的子进程是否结束了,如果结束了就回收并且返回该进程的PID,如果没有就会返回0,不会卡在这里,因此多与while循环搭配

#include<unistd.h>
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){
    pid_t p=fork();
    if(p==0)
    {
        int cnt=5;
        while(cnt--){
            sleep(1);
            printf("子进程PID:%d\n",getpid());
        }
        exit(1);
    }
    while(1)
    { int st=0;
    pid_t k=waitpid(p,&st,WNOHANG);
    if(k>0)
    {printf("回收子进程:%d status:%d :退出码:%d 退出信息 %d\n",k,st,st>>8,st&0x7F);
        break;
    }
    else if(k==0)
    {
        printf("子进程没结束,在等等\n");
    }
    sleep(1);
    }
    return 1;
}

可以看到父进程的waitpid在子进程没有执行完时,并没有被卡住,而是继续执行后面的语句了


进程替换

        请注意,这个函数非常重要!!我们下次要手写一个shell外壳,就需要用到它,所以请读者认真阅读

我们也算是比较了解fork函数了,fork的作用是创建一个子进程,我们可以利用这点让父子进程执行不同的代码,来满足不同的需求,但是子进程的代码时拷贝的父进程的,如果我们想让子进程执行某种代码,就必须把代码写在父进程中,再用if else区分fork返回值进行区分才可以。那假如我想在代码中使用ls ,pwd这种指令真么办;我想在这个文件的代码中运行别的文件的代码怎么办,把那些代码都拷贝进来?那实在太麻烦了,于是进程替换闪亮登场解决了这个问题

exec系列接口一共有6个,我们可以输入 man -3 exec查看

 execl

【返回值】

  • 进程替换失败返回-1
  • 成功则没有返回值

第一个参数,表示要执行的文件的路径,可以是绝对路径也可以是相对路径。

第二个参数,命令行参数中的argv,以列表(list)的形式传参,记得要以nullptr结尾

#include<iostream>
#include<unistd.h>
using namespace std;
int main(){
    execl("/bin/ls","ls","-a","-l",nullptr);
    return 0;
}

这样一看貌似就是执行了该指令而已,但其实不只是这样,请看下面的代码

#include<iostream>
#include<unistd.h>
using namespace std;
int main(){
    execl("/bin/ls","ls","-a","-l",nullptr);
    printf("明天没早八!!\n");
    return 0;
}

是的,你没有看错,本该在exec函数后执行的printf语句没有执行!

这里就涉及exec的原理了。

事实上,执行到exec函数时,会把第一个参数对应的代码和数据加载进内存,并且直接覆盖掉原来的代码和数据,因此在exec之后的代码是不可能被执行的 。

这也是为什么exec成功后没有返回值,因为没有意义!毕竟后面的代码都被覆盖了。

#include<unistd.h>
#include<bits/stdc++.h>
using namespace std;
int main(){
    printf("我是exec后的进程,我的PID是%d",getpid());
}
#include<iostream>
#include<unistd.h>
#include<unistd.h>
using namespace std;
int main(){
  printf("我是exec之前的进程,我的PID是%d\n",getpid());
    execl("./test5","test5",nullptr);
    return 0;
}

但是请注意,虽然代码和数据都被修改了,但是进程还是那个进程,不信我们可以用PID验证


 execlp

int execlp(const char* file, const char* arg, ... /* (char  *) NULL */);

 与execl相比,只是修改了第一个参数,从要求传递路径,变成了要求传递文件名,

这就是告诉我们,不用再传路径了,把要执行的文件名传进来,至于他的路径,会在PATH的环境变量中查找,如果找得到就执行,找不到就无法执行。

#include<iostream>
#include<unistd.h>
#include<unistd.h>
using namespace std;
int main(){
    execlp("pwd","pwd",nullptr);
    return 0;
}


execle

可以指定替换后的进程的环境变量 

#include<unistd.h>
#include<bits/stdc++.h>
using namespace std;
int main(int argc,char*argv[],char*env[]){
int i=0;
while(env[i])
    cout<<env[i++]<<endl;
}
#include<iostream>
#include<unistd.h>
#include<unistd.h>
using namespace std;
int main(){
     char*const env[]={
       (char*) "A=111",
       (char*) "B=222",
       (char*) "LINUX",
        NULL
    };
    execle("./test5","./test5",NULL,env);
    return 0;
}


 举一反三时间

观察三个函数, 我们不难发现这些函数名的含义

  1. 首先都是exec系列,所以前缀都是exec

  2. execl 后缀l(list列表)表示传入的命令行参数argv是以一个个字符串作为参数进行传入的

  3. execlp 后缀p的含义同上,p表示第一个参数不用传路径,直接传文件名

  4. execle 后缀e表示可传入环境变量

在此基础上,对剩下三个进行分析

【execv】

和后缀l相对,后缀v(vector)这个表示传入命令行argv是以指针数组的形式传入的

#include<iostream>
#include<unistd.h>
#include<unistd.h>
using namespace std;
int main(){
     char*const argv[]={"ls","-a","-l",nullptr};
    execv("/bin/ls",argv);
    return 0;
}

【exevp】

后缀v表示传argv是以指针数组传参,p表示第一个参数不传路径而传文件名

【execvpe】

后缀v、p、e,读者不妨自己想想作用是什么

除了以上六个由语言封装的函数,还有一个execve,他是一个系统接口,不难想像六个接口都是对该系统接口的封装。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/912064.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【EasyExcel】EasyExcel导出表格包含合计行、自定义样式、自适应列宽

目录 0 EasyExcel简介1 Excel导出工具类设置自定义表头样式设置自适应列宽添加合计行 2 调用导出工具类导出Excel表3 测试结果 0 EasyExcel简介 在数据处理和报表生成的过程中&#xff0c;Excel是一个非常常用的工具。特别是在Java开发中&#xff0c;EasyExcel库因其简单高效而…

深度优先搜索之全排列问题(C语言版)

本文的一些参考&#xff1a; DFS (深度优先搜索) 算法详解 模板 例题&#xff0c;这一篇就够了_dfs算法-CSDN博客 首先把深度优先搜索算法的基本概论摆出来 深度优先搜索算法&#xff08;Depth First Search&#xff0c;简称DFS&#xff09;&#xff1a; 一种用于遍历或搜…

【Docker】自定义网络:实现容器之间通过域名相互通讯

文章目录 一. 默认网络&#xff1a;docker0网络的问题二. 自定义网络三. nginx容器指之间通过主机名进行内部通讯四. redis集群容器&#xff08;跳过宿主机&#xff09;内部网络通讯1. 集群描述2. 基于bitnami镜像的环境变量快速构建redis集群 一. 默认网络&#xff1a;docker0…

Serverless+AI,前沿技术

大家好&#xff0c;我是袁庭新。如果想在未来成为一名合格且具备前瞻视野的软件开发工程师&#xff0c;新兴且热门的技术领域都是需要去了解的&#xff08;例如包括ServerlessAI、AI可观测性、以及AI原生应用架构&#xff09;&#xff0c;并且在参加工作前尽可能去系统学习掌握…

开放式耳机如何选择?五款千万不能错过的开放式耳机机型推荐

在这里我先做一个行业的知识科普&#xff0c;目前市场上有超过80%的品牌&#xff0c;都是非专业的开放式耳机品牌&#xff0c;也就是跨界大牌或者网红品牌&#xff0c;这些品牌由于没有开放式声学的技术沉淀&#xff0c;在制作开放式耳机的时候&#xff0c;通常都是直接套用传统…

力扣17-电话号码的数字组合

力扣17-电话号码的数字组合 思路代码 题目链接 思路 原题&#xff1a; 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 输…

鸿蒙进阶篇-剩余和展开、简单和复杂类型

“在科技的浪潮中&#xff0c;鸿蒙操作系统宛如一颗璀璨的新星&#xff0c;引领着创新的方向。作为鸿蒙开天组&#xff0c;今天我们将一同踏上鸿蒙基础的探索之旅&#xff0c;为您揭开这一神奇系统的神秘面纱。” 各位小伙伴们我们又见面了,我就是鸿蒙开天组,下面让我们进入今…

【复平面】-复数相乘的几何性质

文章目录 从数学上证明1. 计算乘积 z 1 ⋅ z 2 z_1 \cdot z_2 z1​⋅z2​2. 应用三角恒等式3. 得出结果 从几何角度证明1.给出待乘的复数 u i u_i ui​2.给出任意复数 l l l3.复数 l l l 在不同坐标轴下的表示图 首先说结论&#xff1a; 在复平面中&#xff0c;两个复数&a…

【EMNLP2024】基于多轮课程学习的大语言模型蒸馏算法 TAPIR

近日&#xff0c;阿里云人工智能平台PAI与复旦大学王鹏教授团队合作&#xff0c;在自然语言处理顶级会议EMNLP 2024 上发表论文《Distilling Instruction-following Abilities of Large Language Models with Task-aware Curriculum Planning》。文章提出了一个名为 TAPIR 的知…

Web服务nginx基本实验

安装软件&#xff1a; 启动服务&#xff1a; 查看Nginx服务器的网络连接信息&#xff0c;监听的端口&#xff1a; 查看默认目录&#xff1a; 用Windows访问服务端192.168.234.111的nginx服务&#xff1a;&#xff08;防火墙没有放行nginx服务&#xff0c;访问不了&#xff09; …

github使用基础

要通过终端绑定GitHub账号并进行文件传输&#xff0c;你需要使用Git和SSH密钥来实现安全连接和操作。以下是一个基本流程&#xff1a; 设置GitHub和SSH 检查Git安装 通过终端输入以下命令查看是否安装Git&#xff1a; bash 复制代码 git --version配置Git用户名和邮箱 bash …

excel常用技能

1.基础技能 1.1 下拉框设置 a. 选中需要设置的列或单元格&#xff0c;数据 ---》 数据验证 b.验证条件 ---> 序列&#xff08;多个值逗号隔开&#xff09; 2.函数 2.1 统计函数-count a.count(区域&#xff0c;区域&#xff0c;......) 统计数量&#xff0c;只针…

Flipper Zero BadUSB反弹shell

Flipper Zero BadUSB反弹shell 前置知识点&#xff1a; Flipper Zero BadUSB 以及其他几个 BadUSB 设备使用用 DuckyScript 编写的有效负载。一种简单的脚本语言&#xff0c;用于执行导致键盘注入攻击的击键。 步骤 创建rev_shell_win.txt文件,并将其拖到badusb文件夹中. 相…

【GPTs】Email Responder Pro:高效生成专业回复邮件

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;Email Responder Pro主要功能适用场景优点缺点 &#x1f4af;小结 &#x1f4af;GPTs指令 Email Craft is a specialized assistant for cra…

检测敏感词功能

今天策划给我一个任务 —— 检测昵称中是否含有敏感词功能&#xff0c;然后丢给我两个压缩包&#xff0c;我解压一看&#xff1a; 有的txt文件是一行一个词&#xff1a; 有的txt文件是按逗号分隔开&#xff1a; 不管是什么格式的总之量非常多&#xff0c;把我这辈子脏话都囊括…

【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)

Git仓库 https://gitee.com/Lin_DH/system 介绍 从 MySQL 中&#xff0c;下载保存的 blob 格式的文件。 代码实现 第一步&#xff1a;配置文件 application.yml spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8datasource:driver-class-name: com.mysql.…

Coppelia Sim (v-REP)仿真 机器人3D相机手眼标定与实时视觉追踪 (三)

使用标定好的结果进行跟踪标定板的位置 坐标转换的步骤为&#xff1a; 1.图像坐标点转到相机坐标系下的点 2.相机坐标系下的点转为夹爪坐标系下的点 3.夹爪坐标系下的点转为机械手极坐标系下的点 跟踪的方式 1.采用标定板的第一个坐标点作为跟踪点 3.机器人每次移动到该点位&a…

easyui +vue v-slot 注意事项

https://www.jeasyui.com/demo-vue/main/index.php?pluginDataGrid&themematerial-teal&dirltr&pitemCheckBox%20Selection&sortasc 接口说明 <template><div><h2>Checkbox Selection</h2><DataGrid :data"data" style&…

运动【跑步 03】安踏冠军3的10KM和15KM*2体验(对比必迈PURE LIGHT)

这里写目录标题 1. 前言2. 两双鞋2.1 必迈 PURE LIGHT2.2 安踏 冠军 3 3. 主观对比4. 问题4.1 必迈 PURE LIGHT4.2 冠军 3 5. 总结 1. 前言 我是程序员&#xff0c;并不是专业的运动员&#xff0c;对跑步鞋的研究也不深&#xff0c;至今也就买过两双相对比较专业的跑鞋&#x…

O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈

O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈 O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈O-RAN前端O-RAN 前传平面C-Plane&#xff08;控制平面&#xff09;&#xff1a;控制平面消息定义数据传输、波束形成等所需的调度、协调。U-Plane&#xff08;用户平面&#xff09;&#…