Linux-程序替换

ops/2024/12/23 8:42:15/

🌎进程控制【下】


文章目录:

进程控制

    execl接口介绍

    多进程版本程序替换

    其他exec接口

      接口介绍
      替换本地程序

    总结


前言:

  在Linux系统中,进程程序替换是一种重要的操作,通过进程程序替换,程序可以更新自己的代码和数据,让进程富有动态性和灵活性,话不多说,开始今天的话题。

在这里插入图片描述


🚀execl接口介绍

  我们的程序只能执行该程序自己的代码,这是众所周知的,但是今天,我想要创建一个子进程来执行别的文件的代码是否可行呢?

在这里插入图片描述
  在Linux下是可实现的,因为Linux给我们提供了对应的接口:

在这里插入图片描述
  这些接口支持我们程序在运行的过程中进行程序替换,从而执行到自己想执行的程序。

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

path :表示带路径文件名的字符串,从而搜索到对应的文件
arg, ...:表示可变参数列表,参数不确定,可传入一个或多个
最后必须以NULL结尾

  首先第一个接口,以下面代码来理解:

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{printf("I am a process, pid: %d\n", getpid());printf("exec begin...\n");execl("/usr/bin/ls", "ls", "-a", "-l", NULL); //程序替换,可变参数printf("exec end  ...\n");return 0;
}

在这里插入图片描述
  能够清晰观察到,在begin之后,程序被替换为了ls 指令,并且选项为 -al,执行程序,发现运行成功了,但是仔细观察之后,在execl之后的printf并没有起作用。

  结论1

程序在执行完exec* 的接口之后,是不会再执行后续的代码了,因为后续代码已经被替换

  从man手册里有exec* 接口返回值的描述:

在这里插入图片描述
  结论2

exec* 只有失败有返回值,为-1。成功就是成功替换了,所以没返回值

  替换完成后是属于创建了新的进程还是旧的进程不变呢?我们不妨做个测试:

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{printf("I am a process, pid: %d\n", getpid());printf("exec begin...\n");sleep(5);execl("/usr/bin/top", "top", NULL); printf("exec end  ...\n");return 0;
}

在这里插入图片描述
  虽然在替换之后进程的名字变了,但是前后两次的pid并没有变化。

  结论3

进程替换并不会创建新的程序,依旧是原来进程的pid


🚀多进程版本程序替换

  通过之前的学习,我们知道进程之间相互独立,那么我们就可以创建一个子进程,让其来执行程序替换,而父进程回收结果:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){sleep(3);printf("exec begin...\n");execl("usr/bin/ls", "ls", "-a", "-l", NULL);printf("exec end  ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}

  运行之后,就可以让子进程执行程序替换,并且父进程回收子进程的资源。

在这里插入图片描述
  我们来思考一个问题:程序替换为什么对父进程没有影响?这是因为,进程具有独立性,在程序替换的时候发生写时拷贝


🚀其他exec接口

✈️接口介绍

  我们通过man手册查询exec*接口,发现不止一个接口,还有六个接口:

在这里插入图片描述
在这里插入图片描述
  我们需要了解这七个接口的含义以及用法,但是在这里我不会全部一一列举,因为有些接口是类似的,这些类似的接口我只需要说一个就够了。

  首先,这些接口中带有 ‘p’ 字符的接口都有 path 这个参数,实际上这个参数的意义是:

PATH: 并不需要告诉系统程序的具体位置,只需要告诉系统程序的名称,系统在进行替换的时候,会自动在PATH环境变量中去查找。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){sleep(3);printf("exec begin...\n");execlp("ls", "ls", "-a", "-l", NULL);//使用带有'p'的接口printf("exec end  ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}

在这里插入图片描述

  使用带 ‘p’ 字符的接口,就不需要带替换程序的路径了,只需要替换程序的名字,在OS中会 依照PATH环境变量来寻找该程序

 下面就是带有 ‘v’ 字符的接口,实际上这个v 在参数里表示的是 const char* argv[],我们在main函数里面是见过的,也就是 命令行参数表

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){char *const argv[] = {(char*)"ls",(char*)"-a",(char*)"-l"};sleep(3);printf("exec begin...\n");execv("/usr/bin/ls", argv);//带有 'v' 字符的接口printf("exec end  ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}

在这里插入图片描述

 最开始我们也见过带有 ‘l’ 字符的接口,它表示的是 list,也就是列表,把需要执行的命令和参数全部放在接口内。

  那么带 ‘vp’ 的其实就是传 程序名,以及参数列表即可:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){char *const argv[] = {(char*)"ls",(char*)"-a",(char*)"-l"};sleep(3);printf("exec begin...\n");execvp("ls", argv);//带 'vp' 的接口printf("exec end  ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}

在这里插入图片描述


✈️替换本地程序

  我们前面的程序替换全部都是使用系统提供好的程序,我们使用自己写的程序该当何如?

#include<stdio.h>
#include<stdlib.h>int main(int argc, const char* argv[])
{for(int i = 0; argv[i]; ++i){printf("argv[%d]:%s\n", i, argv[i]);}printf("I'm test process!\n");printf("I'm test process!\n");printf("I'm test process!\n");printf("I'm test process!\n");printf("I'm test process!\n");return 0;
}

  此时我们使用之前学习的make语法已经行不通了,因为无论怎样,只能编译过一个,今天我们来看点别的:

.PHONY:all#由依赖关系无依赖方法
all:myprocess mytest mytest:mytest.cgcc -o $@ $^ -g -std=c99 
mybin:mybin.cgcc -o $@ $^ -g -std=c99.PHONY:clean
clean:rm -f mybin mytest 

  在需要生成多个文件之前使用 .PHONY加上依赖关系,但是不需要依赖方法,这样就能 根据依赖关系 从前到后依次 生成可执行文件

  那么现在我mybin.c文件的子进程要替换 mytest 程序,我们可以这么写:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("I am a process, pid: %d\n", getpid());pid_t id = fork();if(id == 0){ //child processsleep(1);printf("exec begin...\n");execl("./mytest", "./mytest", "-a", "-b", "-c", NULL);//切换自己写的程序printf("exec end  ...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait success\n");}exit(1);
}

在这里插入图片描述

  当然,这里我是用C语言调用C语言程序,但是我们可以调用其他语言吗?答案是 可以调用 其他语言写的程序。

  这是因为:不论什么语言,运行之后都是进程,只要是进程就都能在Linux下运行

  我们修改test文件,让其打印系统环境变量表:

#include<stdio.h>
#include<stdlib.h>int main(int argc, const char* argv[], const char
{for(int i = 0; env[i]; ++i){printf("env[%d]:%s\n", i, env[i]);}return 0;
}

  此时再使用程序替换,让子进程执行这段代码,父进程等待子进程资源回收:

在这里插入图片描述

  我们也可以使用系统变量 environ,来获取环境变量:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main()
{extern char **environ;for(int i = 0; environ[i]; ++i){printf("env[%d]:%s\n", i, environ[i]);}return 0;
}

在这里插入图片描述

  我们使用 mybin 文件来执行程序的:

在这里插入图片描述
  在mybin.c 中,我们并没有传递环境变量表给子进程,但是子进程却能默认拿到环境变量表?

  实际上,子进程会默认拿到父进程环境变量表,那么mybin 也是子进程,是bash的子进程,所以mybin能拿到bash的环境变量,而mybin的子进程可以拿到父进程环境变量:

  我们导入一个新环境变量在系统里以供猜想:

export VAL=youcanseeme

在这里插入图片描述

  我们在进程地址空间那一节说过,进程地址空间内在 栈的上方存储命令行参数以及环境变量的地方

在这里插入图片描述

  而在本文的最开始,我们也说了,进程替换替换的仅仅是进程的代码和数据,环境变量是不变的

  如果我们想单纯新增环境变量呢?我们可以使用 putenv:

在这里插入图片描述

在这里插入图片描述

  此时我在程序内写入了mytest环境变量,但是当我们在系统中查询时:

在这里插入图片描述

  此时并没有在系统中出现,但是当我们运行程序之后:

在这里插入图片描述

  此时进程内就多了一项mytest的环境变量,而这个环境变量的导入位置是mytest 父进程传给子进程的环境变量,而mytest的父进程是bash,也就是说,在这里bash将从 0-24号环境变量传给了进程mytest,而mytest 使用了putenv新增了环境变量给子进程。

  而现在我想 设置全新的环境变量给子进程,这个时候我们就需要用到带有 ‘e’ 字符的接口了

 接口中还存在带 ‘e’ 字符的接口,e表示的就是 env:const char* env[], 也就需要 环境变量表

在这里插入图片描述

  其实这是以 覆盖 的方式来传递环境变量,也就相当于子进程设置了全新的环境变量了。

在这里插入图片描述

  我在最前面总共列举了七个接口,一个程序替换为什么会有这么多的接口呢?但他们的功能都是进行程序替换,所以他们在功能上没有区别。

  他们仅仅是在传参上有区别,其实我们 程序替换的系统调用只有一个,就是 execve 接口,剩下的六个全部都是由这个接口进行封装的。

在这里插入图片描述


📒✏️总结

  •  进程不仅仅只能运行自己的程序,和可以运行其他程序,使用 exec* 的接口 就可以做到,被称为 程序替换
  •  exec* 接口有七个,他们的 功能全部相同,仅仅是 使用参数不同
  •  子进程会 默认 继承父进程的环境变量表,并 不需要父进程显示传给子进程
  •  程序替换 不看 是什么 语言 的程序,因为 在Linux下运行起来都是进程
  •  七个接口只有 execve 是系统调用,其他6个全是由此接口进行封装。

在这里插入图片描述

  创作不易,还望三联支持博主呀~~


http://www.ppmy.cn/ops/23240.html

相关文章

axios项目中使用如何进行封装,api相关请求封装,POST、GET、HEAD、PUT、DELETE、TRACE、OPTIONS 等封装

这些是HTTP协议中的请求方法&#xff1a; POST&#xff08;提交数据&#xff09;&#xff1a;用于向指定资源提交要被处理的数据&#xff0c;常用于提交表单数据或通过API传送数据。GET&#xff08;获取数据&#xff09;&#xff1a;用于请求指定资源的信息&#xff0c;常用于获…

鸿蒙应用开发-初见:入门知识、应用模型

基础知识 Stage模型应用程序包结构 开发并打包完成后的App的程序包结构如图 开发者通过DevEco Studio把应用程序编译为一个或者多个.hap后缀的文件&#xff0c;即HAP一个应用中的.hap文件合在一起称为一个Bundle&#xff0c;bundleName是应用的唯一标识 需要特别说明的是&…

【ARMv9 DSU-120 系列 9 -- DSU-120 Debug block】

请阅读【Arm DynamIQ™ Shared Unit-120 专栏 】 请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 ARM DSU-120 Debug BlockCluster debug componentsCache DebugDSU-120 Cache Debug 特性应用场景Terminology处理元素(PE)

React受控绑定

受控绑定 概念&#xff1a;使用React组件的状态&#xff08;useState&#xff09;控制表单的状态 简单理解为双向绑定 function App(){const [value, setValue] useState()return (<input type"text" value{value} onChange{e > setValue(e.target.value)}/&…

模块三:二分——LCR. 173.点名

文章目录 题目描述算法原理暴力查找二分查找 代码实现暴力查找二分&#xff1a;CJava 题目描述 题目链接&#xff1a;LCR. 173.点名 关于这道题中&#xff0c;时间复杂度为 O(N) 的解法有很多种&#xff0c;⽽且也是⽐较好想的&#xff0c;这⾥就不再赘述。本题只讲解⼀个最优…

MyBatis中默认的别名(简化 “XML映射文件中的类型引用“)

MyBatis中&#xff0c;"默认的别名" 是为了简化 "XML映射文件中的类型引用" 而设计的&#xff0c;这些别名是 MyBatis 自动为 Java 类型提供的简短名称&#xff0c;使得在配置和映射文件中&#xff0c;不需要使用完整的类名。MyBatis默认别名系统中&#x…

利用GaussDB的可观测性能力构建故障模型

D-SMART高斯专版已经开发了几个月了&#xff0c;目前主要技术问题都已经解决&#xff0c;也能够初步看到大概的面貌了。有朋友问我&#xff0c;GaussDB不已经有了TPOPS了&#xff0c;为什么你们还要开发D-SMART高斯专版呢&#xff1f; 实际上TPOPS和D-SMART虽然都可以用于Gaus…

SpringBoot+layuimini实现左侧菜单动态展示

layuimini左侧菜单动态显示 首先我们看一下layuimini的原有菜单显示格式 {"homeInfo": {"title": "首页","href": "page/welcome-2.html?t2"},"logoInfo": {"title": "LAYUI MINI","…