Linux系统编程:进程的替换

news2024/11/15 15:34:56

目录

一. 进程替换的原理

二. 进程替换的方法

2.1 进程替换的相关函数

2.2 进程替换为其它的C/C++程序或其它语言编写的程序

三. 自主实现简单地命令行解释器

四. 总结


一. 进程替换的原理

进程替换,就是对进程所执行的代码进行替换,让正在运行的一个进程,终止运行其当前的代码,转而执行其他的代码。

在我之前的博文Linux系统编程:详解进程地址空间_【Shine】光芒的博客-CSDN博客中提到,OS需要为每个进程创建一个进程控制块(PCB)、一份地址空间、一张页表,CPU拿到虚拟地址,通过页表映射,找到实际的物理内存,从而访问相应的数据和执行对应的程序。

通过系统调用接口(exec系列),可以实现对进程的替换。如果一个正在运行的进程要去执行其他的可执行程序,那么OS会将那份即将被执行的可执行程序的代码和数据加载到内存中去,并改变页表的映射关系,并释放原来程序代码和数据占用的物理内存空间,从而让PCB可以通过当前进程的地址空间和页表,映射找到替换后的可执行程序的代码和数据。

进程替换原理总结:新程序/数据载入内存 + 释放原来程序数据占用的物理内存空间 + 重新建立页表映射关系。

图1.1 进程替换原理图

一般而言,采用创建子进程的方法来进行进程替换,子进程调用exec系列函数替换进程,父进程通过wait/waitpid函数,来监视子进程的状态。

如果不创建子进程,直接在对父进程进行进程替换操作,那么父进程在在于exec系列函数替换进程之后的代码将不再执行。

二. 进程替换的方法

2.1 进程替换的相关函数

有下面六个函数,可以实现进程的替换:

  1. int execl(const char* path, const char* argv, ...)
  2. int execv(const char* path, char* const argv[])  
  3. int execlp(const char* file, const char* argv, ... )
  4. int execvp(const char* file, char* const argv[] )
  5. int execle(const char* path, const char* argv, ... , char* const env[])
  6. int execve(const char* file, char* const argv[], char* const env[])

在上面的函数中1~5为C语言封装后的进程替换函数,6为Linux提供的系统接口函数。函数1~5的底层都是通过封装execve来实现的。 

图2.1 进程替换函数及它们之间的封装关系

一般来说,我们不会直接调用6号函数execve来替换进程,函数1~5有些需要传递完整的可执行程序/指令路径,有些可以直接给指令名称,有些应当以const char* 格式的数据,依次传入每个命令行参数,有些应当采用指针数组的形式传递命令行参数。表2.1为具体的分类方法,代码2.1分别展示了如何采用1~4号函数,替换进程执行运行Linux内置的指令ls -a -l。

  • 函数名中p代表环境变量PATH,即:会去PATH中指定的路径查找指令,如果运行自己编写的代码,还需要指定路径。
  • l表示需要以列表的形式逐个传入命令行参数,v表示以指针数组的形式传入,无论以那种方式传入命令行参数,都需要以NULL结尾。
  • e表示需要用户自己组装环境变量。 
表2.1 进程替换相关函数名称及意义
函数名命令行选项格式是否需要带路径是否需要用户组装环境变量
execl列表
execv指针数组
execlp列表
execvp指针数组
execle列表
execve指针数组

代码2.1:使用进程替换函数运行Linux指令

  #include<stdio.h>    
  #include<stdlib.h>    
  #include<unistd.h>    
  #include<sys/types.h>    
  #include<sys/wait.h>    
      
  #define NUM 10    
      
  int main()    
  {    
      pid_t id = fork();    
      
      if(id == 0)    
      {    
          char* const _argv[NUM] = {    
               (char* const)"ls",    
               (char* const)"-a",    
               (char* const)"-l",    
               NULL    
          };    
      
          //四种方法执行进程替换    
          //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);    
          //execv("/usr/bin/ls", _argv);    
          //execlp("ls", "ls", "-a", "-l", NULL);    
          //execvp("ls", _argv);    
                                                                                                                                                                                                                                                         
          exit(1);    
      }    
      
      int status = 0;    
      pid_t res = waitpid(id, &status, 0);    
      
      if(res > 0)    
      {    
          printf("exit code:%d\n", WEXITSTATUS(status));    
      }    
      
      return 0;    
  }  

2.2 进程替换为其它的C/C++程序或其它语言编写的程序

  • 进程替换为其它的C/C++程序

这里使用execle函数来进行替换,被替换的可执行程序为mycmd.exe,在父进程中定义环境变量VAL_1=1234 和 VAL_2=5678,作为execle的最后一个参数传入,mycmd的源文件mycmd.cpp中,依次输出每个环境变量的值。

代码2.2:test.cpp文件(父进程源文件)和mycmd.cpp文件

//1.test.cpp文件
#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
    
#define NUM 10    
    
int main()    
{    
    pid_t id = fork();    
    
    if(id == 0)    
    {    
        char* const env[NUM] = {    
            (char* const)"VAL_1=1234",    
            (char* const)"VAL_2=5678",    
            NULL    
        };    
    
        execle("./mycmd.exe", "mycmd.exe", NULL, env);                                                                                                                                                                                                   
    }    
    
    int status = 0;    
    pid_t res = waitpid(id, &status, 0);    
    
    if(res > 0)    
    {    
        printf("exit code:%d\n", WEXITSTATUS(status));    
    }    
    
    return 0;    
} 


//2. mycmd.cpp文件
#include<stdio.h>    
int main(int argc, char* argv[], char* env[])    
{    
    for(int i = 0; env[i]; ++i)    
    {    
        printf("env[%d]:%s\n", i, env[i]);                                                                                                                                                                                                             
    }    
      
    return 0;    
} 
图2.2 代码2.2运行结果
  • 替换为其它语言生成的可执行程序(python为例)

我们编写test.py文件和test.cpp文件,在test.cpp文件中,使用execl指令,让进程替换为运行test.py的代码,在命令行中,可以使用python test.py运行程序,如果test.py文件具有可执行权限,那么可直接在命令行中输入./test.py运行程序。

代码2.3:test.cpp文件和test.py文件

// test.cpp 文件
#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)    
    {              
        execl("/usr/bin/python", "python", "test.py", NULL);    
        exit(1);                                                
    }               
         
    int status = 0;    
    pid_t res = waitpid(id, &status, 0);    
                                            
    if(res > 0)    
    {              
        printf("exit code:%d\n", WEXITSTATUS(status));    
    }                                                     
         
    return 0;    
}    

// test.py 文件
#! /usr/bin/python3.6                                                                                                                                            
print("hello Python")                                                               
print("hello Python")                                                               
print("hello Python")                                                               
print("hello Python") 

三. 自主实现简单地命令行解释器

命令行解释器是常驻系统运行的,需要循环执行下面的操作:

  1. 打印提示信息,假设为[root@local-address-CentOS]# 
  2. 读入用户指令,可以使用fgets函数。
  3. 从用户的指令中分离出命令行的每个选项(以' '为间隔),通过strtok函数分割。
  4. 判断是否为内置指令,内置指令应当在父进程中执行,否则在子进程运行。(这里简化为只考虑cd指令为内置指令)
  5. 创建子进程,在子进程中调用execv函数运行指令。
  6. 父进程阻塞等待子进程退出码,进入下一层循环等待用户输入下一条指令。

这里对命令行解释器中涉及到的系统接口和函数进行简单解读:

  • 系统接口chdir:int chdir(const char* path),将当前进程的路径变为path,如果成功改变路径返回0,否则返回-1。
  • 以文本方式读取字符串函数fgets:char* fgets(char* str, size_t size, FILE* stream),从stream流中去读size_t个字符到str指向的空间中去,函数返回值就是str。
  • strtok函数,char* strtok(char* str, const char* delim),通过指定分割符的方式,将一个字符串分割为若干个。如果调用时第一个参数为NULL,那么str就等价于上次找到的分隔符的后面那一个字符的位置,因为strtok具有记忆功能,能够记录下来每次进行分割的位置。

代码3.1为命令行解释器的简易实现程序,由于Linux系统是用C语言编写的,所以这里也采用C语言模拟实现命令行解释器。

代码3.1:命令行解释器的简单模拟实现 -- C语言代码

      //所以需要死循环    
      while(1)    
      {    
          //1.打印提示信息    
          printf("[root@local-address-CentOS]# ");    
          fflush(stdout);  //强制刷新缓冲区    
      
          //2.获取用户输入的指令    
          fgets(g_cmd, NUM * sizeof(char), stdin);    
      
          //fgets会引入换行符/0,因此要将/n改为'/0'    
          g_cmd[strlen(g_cmd) - 1] = '\0';    
      
          //3.将指令的每个选项读入到g_argv中去    
          int index = 0;    
          g_argv[index++] = strtok(g_cmd, g_seq);    
      
          while(g_argv[index++] = strtok(NULL, g_seq));    
      
          //for(int i = 0; g_argv[i]; ++i)    
          //{    
          //    printf("%s\n", g_argv[i]);    
          //}    
      
          //4.处理内置命令(直接在父进程中运行,不切换子进程)    
          if(strcmp(g_argv[0], "cd") == 0)    
          {    
              if(g_argv[1] != NULL)    
              {    
                  chdir(g_argv[1]);    
              }    
      
              continue;    
          }    
      
          //5.创建子进程,运行指令    
          pid_t id = fork();    
      
          if(id == 0)  //子进程代码    
          {    
              execvp(g_argv[0], g_argv);    
              exit(1);  //如果进程替换成功,就不会运行exit(1)    
          }    
          else if(id < 0)  //如果子进程创建失败                                                                                                                                                                                                          
          {    
              perror("fork");    
              exit(1);    
          }    
      
          //6.阻塞等待指令运行的结果
          int status = 0;   //接收子进程运行结果
          pid_t ans = waitpid(id, &status, 0);
          
          if(ans > 0)
          {
              printf("exit code: %d\n", WEXITSTATUS(status));
          }
      }
  
      return 0;
  }

四. 总结

  • 进程替换的底层实现原理是改变页表的映射关系,一般在子进程中进行进程替换操作。
  • 通过exec系列函数,可以实现进程的替换,可以是替换为Linux系统内置的指令,可以替换为其他的C/C++程序,也可以替换为其它语言的程序。

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

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

相关文章

华为OD机试真题 Python 实现【简单的自动曝光】【2023Q1 100分】,附详细解题思路

目录 一、题目描述二、输入描述三、输出描述四、备注五、解题思路六、Python算法源码七、效果展示1、输入2、输出3、说明4、再输入5、输出6、说明 一、题目描述 一个图像有 n 个像素点&#xff0c;存储在一个长度为 n 的数组 img 里&#xff0c;每个像素点的取值范围[0,255]的…

HOT33-排序链表

leetcode原题链接&#xff1a;排序链表 题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出…

html内盒子长宽增加溢出但是外盒子不自动向下延

自动扩展 问题描述 外盒子设置固定px&#xff0c;导致内盒子如图片长宽增加后&#xff0c;溢出但是外盒子不自动扩展&#xff08;向下延申&#xff09; 图片高230时正常 设置250后超出 问题解决 /*height: 660px;*/ /*设死就不能自动扩展&#xff0c;内块块长宽超出&#x…

vuex-persistedstate —— 数据持久化

在之前的篇目当中对于 Vuex 中的相关内容都讲得差不多&#xff0c;但是在项目中去使用vuex&#xff0c;虽然数据状态得到管理了&#xff0c;但数据在每一次都需要去重新加载&#xff0c;那么对于数据的持久化vue是没有给解决的&#xff0c;而是通过第三方的工具去进行数据的持久…

代码随想录算法训练营第17期第4天(5休息) | 24. 两两交换链表中的节点、19. 删除链表的倒数第 N 个结点、面试题 02.07. 链表相交、​​​​​​142. 环形链表 II

目录 24. 两两交换链表中的节点 19. 删除链表的倒数第 N 个结点 面试题 02.07. 链表相交 ​​​​​​142. 环形链表 II 这题不是很难&#xff0c;目前除了从【.】变成了【->】之外&#xff0c;python和C也没啥区别 另外就是对虚拟头结点的掌握了 /*** Definition for …

爬虫小白入门在服务器上-部署爬虫或者开服务接口并供给他人访问

目录 一、准备工作-服务器1、先准备一个服务器&#xff08;以阿里云为例子&#xff09;2、开通服务端口号访问权限 二、准备工作-Xshell登录服务器1、xshell基本登录操作2、xftp基本操作 三、部署代码到服务器上1、部署一个python爬虫脚本在服务器上定时运行等2、部署一个pytho…

Java-API简析_占位符类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131504916 出自【进步*于辰的博客】 因为我发现目前&#xff0c;我对Java-API的学习意识比较薄弱…

区块链开发:JS/TS本地|项目环境搭建

区块链开发&#xff1a;JS/TS本地|项目环境搭建 本地环境搭建VSCode Solidity扩展全局安装Solc,corepackVSCode配置本地Solc安装Ganache搭建JS虚拟环境 项目测试安装依赖编写代码部署合约test_blockchain.ts 设置Script部署查看 报错说明1. Error&#xff1a;missing revert da…

【EasyX】使用C/C++实现 流星雨效果(配上详细注释解释)

&#x1f38a;专栏【​​​​​​​EasyX】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Love Story】 &#x1f970;大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 文章目录 &#x1f354;效果&#x…

RNN LSTM

参考资料&#xff1a; 《机器学习2022》李宏毅史上最详细循环神经网络讲解&#xff08;RNN/LSTM/GRU&#xff09; - 知乎 (zhihu.com) LSTM如何来避免梯度弥散和梯度爆炸&#xff1f; - 知乎 (zhihu.com) 1 RNN 的结构 首先考虑这样一个 slot filling 问题&#xff1a; 注意…

云解析DNS

云解析过程&#xff1a; DNS查询的结果通常会在本地域名服务器中进行缓存&#xff0c;如果本地域名服务器中有缓存的情况下&#xff0c;则会跳过如下DNS查询步骤&#xff0c;很快返回解析结果。下面的示例则概述了本地域名服务器没有缓存的情况下&#xff0c;DNS查询所需的8个步…

电路的组成和连接方式-通路、开路、短路

电路是电子设备中最基本的组成部分之一&#xff0c;它由各种电子元件组成&#xff0c;并通过连接方式构建成不同的电路结构。在电路设计和维护中&#xff0c;通路、开路和短路是常见的概念&#xff0c;它们分别代表了电路中不同的连接状态和故障情况。 工具认识&#xff1a; …

万能的微信小程序个人主页:商城系统个人主页、外卖系统个人主页、购票系统个人主页等等【全部源代码分享+页面效果展示+直接复制粘贴编译即可】

前言 以下给出来四个常见的小程序个人主页,分别是商城系统个人主页,外卖系统个人主页,挂号系统个人主页,电影购票系统个人主页。包括完整的页面布局代码,完整的样式代码。使用的时候,只需要将页面代码和样式代码复制到自己项目对应的页面即可。而且可以根据已有代码只需稍…

【机器学习】准确率、精确度、召回率和 F1 定义

一、说明 数据科学家选择目标变量后 - 例如他们希望预测电子表格中的“列”&#xff0c;并完成了转换数据和构建模型的先决条件&#xff0c;最后步骤之一是评估模型的性能。 二、混淆矩阵的模型 2.1 混淆矩阵 选择性能指标通常取决于要解决的业务问题。假设您的数据集中有 10…

电子时钟制作(瑞萨RA)(2)----使用串口进行程序烧写

概述 本篇文章主要介绍如何使用UART串口烧写程序到瑞萨芯片&#xff0c;并以实际项目进行演示。 硬件准备 首先需要准备一个开发板&#xff0c;这里我准备的是芯片型号R7FA2E1A72DFL的开发板&#xff1a; 视频教程 https://www.bilibili.com/video/BV1kX4y1v7tL/ 电子时钟制…

Symbol.for()

示例&#xff1a;Symbol() 和 Symbol.for(‘ ’)的区别 Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中&#xff0c;键为 "foo" Symbol.for("foo"); // 从 symbol 注册表中读取键为"foo"的 symbolSymbol.for("b…

常见面试题之线程池

1. 说一下线程池的核心参数&#xff08;线程池的执行原理知道嘛&#xff09;&#xff1f; 线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数 corePoolSize 核心线程数目 maximumPoolSize 最大线程数目 (核心线程救急线程的最大数目) keepAliveTime 生存…

蘑菇车联用城市级落地讲述自动驾驶新故事

作者 | 魏启扬 来源 | 洞见新研社 “如果不能实现自动驾驶&#xff0c;特斯拉将一文不值”。 这是马斯克在接受媒体采访时的公开发言&#xff0c;这句话的语境是&#xff0c;特斯拉是自动驾驶坚实的拥护者&#xff0c;且一直在付诸行动。 可是特斯拉渐进式的单车智能路线&am…

【游戏逆向】探索可靠的线程检查方法

一、关键的线程检查 在对抗外挂和木马的方案中&#xff0c;不可能将所有的检查操作放在主线程中&#xff0c;因此&#xff0c;在方案中总有一个扫描线程或者环境检查线程必须保持工作&#xff0c;而它们也就成了外挂和木马的重要攻击目标&#xff0c;外挂和木马只要搞定了它们…

【高可用架构】聊聊故障和高可用架构设计

在架构设计中&#xff0c;高性能、高可用、可拓展以及安全等等有多种维度去判断架构的设计纬度&#xff0c;但是一般来说我们需要考虑具体的业务场景&#xff0c;去判断采用那种合适的架构方案&#xff0c;但是对于大多数的设计来说&#xff0c;都需要满足高性能、高可用。所以…