Linux进程控制(2)

Linux进程控制(2)

📟作者主页:慢热的陕西人

🌴专栏链接:Linux

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容讲解了进程等待收尾内容和进程的程序替换,以及进程程序替换的原理,进程程序替换的7个重要接口

文章目录

  • Linux进程控制(2)
    • 1.进程等待(续)
    • 2.进程程序替换
      • 2.1 程序替换是如何完成的---单线程版
      • 2.2程序替换的原理
      • 2.3引入多进程,使用所有程序替换的接口
        • 熟悉所有的替换程序接口(7个)

1.进程等待(续)

我们稍微改造一下,之前进程等待的时候,父进程不要阻塞等待的代码,让父进程真正的去运行一些任务。

我们采用函数回调的方式,让父进程在等待子进程的时候也可以去运行自己的一些任务!

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>#define TASK_NUM 10//预设一批任务
void sync_disk()
{printf("这是一个刷新数据的任务\n");
}void sync_log()
{printf("这是一个同步日志的任务\n");
}void net_send()
{printf("这是一个网络发送的任务\n");
}                                                                                                                                                            //保存相关的任务
typedef void (*func_t)(); //定义了一个函数指针类型
func_t orther_task[TASK_NUM] = {NULL}; //装载任务
int Load_Task(func_t fuc)
{int i = 0;for(; i < TASK_NUM; ++i){if(orther_task[i] == NULL) break;}if(TASK_NUM == i) return -1;else orther_task[i] = fuc;return 0;
}//初始化函数指针数组
void Init_Task()
{for(int i = 0; i < TASK_NUM; ++i) orther_task[i] = NULL;Load_Task(sync_disk);  Load_Task(sync_log);Load_Task(net_send);
}void Run_Task()
{for(int i = 0; i < TASK_NUM; ++i){if(orther_task[i] == NULL) continue;else orther_task[i]();}
}int main()
{pid_t id = fork();if(id == 0){//子进程int cnt = 5;while(cnt){printf("我是子进程,我还活着呢,我还有%dS,我的pid:%d,我的ppid:%d\n", cnt--, getpid(), getppid());sleep(1);}exit(0);}Init_Task();while(1){int status = 0;pid_t ret_id = waitpid(id, &status, WNOHANG);// 夯住了if(ret_id < 0){printf("waitpid_error\n");}else if(ret_id == 0){Run_Task();                                                                                                                                          sleep(1);continue;}else{printf("我是父进程,我等待成功了,我的pid:%d,我的ppid:%d, ret_id: %d, child exit code: %d, child exit signal:%d\n",getpid(), getppid(), ret_id, (status >> 8)&0xFF, status & 0x7F);exit(0);}sleep(1); }return 0;
}

运行结果:

image-20231109151934831

继续改进,我们之前获取进程退出码的时候是使用(status >> 8)& 0xFF的方式来进行获取的,那么实际上C库也给我们提供了两个宏来帮助我们获取进程的退出码:

status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

我们用这两个宏来优化一下我们等待成功,也就是子进程结束的时候的代码:

      else                                                                                       {                                                                                            //等待成功                                                                                   if(WIFEXITED(status))                                                       {                                                                                         //正常退出                                                                                 printf("wait success, child exit code :%d", WEXITSTATUS(status));                           }                                                                                             else {                                                                                             //异常退出                                                                                printf("wait success, child exit signal :%d", status & 0x7F);    }            exit(0);    }   

正常退出:

image-20231109153300831

异常退出:我们尝试在父进程等待的时候杀掉子进程:

image-20231109153505131

2.进程程序替换

我们为什么需要创建子进程?为了让子进程帮我执行特定的任务;

①让子进程执行父进程的一部分代码;

②如果子进程指向一段全新的代码呢?这时候我们就需要进程的程序替换!

也是为什么需要进程的程序替换。

2.1 程序替换是如何完成的—单线程版

代码:

#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    int main()    
{    printf("begin......\n");    printf("begin......\n");    printf("begin......\n");    printf("begin......\n");    printf("begin......\n");    execl("/bin/ls", "ls", "-a", "-l", NULL);                                                           printf("end........\n");    printf("end........\n");    printf("end........\n");    printf("end........\n");    printf("end........\n");    return 0;    
}  

运行结果:

那么我们可以看到这个进程运行了开始的begin....,然后运行了ls,但是后面的end...却不见了。

这是因为发生了进程的程序的替换,简要的原理就是,操作系统通过提供的地址/bin/ls从磁盘中拿出ls然后选到指定的文件ls,在输入一些参数-a, -l,以NULL表示结束。

image-20231109162541803

2.2程序替换的原理

操作系统不动当前进程的内核数据结构,而是去磁盘内部拿到要替换的数据和代码,将我们当前进程的数据和代码替换掉。

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。

所以进程的程序替换,是没有创建新的进程的。

image-20231109163349868

①站在进程的角度

操作系统,帮我们在磁盘内部找到我们要替换的数据和代码,替换进程的数据和代码。

②站在程序的角度

这个程序被加载了,所以我们也称execl这类函数为加载器

我们在回到一开始,为什么我们程序后面的end.....却没有打印出来?

image-20231109164919209

原因是当我们加载程序替换的时候,新的数据和代码就进入了进程,当前进程后续没有没运行的代码就成为了老代码,直接被替换了,没有机会执行了。

所以进程的程序替换是整体替换,而不是局部替换

所以我们接下来引入多进程的程序替换

2.3引入多进程,使用所有程序替换的接口

例程:

#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)    {    //child    printf("我是子进程:%d", getpid());    execl("/bin/ls", "ls", "-a", "-l", NULL);    }    sleep(2);    //father    waitpid(id, NULL, 0);    printf("我是父进程:%d\n", getpid());                                                                                                                      return 0;    
}

运行结果:

我们看到execl之后父进程的内容也被运行了

因为进程的独立性,所以进程的程序替换只会影响调用程序替换的子进程

子进程加载新程序的时候,是需要进行程序替换的,发生写时拷贝(子进程执行的可是全新的代码啊,新的代码,所以代码区也可以发生写时拷贝)

image-20231109172035019

那么对于execl这类加载函数,它有没有返回值呢?

答案是分情况:

①替换成功是没有返回值的

②替换失败是有返回值的-1

原因是:假设替换替换成功了,那么我们该进程中的代码和数据,都会被替换成新的代码和数据,那么我们之前的返回值也就不复存在了,并且我们也不需要返回值了。

替换失败的情况下,进程之前的代码和数据还是存在的,那么我们的返回值也是存在的,从而可以返回。

所以我们调用了加载函数之后我们不用去判断它是否加载成功,只需要在函数后面返回异常即可。

失败的例程:

#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)    {    //child    printf("我是子进程:%d\n", getpid());                                                                                                                    execl("/bin/lsss", "lsss", "-a", "-l", NULL);    exit(1);    }    sleep(2);    //father    int status;    waitpid(id,&status, 0);    printf("我是父进程:%d, child exit signal:%d\n", getpid(), WEXITSTATUS(status));    return 0;    
} 

image-20231109173257987

熟悉所有的替换程序接口(7个)

int execl(const char *path, const char *arg, ...);

例如:execl("/bin/ls", "ls", "-a", "-l", NULL);

l代表list;

path:路径,也就是告诉操作系统,你要用来替换的程序,在磁盘的哪个路径,例程里的/bin/ls

arg:文件,是你要用来替换的文件名,例程里面的ls

其中的...是可变参数列表,例程中的那些参数-a , -l

其中最后我们要特别的输入一个NULL参数,告诉函数参数结束了;

int execv(const char *path, char *const argv[]);

相比较第一个函数的差别就是,第一个函数要求我们一个一个的去传参数,而第二个要求我们直接用数组的形式去传,但是原则也是一样的,数组的最后一个元素也要置成NULL

我们在子进程内部调用的时候是这样的:

先创建一个数组,将这些参数一个一个放进去,再传给execv即可

那么其实v就是vector就是数组的意思;

   char* const argv[] =    {    "ls",    "-a",    "-l",    NULL    };    // execl("/bin/lsss", "lsss", "-a", "-l", NULL);    execv("/bin/ls", argv);                                                                                                                                 exit(1);    }    

运行结果:

image-20231109175259752

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

p:当我们指定执行程序的时候,只需要指定程序名即可,系统会自动在**环境变量PATH**中查找。

也就是说我们要用于替换的程序,必须在环境变量PATH中,或者说我们在环境变量PATH中设置过;

 execlp("ls", "ls", "-a", "-l", NULL);    

image-20231109180335737

那么其中的两个ls是不一样的,一个是文件名,一个是参数。

int execvp(const char *file, char *const argv[]);

v:表示参数以数组的形式传入;

p:表示在环境变量PATH中去寻找用于替换的文件;

   char* const argv[] =                                                                                                                                   {    "ls",    "-a",    "-l",    NULL    };    execvp("ls", argv);    

运行结果:

image-20231109181025149

int execle(const char *path, const char *arg, ..., char * const envp[]);

envp[]:叫做自定义环境变量,当我们不想使用系统默认的环境变量的时候,这个时候我们就传递一个envp

比如我们现在要让我们的调用exec目录下的ortherproc来替换myproc的子进程的后续代码

image-20231109182618129

先用execl尝试一下:

 execl("./exec/ortherproc", "ortherproc", NULL);  

运行结果:

image-20231109182932018

换成的动态的效果再看看:

动态效果

下来我们尝试用execle来实现一下:

proc.c

   char* const envp[] =                                                                                                             {                                                                                                                                "MYENVP=UCanCMe!",    NULL    };    execle("./exec/ortherproc", "ortherproc",envp);  

ortherproc.cc

for(int i = 0; i < 5; ++i)      
{      cout << "我是另一个程序,我的PID是 :" << getpid() << endl;      cout << "MYENVP: " << (getenv("MYENVP")==NULL ? "NULL" : getenv("MYENVP")) << endl;           cout << "PATH: " << (getenv("PATH") == NULL ? "NULL" : getenv("PATH")) << endl;      sleep(1);                                                                     
}     

运行结果:

我们看到自定环境变量打印出来了,但是操作系统内部的环境变量却不见了,所以我们可以得到一个结论:

自定义环境变量覆盖了,默认的环境变量;

image-20231109193230574

我们传默认的环境变量试试:

extern char ** environ;execle("./exec/ortherproc", "ortherproc",NULL , environ);  

运行结果:

image-20231109193621091

那么如果我们两个都要呢,那么有一个接口putenv给我们提供了一个将自定义环境变量追加到进程的默认环境变量的方法:接下来我们尝试一下

   putenv("MYENVP=UCanCMe");    execle("./exec/ortherproc", "ortherproc", NULL, environ);  

运行结果:

image-20231109201734620

插播一段

我们知道环境变量具有全局属性,可以被子进程继承下去,那么操作系统是怎么办到的?

只需要用execle的最后一个参数传过去即可!

那么我们是不是不需要putenv也能实现两个都能被子进程读取到呢?

我们直接把自定义的环境变量exportbash中试试:

[mi@lavm-5wklnbmaja lesson6]$ export MYENVP=UCanCMe
[mi@lavm-5wklnbmaja lesson6]$ echo $MYENVP
UCanCMe

运行结果:

我们发现是可行的,自定义环境变量-----> bash ----->父进程------>子进程

image-20231109202438896

int execvpe(const char *file, char *const argv[], char *const envp[]);

p:不需要指定路径,只要在环境变量内部即可;

v:参数以数组的形式传入;

e:环境变量数组传入;

使用方法都与上面的类似。

int execve(const char *filename, char *const argv[], char *const envp[]);

这个接口也不用过多介绍了,使用方法都是一样的。

那么我们需要注意的是,在linux的man手册中将区域六个接口都放在了3号手册,唯独这个却放在了2号手册。

其实操作系统只给我们提供了一个程序替换的接口execve,剩下的几个接口都是由这个接口封装出来的。

image-20231109204020146

并且我们程序替换的时候不仅可以替换C语言的,甚至其他的语言都可以替换,我上面的例子也做到了用C++替换,因为这些代码都是交给操作系统来处理的而不是编译器,所以不论是什么语言都是可以替换的!

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

基于若依的ruoyi-nbcio流程管理系统仿钉钉流程json转bpmn的flowable的xml格式(简单支持发起人与审批人的流程)续

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 之前生产的xml&#xff0c;在bpmn设计里编辑有些内容不正确&#xff0c;包括审批人&#xff0c;关联表单等…

【Linux C IO多路复用】多用户聊天系统

目录 Server-Client mutiplexingServer mutiplexingClient mutiplexing Server-Client 在Linux系统中&#xff0c;IO多路复用是一种机制&#xff0c;它允许一个进程能够监视多个文件描述符&#xff08;sockets、pipes等&#xff09;的可读、可写和异常等事件。这样&#xf…

Linux学习笔记--高级

Shell概述 1&#xff0c;shell概述 是一个c语言编写的脚本语言&#xff0c;是linux和用户的桥梁&#xff0c;用户输入命令交给shell处理。shell&#xff0c;将相应的操作传递给内核&#xff08;kernel&#xff09;&#xff0c;内核把处理的结果输出给用户 1.1Shell解释器有哪…

redis持久化和Redis事务

一)Redis持久化之RDBredisDataBase: 什么是持久化: 1)持久性:和持久化说的是同一回事&#xff0c;衡量持久性的前提是重启进程或者是重启主机以后数据是否还存在 持久:把数据存储在硬盘上&#xff0c;那么就是持久性 不持久:把数据存储在内存中 2)redis是一个内存级别的数据库&…

el-table给某一行加背景色

数据列表中总价大于100的一行背景色为红色&#xff0c;效果图如下&#xff1a; 代码示例&#xff1a; <template><div id"app"><!-- 测试区域&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&am…

Cordova插件开发三:通过广播实现应用间跨进程通信

文章目录 1.最终效果预览2.数据发送3.插件接受数据4.JS页面中点击获取数据返回1.最终效果预览 场景说明:我们给自来水公司开发了一个h5应用,需要对接第三方厂家支持硬件设备以便于获取到高精度定位数据,之前几篇文件写过,我已经集成过南方测绘RTK和高精度定位模块的设备,厂…

[unity]切换天空盒

序 unity是自带天空盒的&#xff1a; 但有的时候不想用自带的。怎么自定义&#xff1f;如何设置&#xff1f; 官方文档 Unity - Manual: The Lighting window (unity3d.com) 相关窗口的打开方法 天空盒对应的选项 实际操作 从标准材质球到天空盒材质球 新建一个材质球&…

cookie 里面都包含什么属性?

结论先行&#xff1a; Cookie 中除了名称和值外&#xff0c;还有几个比较常见的&#xff0c;例如&#xff1a; Domain 域&#xff1a;指定了 cookie 可以发送到哪些域&#xff0c;只有发送到指定域或其子域的请求才会携带该cookie&#xff1b; Path 路径&#xff1a;指定哪些…

互联网常见职称

1、管理层 CEO – Chief Executive Officer 首席执行官 VP – Vice President 副总裁 HRD – Humen Resource Director 人力资源总监 OD – Operations Director 运营总监 MD – Marketing Director 市场总监 GM – General Manager 总经理 PM – Production Manager 产品…

stm32 Bootloader设计(YModem协议)

stm32 Bootloader设计&#xff08;YModem协议&#xff09; Chapter1 stm32 Bootloader设计(YModem协议)YModem协议&#xff1a;STM32 Bootloader软件设计STM32 Bootloader使用方法准备工作stm32 Bootloader修改&#xff1a;stm32目标板程序.bin偏移地址修改&#xff1a; Chapt…

服务器部署 Nacos 获取不到配置浏览器可以访问

服务器部署 Nacos 获取不到配置浏览器可以访问 &#x1f4d4; 千寻简笔记介绍 千寻简笔记已开源&#xff0c;Gitee与GitHub搜索chihiro-notes&#xff0c;包含笔记源文件.md&#xff0c;以及PDF版本方便阅读&#xff0c;且是用了精美主题&#xff0c;阅读体验更佳&#xff0c…

样式问题解决

1.深度样式选择器 1.vue2中 原生css >>> .el-card__header saas\scss ::v-deep .el-card__header less /deep/ .el-card__header 2.vue3中 :deep() { //styles } ::deep() { //styles } 2.修改element.style样式 3.用户代理样式表样式修改 用户代理样式表是浏…

在现实生活中传感器GV-H130/GV-21的使用

今天&#xff0c;收获了传感器GV-H130/GV-21&#xff0c;调试探头的用法&#xff0c;下面就来看看吧&#xff01;如有不妥欢迎指正&#xff01;&#xff01;&#xff01;&#xff01; 目录 传感器GV-H130/GV-21外观 传感器调试探头 探头与必要准备工作 传感器数值更改调试 …

HTML使用canvas绘制海报(网络图片)

生成前&#xff1a; 生成后&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>媒体参会嘉宾邀请函生成链接</title><link rel"stylesheet" href"https://cdn.jsdelivr.net/npm/vant2.10…

vr地铁消防虚拟逃生自救系统降低财产及人员伤害

无论是在公共场所还是在家中&#xff0c;火灾都是一种常见的突发事件。这往往会严重影响到人们的财产和生命安全。因此&#xff0c;如何预防火灾和安全逃生就成为了非常重要的话题。这款VR模拟火灾疏散逃生系统&#xff0c;帮助人们了解火灾逃生的技巧以及正确的应对方法。 以传…

【操作系统】考研真题攻克与重点知识点剖析 - 第 2 篇:进程与线程

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

【星海出品】flask 与docker

import os from flask import Flask, request from flask import Response, make_response, jsonify import cv2 import base64 import io import uuid from main import eye ​ app Flask(__name__)​ app.route(/, methods[GET, POST]) # 添加路由blend def upload_file():…

【Agent模型1】MemGPT: Towards LLMs as Operating Systems

论文标题&#xff1a;MemGPT: Towards LLMs as Operating Systems 论文作者&#xff1a;Charles Packer, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, Joseph E. Gonzalez (UC Berkeley) 论文原文&#xff1a;https://arxiv.org/abs/2310.08560 论文出处&#x…

MySQL库的库操作指南

1.创建数据库 一般格式&#xff1a;create database (if not exists) database1_name,database2_name...... 特殊形式&#xff1a; create database charset harset_name collate collate_name 解释&#xff1a; 红色字是用户自己设置的名称charset&#xff1a;指定数据…

Netty入门指南之NIO Buffer详解

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言ByteBu…