进程控制(三):进程替换

文章目录

  • 进程控制(三)
  • 进程替换
    • 进程替换函数
    • 进程中的环境变量
  • 总结


进程控制(三)

进程控制中的进程替换,下文我们学习进程替换的意义,以及进程替换的方式

进程替换

初步认识进程替换,我们先使用一个比较简单的execl函数,来实现进程替换,并从结果上,了解替换的原理。

单进程进程替换

简单实现一下进程替换,以此推理进程替换原理

进程控制(三):进程替换_第1张图片

#include
#include
#include


int main()
{
  //实现单进程的进程替换
  printf("hello linux\n");//输出
  execl("/usr/bin/ls","ls","-a","-l",NULL);//输入的第一个参数为要执行的进程的绝对路径,后面的参数为命令行参数,可变序列参数以NULL结尾,表示结束
  
  printf("hello world\n");
  return 0;
}

单进程实现进程替换,我们发现进程的代码和数据会被替换,替换execl函数内参数所指定的内容。

多进程(父子进程)实现进程替换

进程控制(三):进程替换_第2张图片

#include
#include
#include
#include
#include

int main()
{
  //实现单进程的进程替换
 // printf("hello linux\n");//输出
 // execl("/usr/bin/ls","ls","-a","-l",NULL);//输入的第一个参数为要执行的进程的绝对路径,后面的参数为命令行参数,可变序列参数以NULL结尾,表示结束
 // 
 // printf("hello world\n");
  
  //实现多进程(父子进程)进程替换
  
  //创建子进程
  pid_t id=fork();

  if(id==0)
  {
    //表示子进程
    printf("i am child process pid: %d ppid: %d\n",getpid(),getppid());//得到父进程子进程的pid
    execl("/usr/bin/ls","ls","-l",NULL);//实现程序替换

    exit(11);//退出码,查看是否是会覆盖该代码,如果覆盖的话,我们执行的是ls命令,执行成功,返回码exit为0,通过waitpid进行等到得到status,WEXITSTATUS(status)来判断是否是这样
  }
  else if(id<0)
  {
    //表示子进程创建失败
    perror("fork failed\n");
    exit(1);//表示是创建失败
  }
  else 
  {
    //这是父进程要执行的内容,我们先进行等待
    
    //waitpid等待
    int status=0;
    pid_t ret=waitpid(-1,&status,0);//阻塞等待

    if(ret<0)
    {
      //表示等待失败
      perror("wait failed\n");
      exit(2);
    }
    else 
    {
      //等待成功,我们要输出的是子进程的退出码
      printf("i am parent pid: %d child pid: %d child exit_code: %d\n",getpid(),id,WEXITSTATUS(status));
    }
  }
    return 0;
}

上述多进程进程替换后的结果,表明了进程替换的原理,为将新进程的数据和代码直接覆盖原来的进程的代码。

进程替换原理

  1. 首先,我们知道进程运行时,有task_struct结构体来存储进程信息,以及虚拟空间地址(mm_struct)存储数据,通过页表映射到物理内存
  2. 先运行原有进程,然后fork创建子进程,在子进程中使用进程替换,把子进程的数据和代码进行替换,操作系统识别到要更改子进程的代码和数据,在物理内存中开辟空间,将新进程代码和数据放入,更改页表指向该地址。
  3. 通过上述步骤,实现了子进程的进程替换,此时子进程的task_struct除了代码和数据地址,其他没有变化。

如下图所示:

进程控制(三):进程替换_第3张图片

通过上述内容,我们就能明白进程替换的原理了,起始就是更改物理空间原有进程的代码数据,如果是父子进程,那么就在物理空间上开辟新空间,存放代码和数据,更改子进程页表指向。

进程替换函数

进程替换函数有7种,其中,库函数(man 3号手册)中有6种函数,系统调用(man 2号手册)有一种函数

进程控制(三):进程替换_第4张图片

       //man 2 exec   库函数 6种
	   int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                   char *const envp[]);

下面是这6种函数的使用样例:

#include
#include
#include
#include
#include


int main()
{
  //使用进程替换,为了简单的演示,我们就单纯使用单进程进程替换来实现
  //1. int execl(const char *path, const char *arg, ...);
  int num=0;
  scanf("%d",&num);//输入参数选定不同的进程替换参数
  if(num==1)
  {
    printf("进程替换函数execl\n");
    execl("/usr/bin/ls","ls","-l",NULL);//实现完毕,可变列表的实现,以NULL为结尾
    printf("进程替换完毕\n");
  }
  else if(num==2)
  {
    //int execlp(const char *file, const char *arg, ...);
    //该函数的实现,第一个参数,不必要使用绝对路径,会从PATH以及当前目录下,找到可执行文件,并结合avg命令行参数实现
    printf("execlp函数的实现\n");
    execl("/usr/bin/pwd","pwd",NULL);//实现显示当前工作路径
  }
  else if(num==3)
  {
    //int execle(const char *path, const char *arg,  ..., char * const envp[]);
    //实现功能:需要绝对路径,需要可变列表,需要环境变量
    printf("execle函数的实现\n");
    char * const env[]={"why=123",NULL};
    execle("/usr/bin/ls","ls",NULL,env);
  }
  else if(num==4)
  {
    // int execv(const char *path, char *const argv[]);
    printf("execv函数的实现\n");
    char * const argv[]={"top",NULL};
    execv("/usr/bin/top",argv);

  }
  else if(num==5)
  {
    //int execvp(const char *file, char *const argv[]);
    printf("execvp函数的实现\n");
    char * const argv[]={"ls",NULL};
    execvp("ls",argv);
  }
  else if(num==6)
  {
    //int execvpe(const char *file, char *const argv[],  char *const envp[]);
    printf("execvpe函数的实现\n");
    char * const argv[]={"ls",NULL};
    char * const env[]={"lll=123456",NULL}; 
    execvpe("/usr/bin/ls",argv,env);
  }
  return 0;
}

解释:

  1. 对于环境变量以及命令函参数,无论是可变参数形式,还是字符指针数组形式,最后一个元素都要是NULL结尾,标志参数输入完毕。
  2. 对于这六种函数本质上没什么太大的区别,只是对于参数的形式不同。
  3. 关于环境变量,我们可以使用putenv和getenv函数来增加或者得到指定的环境变量,参数都是字符指针类型,比如getenv(“PATH”)

进程中的环境变量

对于进程中的环境变量进行解释,以下图为准

进程控制(三):进程替换_第5张图片

  1. getenv和putenv函数可以实现得到/创建环境变量信息
  2. PATH环境变量,父子进程都能获得,我们在后文可知,实际上是bash对于进程默认传参environ环境变量(在Linux下就是env命令展示的内容),所以只要系统环境变量中存在的,我们在进程中都能直接getenv获得。
  3. 子进程得到环境变量有两种方式:新增环境变量(putenv,在fork之前),另外是通过进程替换函数,自定义环境变量参数,传参(是彻底替换,即覆盖默认environ)

通过上述图中代码结果表明,我们putenv新建的环境变量,实际上只是在当前进程中的environ有效,但是等进程结束之后,输入env命令,并不会有我们新增的环境变量,这说明,由于进程的独立性,子进程的环境变量并不会影响其他进程(系统,本身也是一个进程)。

进程控制(三):进程替换_第6张图片

最后,对于库函数中的6种进程替换函数,以及对于其中环境变量与进程的关系,我们已经解释完毕,最后一个系统函数调用execve,我们只需要知道,前6种库函数,最后底层都是调用该函数,对于操作系统进行操作(也只有系统调用函数才能对于操作系统操作),以此全局视角,才能操控进程,从而替换进程中数据和代码,最终实现进程替换。

总结

  • 进程替换,实际上只是对于该进程的程序和代码进行替换,子进程的其他信息如pid等都不发生变化,如果是存在父子进程的情况,对父子进程进程替换,会在物理空间中在开辟新空间来存放新进程的代码和数据。
  • 掌握7个进程替换函数,其中6个是库函数,一个是系统调用函数,库函数的底层会使用这个系统调用函数,来实现对于系统中的进程进行操作。
  • 进程环境变量是有默认的数值的,即一个进程默认的环境变量为操作系统中的env命令展示的内容,在C语言中,我们将environ(类型为char**)变量指向该环境变量,如果在执行进程替换中传递我们自己创建的环境变量, 如myenv,会覆盖原有的environ环境变量。

你可能感兴趣的:(Linux,linux,进程控制,进程替换,environ)