Linux进程控制(2)

news2024/11/24 5:36:18

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.coloradmin.cn/o/1190159.html

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

相关文章

【Codeforces】Codeforces Round 905 (Div. 3)

Problem - 1883C - Codeforces 这题当时想复杂了。 题目大意&#xff1a; 给一串数组和一个数字k&#xff0c;求对数组进行多少次操作能是他们的乘积是k的倍数。 操作是选定一个数加上1。 这题需要抓住一个点k属于[2,5]&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5中…

python连接mysql进行查询

pymysql连接工具类 import pymysql 数据库连接工具类 class MySQLConnection:def __init__(self, host, port, user, password, database):self.host hostself.port portself.user userself.password passwordself.database databaseself.conn Noneself.cursor None# …

Umdh进行内存泄露分析软件的下载、安装与使用

1 下载与安装 1.1 软件介绍 Umdh一款轻量级的内存泄露分析工具UMDH&#xff08;User-Mode Dump Heap&#xff09;&#xff0c;是 Debugging Tools for Windows 里面的一个工具&#xff0c;主要通过分析比较进程的Heap Stack trace信息来发现内存泄露。 Umdh内存泄露分析适用…

Spring Boot 请求/actuator/beans 无法访问 返回404

问题复现 在保证项目加入了spring-boot-starter-actuator依赖&#xff0c;并成功启动后。通过浏览器进行访问&#xff0c;返回如下图结果&#xff1a; 问题排查 1. 查看日志 从日志中可以看到基于路径’/actuator’下只暴露了一个端点 2. 访问http://localhost:8080/actua…

【309. 买卖股票的最佳时机含冷冻期】

目录 一、题目解析 二、算法原理 三、代码实现 class Solution { public:int maxProfit(vector<int>& prices) {int nprices.size();vector<vector<int>> dp(n,vector<int>(3));dp[0][0]-prices[0];dp[0][1]0;dp[0][2]0;for(int i1;i<n;i){dp…

HTML的表单标签和无语义标签的讲解

HTML的表单标签 表单是让用户输入信息的重要途径, 分成两个部分: 表单域: 包含表单元素的区域. 重点是 form 标签. 表单控件: 输入框, 提交按钮等. 重点是 input 标签 form 标签 使用form进行前后端交互.把页面上,用户进行的操作/输入提交到服务器上 input 标签 有很多形态,能…

12V升压36V芯片,2A输出方案

12V升压36V芯片是一款专为EPC/笔记本车载适配器升压、升降压转换以及手持设备供电等应用领域设计的芯片。它具有12V升压至36V的功能&#xff0c;输出电流可达2A&#xff0c;采用外置MOS管&#xff0c;5V-35V的宽输入电压范围&#xff0c;参数特点包括高效率、宽输入电压范围、内…

基于GCC的工具objdump实现反汇编

一&#xff1a;objdump介绍 在 Linux中&#xff0c;一切皆文件。 Linux 编程实际上是编写处理各种文件的代码。系统由许多类型的文件组成&#xff0c;但目标文件具有一种特殊的设计&#xff0c;提供了灵活和多样的用途。 目标文件是包含带有附加地址和值的助记符号的路线图。这…

编译过程 学习 CMake 文档的前置知识

OHHHH&#xff0c;发现自己的基础知识真他妈的是呼呼漏风&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c; 尴尬得意识到&#xff0c;不仅是英语水平有问题&#xff0c;他码的基础知识…

在 Gorm 中学习分页和排序

一个全面的指南&#xff0c;教您在 GORM 中实现分页和排序&#xff0c;以实现高效的数据检索和展示 高效的数据检索和展示是应用程序开发的关键方面。GORM&#xff0c;强大的 Go 对象关系映射库&#xff0c;为开发人员提供了强大的工具来实现这一目标。在本指南中&#xff0c;…

【Mysql】模糊查询

目录 表&#xff1a; like用法 1.查询姓孙的王者荣耀英雄 ​编辑 2.查询姓孙&#xff0c;且名后面只有一个字的王者荣耀英雄 3.查询姓孙&#xff0c;且名后面有两个字的王者荣耀英雄 4.查询名字带 亮 的王者荣耀英雄 ​编辑 where...in...用法 1.查询id 为1&#x…

uni-app学习笔记(二)

目录 一、路由与页面跳转 1、tabar与普通页面跳转例子 2、navigateTo 3、switchTab 二、vue组件 1、传统vue组件的使用 2、easycom 三、uView组件库 1、安装配置 2、引入配置 3、使用 四、Vuex 1、认识 2、state基本使用 3、mapState使用 五、网络请求 1、封装…

MGEF 记录添加(物料主数据有一个存储区域的选项英文显示Haz. material number(危险物料号))

物料主数据有一个存储区域的选项英文显示Haz. material number&#xff08;危险物料号&#xff09;&#xff09; 看了一下对应的时MGEF-STOFF 刚开始在后台配置里面加好了需要的项目发现物料主数据还是选不到 找了半天&#xff0c;查了一堆资料。没有找到MGEF 是在哪里增加配置…

计算机毕业设计 基于Web的视频及游戏管理平台的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

Vscode Vim自动切换

在VsCode里安装了Vim插件&#xff0c;由于Vim插件存在Normal和Insert两种模式&#xff0c;会需要经常性的按shift切换中英文&#xff0c;太过麻烦&#xff0c;本文介绍一下如何通过im-select来解决。 首先先确保自己的电脑里装有英文语言包&#xff0c;win10系统下可以使用Win…

树莓派连接打印机我都作了什么工作~

目录 前言1 安装系统2 修改一些设置3 安装更新了一些东西4 编辑DHCP配置文件5 CUPS网页设置6 最后后记参考链接 前言 为了给树莓派连接打印机&#xff0c;并将打印机共享到局域网中&#xff0c;参考了很多博文&#xff0c;也安照教程做了很多操作设置&#xff0c;但是由于参考的…

认证服务-SpringSecurity及Oauth2介绍

认证服务-SpringSecurity及Oauth2介绍 统一身份认证服务 统一身份认证服务系统&#xff1a;以统一身份认证服务为核心&#xff0c;用户登录统一身份认证服务后&#xff0c;即可以使用所有支持统一身份认证服务的管理应用系统。 统一认证服务的提供方在项目实施中通常由公司平…

Unity地面交互效果——5、角色足迹的制作

大家好&#xff0c;我是阿赵。   之前几篇文章&#xff0c;已经介绍了地面交互的轨迹做法。包括了法线、曲面细分还有顶点偏移。Shader方面的内容已经说完了&#xff0c;不过之前都是用一个球来模拟轨迹&#xff0c;这次来介绍一下&#xff0c;怎样和角色动作结合&#xff0c…

基于ssm的大学生社团管理系统

基于ssm的大学生社团管理系统 摘要 基于SSM的大学生社团管理系统是一个全面、高效的社团管理平台&#xff0c;旨在帮助大学生和社团管理员更方便、更快捷地进行社团活动的组织和管理。该系统基于Spring、SpringMVC和MyBatis&#xff08;简称SSM&#xff09;开发&#xff0c;这三…

IS-LM模型:从失衡到均衡的模拟

IS-LM模型&#xff1a;从失衡到均衡的模拟 文章目录 IS-LM模型&#xff1a;从失衡到均衡的模拟[toc] 1 I S − L M 1 IS-LM 1IS−LM模型2 数值模拟2.1 长期均衡解2.2 政府部门引入2.3 价格水平影响2.4 随机扰动因素 1 I S − L M 1 IS-LM 1IS−LM模型 I S − L M IS-LM IS−LM是…