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

news2024/11/14 11:56:00

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

          主厨:邪王真眼

主厨的主页: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,他是一个系统接口,不难想像六个接口都是对该系统接口的封装。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

运用Agent搭建“狼人杀”游戏服务器端!

背景 从23年开年以来&#xff0c;大模型引爆了各行各业。去年比较出圈的是各类文生图的应用&#xff0c;比如Stable Diffusion。网上可以看到各类解释其背后的原理和应用的文章。另外一条平行线&#xff0c;则是文生文的场景。受限于当时LLM&#xff08;大语言模型&#xff09…

笔记分享 |【黑马Pink老师】Web APIs

Web API 基本认知 介绍 严格意义上讲&#xff0c;我们在 JavaScript 阶段学习的知识绝大部分属于 ECMAScript 的知识体系&#xff1b; ECMAScript 简称 ES 它提供了一套语言标准规范&#xff0c;如变量、数据类型、表达式、语句、函数等语法规则都是由 ECMAScript 规定的&am…

Android Glide动态apply centerCropTransform(),transition withCrossFade动画,Kotlin

Android Glide动态apply centerCropTransform(),transition withCrossFade动画,Kotlin import android.graphics.Bitmap import android.os.Bundle import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.Glide import …

大数据学习10之Hive高级

1.Hive高级 将大的文件按照某一列属性进行GROUP BY 就是分区&#xff0c;只是默认开窗存储&#xff1b; 分区是按行&#xff0c;如一百行数据&#xff0c;按十位上的数字分区&#xff0c;则有十个分区&#xff0c;每个分区里有十行&#xff1b; 分桶是根据某个字段哈希对桶数取…

嵌入式新手必读好文,常见传感器类型中,LM393的作用,及模块原理(看不懂来问我)!!!

目录 序言 常感器基本知识 常见传感器原理 D0引脚的作用 如何设置电位器 欢迎指正&#xff0c;希望对你&#xff0c;有所帮助&#xff0c;禁止搬运&#xff01;&#xff01;&#xff01; 前言&#xff1a;编写不易&#xff0c;请问搬运&#xff0c;仅供学习&#xff0c;有…

机器学习2_支持向量机_线性可分——MOOC

目录 定义 线性可分&#xff08;Linear Separable&#xff09; 线性不可分&#xff08;Nonlinear Separable&#xff09; 数学化定义 问题描述 优化问题 线性可分定义 假定训练样本集是线性可分的 1、最小化&#xff08;Minimize&#xff09;&#xff1a; 2、限制条件…

git 工具原理

git 目录 git git的使用 了解git的三个区域 具体操作 如何下载别人上传到git的工程 -- 可以参考菜鸟教程&#xff0c;包括安装配置git Git 安装配置 | 菜鸟教程 -- Git 是一种分布式版本控制系统&#xff0c;用于管理软件项目的源代码。它是由 Linux 之父 Linus Torval…

MAN TruckScenes数据集:第一个用于自动驾驶卡车的大规模多模式数据集。

2024-07-15,为了推进自动驾驶卡车技术的发展并确保其在公共道路上的安全性&#xff0c;由慕尼黑工业大学和MAN Truck & Bus SE联合创建了MAN TruckScenes数据集。数据集首次为研究社区提供了一个包含丰富环境条件下的卡车特定挑战&#xff0c;如拖车遮挡、新型传感器视角和…

基于微信小程序的实习管理系统(附源码,文档)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

mp3格式音频怎么做成二维码?扫码获取音频文件的制作方法

随着二维码的广泛使用&#xff0c;现在很多内容都会通过生成二维码的方式来传输内容&#xff0c;通过这种方式可以更快捷的实现内容分享&#xff0c;简化其他人获取内容的流程&#xff0c;有效提高效率。音频是目前常见的一种内容分享方式&#xff0c;比如录音、听力、音乐等类…

山东布谷科技:关于直播源码|语音源码|一对一直播源码提交App Store的流程及重构建议

自从YY、六间房开启国内聊天室和秀场等网红盛行的网络红利时代以来&#xff0c;紧随其后国内各大音视频平台相应出现&#xff0c;先有映客花椒等直播平台的风头正劲&#xff0c;后有功能板块更丰富的头条抖音Tiktok等&#xff0c;盈利功能点不仅仅有直播PK连麦等礼物打赏功能&a…

【NOIP普及组】统计单词数

【NOIP普及组】统计单词数 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 一般的文本编辑器都有查找单词的功能&#xff0c;该功能可以快速定位特定单词在文章中的位置&#xff0c;有的还能统计出特定单词在文章中出现的次数。 现在&#x…

ArkTs语言常用数据类型和使用说明及示例(9种常用,7种非常用)

具体请前往&#xff1a;ArkTs语言基本数据类型及使用说明--包括9种常用变量类型和7种非常用类型

“双十一”电商狂欢进行时,在AI的加持下看网易云信IM、RTC如何助力商家!

作为一年一度的消费盛会&#xff0c;2024年“双十一”购物狂欢节早已拉开帷幕。蹲守直播间、在主播热情介绍中点开链接并加购&#xff0c;也已成为大多数人打开“双11”的重要方式。然而&#xff0c;在这火热的购物氛围背后&#xff0c;主播频频“翻车”、优质主播稀缺、客服响…

机器学习中的分类:决策树、随机森林及其应用

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

ETL架构怎么选?全量、增量还是实时流式?

一、 ETL &#xff1a; 基本定义&#xff1a;ETL 是将业务系统的数据经过抽取&#xff08;Extract&#xff09;、清洗转换&#xff08;Transform&#xff09;之后加载&#xff08;Load&#xff09;到数据仓库的过程&#xff0c;目的是将企业中分散、零乱、标准不统一的数据整合…

特色3D打印机stm32迷你8轴双核心主板

我自己设计的3D打印机主板 1. 这是一块迷你的8轴主板, 主板尺寸为100mm*75mm, 使用一个8cm静音风扇散热足够了2. 这是一个带有保护的板子, 驱动上的gpio具有过压保护功能, 能够直接抗住24V的冲击, 意味着一个驱动炸了, 板子不烧, 并且其他的驱动也没事, 主板支持自动关机3. 8…

【2】GD32H7xx 串口Idle + DMA接收不定长数据

目录 1. IDLE中断相关介绍2. D-Cache与DMA同时使用2.1 I-Cache与D-Cache2.2 D-Cache与DMA同时使用时的数据一致性问题2.2.1 CPU读取DMA写入到SRAM的数据2.2.2 DMA读取CPU写入到SRAM的数据 3. Uart Idle DMA收发程序4. 程序测试 1. IDLE中断相关介绍 在 GD32H7xx MCU 中&#…

证书学习(六)TSA 时间戳服务器原理 + 7 个免费时间戳服务器地址

目录 一、简介1.1 什么是时间戳服务器1.2 名词扩展1.3 用时间戳标记顺序1.4 7 个免费TSA时间戳服务器地址(亲测可用)1.5 RFC 3161 标准二、时间戳原理2.1 时间戳服务工作流程2.2 验证工作流程2.3 举个例子2.4 时间戳原理总结三、代码实现3.1 curl 命令请求时间戳3.2 java 代码…

一步一步从asp.net core mvc中访问asp.net core WebApi

"从asp.net core mvc中访问asp.net core WebApi"看到这个标题是不是觉得很绕口啊&#xff0c;但的确就是要讲一讲这样的访问。前面我们介绍了微信小程序访问asp.net core webapi(感兴趣的童鞋可以看看前面的博文有关WEBAPI的搭建)&#xff0c;这里我们重点不关心如何…