Linux进程替换

本章代码gitee仓库:进程替换

文章目录

    • [🌼1. 单进程的程序替换](#🌼1. 单进程的程序替换)
    • [🌻2. 进程替换的原理](#🌻2. 进程替换的原理)
    • [🌴3. 多进程程序替换](#🌴3. 多进程程序替换)
    • [🌳4. 程序替换接口 -- exec*](#🌳4. 程序替换接口 -- exec*)

🌼1. 单进程的程序替换

在Linux中,存在一批接口,可以进行程序的替换

先多的不说,先来看看猪跑

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
  printf("before:I am process,pid:%d,ppid:%d\n",getpid(),getppid());

  execl("/usr/bin/ls","ls","-a","-l",NULL);

  printf("after:I am process,pid:%d,ppid:%d\n",getpid(),getppid());
  return 0;
}

我们发现,在excel之后的代码,没有执行,而是执行了我们替换的ls指令

🌻2. 进程替换的原理

fork之后创建的子进程会执行父进程的代码接下里的,而调用exec函数会直接替换当前的代码和数据,并不会再创建一个新的进程

当我们调用execl时,会先将/usr/bin/ls目录下的ls加载到内存当中,这个进程就会将自己的代码和数据直接替换为ls的代码和数据,重新开始运行ls

🌴3. 多进程程序替换

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)
  {
    printf("before:I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    execl("/usr/bin/ls","ls","-a","-l",NULL);
    printf("after:I am process,pid:%d,ppid:%d\n",getpid(),getppid());
    exit(1);
  }
  pid_t ret = waitpid(id,NULL,0);
  if(ret>0) printf("wait success,father pid:%d,ret id:%d\n",getpid(),ret); 
  return 0;
}

运行发现,子进程的程序替换并不影响父进程的运行,这是因为写时拷贝不仅将数据进行了写时拷贝,同时也将代码进行了写时拷贝。

另外,我们看到子进程的先前的进程pid1047 ,最后父进程回收子进程的时候,回收子进程的pid也是1047 ,这就能够充分的说明进程替换并不会创建新的进程!

Tips:

我们这里发现,exec*系列的函数,程序替换成功之后,后续的代码不会执行,如果替换失败了,才会执行后续的代码,这就代表着exec*函数,只有失败的返回值,并没有成功的返回值

🌳4. 程序替换接口 -- exec*

关于exec*系列的接口一共有7个,他们的开头都说exec

我们目前常用的是man的1、2、3号手册:

  • 1号手册:用户命令手册
  • 2号手册:系统调用
  • 3号手册:C语言库函数

🌿execl

c 复制代码
int execl(const char *path,const char *arg,...);

这里的l可以理解成list,这里传参是可变参数,从第二个参数开始,我们在命令行中如何写的,那么我们就如何将参数传给我们的函数,只不过是将空格换成逗号,然后最后一个参数传NULL

我们要执行这个程序,首先第一件事肯定是找到这个程序在哪儿,所以,第一个参数就表示这个程序的路径。

🌿execlp

c 复制代码
int execlp(const char *file,const char *arg,...);

这里的p就是我们的path环境变量,我们可以不指定路径,它会直接去PATH环境变量里面找有没有这个可执行程序。

当然了,这里我们第一个参数和第二个参数虽然是一样的,但是含义不一样,第一个表示先找到这个程序,第二个表示如何执行。

🌿execv

c 复制代码
int execv(const char *path,char* const argv[]);

这里的v就可以理解为vector,第一次参数同样是表示路径;第二个参数是一个字符指针数组,将命令行参数的地址直接填到这个数组里面。

那这样我们就能将命令行参数填到这个数组里面,然后直接把这个数组进去即可

**Tips:**例如指令ls,它也是一个被编译好的程序,那它也是有main函数的,也有对应的命令行参数,那它这个命令行参数,其实也是从execv的第二个参数传进来的;另外,execl我们已列表的方式传进来,最后也是会被变成char* const argv[这个样子。然后再将这个argv参数传到对应的main函数当中。

在**命令行当中所有的进程都是bash的子进程**,那么所有进程都是采用exec*系列函数进行启动执行的。

所以exec*系列的函数,起到的是一个代码级别的加载器作用,将我们的可执行程序导到内存里面

🌿execvp

c 复制代码
int execvp(const char *file, char *const argv[]);

有了前三个函数的例子,我们看到这个p,其实就能猜到,这里的p就表示PATH环境变量。这里就不再过多赘述了

🌿execle && execvpe

c 复制代码
int execle(const char *path, const char *arvg, ..., char *envp[]);
int execvpe(const char *file, char *const argv[], char *const envp[])

这里带的e,其实就是表示environment环境变量。

如果exec*系列函数能够执行系统命令,那可否执行我们的自己的程序呢?

我们用C语言程序来调用C++做一个示范

makefile会自顶向下扫描遇到的第一个目标文件是mycommand,所以会执行mycommand的方法,而我们的cppExe和先扫描到的没有任何关联,那么就结束了。

所以我们让第一个目标文件是一个伪目标,然后再建立依赖

makefile 复制代码
.PHONY:all
all:mycommand cppExe
mycommand:mycommand.c
	gcc -o $@ $^ -std=c99
cppExe:cppExe.cpp 
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f mycommand cppExe

这里补充一点:前面讲到,命令行怎么输,那么我们的参数就如何传,可是我们执行cppExe的时候,命令行是./cppExe,而这里传的参数确实cppExe,这里是因为我们命令行中带上./是要告诉bash这个可执行程序在哪儿,而在execl中,第一个参数已经说明了在当前目录,所以第二个参数就不需要再带上./了。

当然了,这也不只是可以调用C++,类似Java、python这些都是可以进行调用的

这些跨语言调用,其实也说明了,所以的语言运行起来,本质上都是进程!

有了上面C语言调用其他语言的例子,我们就能验证,一个程序是否可以给另一个程序传入环境变量参数和命令行参数了。

验证发现,这里传入的命令行参数拿到了,而环境变量压根没有传入,但是也能拿到。

这是因为环境变量也是数据,创建子进程的,环境变量就已经被子进程继承了。

当我们不写这些int argcchar *argv[]char *env[]的时候,也能传进来。

有个extern char **environ第三方变量,它直接指向了环境变量信息,当它被定义的时候,就已经被父进程初始化指向了自己的环境变量表,所以再创建子进程的时候,这个变量就被继承下去了。

同时,我们这里做的是程序替换,而程序替换是直接替换代码和数据,而环境变量并没有被替换掉,所以程序替换中,环境变量信息不会被替换

如果我们想自己手动给子进程传递环境变量,也是可以的,这分为2种:

  1. 新增环境变量
    • 我们可以从shell直接导入环境变量;
    • 也可以通过父进程创建环境变量导入子进程,采用库函数putenv给进程导入环境变量,当然,我们这里的导入的环境变量和父bash并没有关系。
  2. 直接替换环境变量
    如果需要直接替换掉,那么就可以用execle或者execvpe,这里采用execle举例子,因为这个更为复杂

    如果我们需要自己的环境变量,我们就可以自定义环境变量,不要父进程的环境变量

以上这6个都是C语言封装的库函数,还一个接口,是系统提供的,本质上就是上面这6都都是调用的这个接口:

相关推荐
A小辣椒8 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒12 小时前
TShark:基础知识
linux
AlfredZhao14 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式