Linux进程替换

Linux进程替换_第1张图片

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

文章目录

    • 1. 单进程的程序替换
    • 2. 进程替换的原理
    • 3. 多进程程序替换
    • 4. 程序替换接口 -- exec*
      • execl
      • execlp
      • execv
      • execvp
      • execle && execvpe

1. 单进程的程序替换

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

Linux进程替换_第2张图片

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

#include
#include
#include

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指令

Linux进程替换_第3张图片

2. 进程替换的原理

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

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

Linux进程替换_第4张图片

3. 多进程程序替换

#include 
#include 
#include 
#include 
#include 
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;
}

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

Linux进程替换_第5张图片

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

Tips:

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

4. 程序替换接口 – exec*

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

Linux进程替换_第6张图片

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

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

execl

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

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

Linux进程替换_第7张图片

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

execlp

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

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

Linux进程替换_第8张图片

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

execv

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

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

Linux进程替换_第9张图片

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

Linux进程替换_第10张图片

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

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

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

execvp

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

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

Linux进程替换_第11张图片

execle && execvpe

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++做一个示范

Linux进程替换_第12张图片

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

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

.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这些都是可以进行调用的

Linux进程替换_第13张图片

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

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

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

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

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

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

Linux进程替换_第14张图片

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

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

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

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

Linux进程替换_第19张图片

你可能感兴趣的:(Linux,原创,linux,运维,服务器)