进程的替换

1、替换原理:
用fork创建子进程后执行的是和父进程相同的程序,也有可能执行不同的代码分支,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新进程替换,从新程序的启动例程(main函数)开始执行。记住:调用exec并不创建新进程,所以调用exec前后该进程的id并为改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

2、替换函数
其实,有六种以exec开头的函数,统称exec函数:

#include<unistd.h>

int execl(const char *path, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);

3、函数解释
(1)返回值:这些函数如果调用成功,则加载新程序从启动代码开始执行,不再返回。如果调用出错,则返回-1。可知,exec函数只有出错的返回值,而没有成功的返回值。

(2)参数:前四个函数取路径名作为参数,后两个函数则取文件名作为参数。

(3)参数表的传递:
1)函数execl、execle、execlp要求将新程序的每个命令行参数都说明为一个单独的参数,这种参数表以空指针结尾。
2)对于另外3个函数,则先构造一个指向各参数的指针数组,然后将该数组地址作为这3个函数的参数。

(4)传递环境表:
1)以e结尾的2个函数(execle和execve)可以传递一个指向环境字符串指针数组的指针。
2)其他4个函数则使用调用进程中的environ变量为新程序复制现有的环境。
3)通常,一个进程允许将其环境传播给其子进程,但有时进程想为子进程指定某一个确定的环境。

4、命名理解
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
(1)l(list):表示参数采用列表。
(2)v(vector):参数用数组。
(3)p(path):自动搜索环境变量PATH。
(4)e(env):自己维护环境变量。
这里写图片描述

5、exec调用举例如下:

#include<stdio.h>

int main()
{
    char *const argv[] = { "ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};

    execl("/bin/ps", "ps", "-ef", NULL);

   //带p的,可以使用环境变量PATH,无需写全路径
    execlp("ps", "ps", "-ef", NULL);

   //带e 的,需要自己组装环境变量
    execle("ps", "ps", "-ef", NULL, envp);

    execv("/bin/ps", argv);

   //带p的,可以使用环境变量PATH,无需写全路径
    execvp("ps", argv);

   //带e的,需要自己组装环境变量
    execve("/bin/ps", argv, envp);

    exit(0);
}

注:事实上,只有exec是真正的系统调用,其他五个函数最终都调用execve,所以exexcve在man手册第二节,其他函数在man手册第三节。这些函数之间的关系如下图所示:
这里写图片描述

6、替换的过程
(1)获取命令行
(2)解析命令行
(3)建立一个子进程(fork)
(4)替换子进程(execvp)
(5)父进程等待子进程退出(wait)
【例】

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

char *argv[8];
int argc = 0;

void do_parse(char *buf)
{
    int i;
    int status = 0;

    for(argc=i=0; buf[i]; i++)
        if(!isspace(buf[i]) && status == 0){
            argv[argc++] = buf+i;
            status = 1;
        }else if (isspace(buf[i])){
            status = 0;
            buf[i] = 0;
        }
    }
    argv[argc] = NULL;
}

void do_execute(void)
{
    pid_t pid = fork();

    switch(pid){
        case -1;
        perror("fork");
        exit(EXIT_FAILURE);
        break;
        case 0;
        execvp(argv[0], argv);
        perror("execvp");
        exit(EXIT_FAILURE);
        default:
        {
            int st;
            while(wait(&st) != pid)
                ;
        }
    }
}

int main()
{
    char buf[1024] = {};
    while(1){
        printf("myshell>");
        scanf("%[^\n]%*c", buf);
        do_parse(buf);
        do_execute();
    }
}

7、函数和进程之间的相似性
一个C程序有很多函数组成,exec/exit就像call/return。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有它的局部变量,不同的函数通过call/return进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间。如下图所示:
这里写图片描述

释:一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

【例1】演示exec函数

     1  #include<stdio.h>
     2  #include<stdlib.h>
     3  #include<unistd.h>
     4  #include<sys/wait.h>
     5  #include<errno.h>
     6  
     7  char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL};
     8  
     9  int  main(void)
    10  {
    11      pid_t pid;
    12      if((pid = fork()) < 0){
    13          perror("fork error"),exit(1);
    14      }else if(pid == 0){
    15          if(execle("/home/sar/bin/echoall", "echoall", "myarg1","MY ARG2", (char*)0, env_init) < 0){
    16              perror("execle error!"),exit(1);
    17              }
    18          }
    19  
    20          if(waitpid(pid, NULL, 0) < 0){
    21              perror("wait error"),exit(1);
    22          }
    23  
    24          if((pid = fork()) < 0){
    25              printf("fork error");
    26          }else if(pid == 0){
    27              if(execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
    28                  perror("execlp error"),exit(1);
    29          }
    30      return 0;
    31  }

释:在程序中先调用execle,它要求一个路径名和一个特定的环境。下一个调用的是execlp,它用一个文件名,并将调用者的环境传送给新程序。

【例2】回显所有命令行参数和所有环境字符串

     1  #include<stdio.h>
     2  #include<stdlib.h>
     3  #include<unistd.h>
     4  #include<sys/wait.h>
     5  
     6  int main(int argc, char *agrv[])
     7  {
     8      int i;
     9      char **ptr;
    10      extern char **environ;
    11  
    12      //echo all command-line args
    13      for(i = 0; i < argc; i++)
    14          printf("argv[%d]:%s\n", i, argv[i]);
    15  
    16      //and all env strings
    17      for(ptr = environ; *ptr != 0; ptr++)
    18          printf("%s\n", *ptr);
    19  
    20      exit(0);
    21  }

猜你喜欢

转载自blog.csdn.net/m0_38121874/article/details/78617845