(一)进程替换

  • 进程替换不会创建新的进程,进程PCB未发生改变,进程实体(数据代码内容)被替换
  • 进程替换成功后不会执行替换函数下的代码,失败后会执行
  • 进程替换成功不返回,失败后返回-1

(二)环境变量

  • 环境变量的作用:

当使用shell来运行一个程序时,若没有加绝对路径,系统先会在当前路径下寻找该程序,若没找到就会去环境变量中去寻找该程序。都没找到就会报没有该指令的错误。

1
2
3
4
5
6
7
8
jiege@ubuntu:~/Desktop/code/c$ pwd
/home/jiege/Desktop/code/c
jiege@ubuntu:~/Desktop/code/c$ which gcc
/usr/bin/gcc
jiege@ubuntu:~/Desktop/code/c$ echo $PATH
/home/jiege/tools/nodejs/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
jiege@ubuntu:~/Desktop/code/c$

  • 查看所有的环境变量
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(int arg, char* argv[], char* envp[])
{
int i = 0;
while(envp[i] != NULL)
{
printf("%s\n", envp[i++]);
}

return 0;
}

结果:


(三)进程替换API(unistd.h)

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:

  • l : 使用参数列表
  • p:使用文件名,并从PATH环境进行寻找可执行文件
  • v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
  • e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

(1)系统调用API

1
2
3
4
5
 #include <unistd.h>

int execve(const char *pathname, char *const argv[],
char *const envp[]);

(2)库函数API

  • int execl(const char* pathname, const char* arg, .../*(char*)NULL*/);

    参数解释:目标程序的进程替换

    • pathname:目标程序的路径
    • arg:执行这个程序的方式
    • 返回值:失败-1
    • 例如:execl("/bin/ls", "ls" "-a", "-l", (char*)NULL);

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

    参数解释:该函数会在环境变量的路径中查找file

    • file:要执行的目标程序
    • arg:传给目标程序的参数
    • 返回值:失败-1
    • 例如:execlp("ls", "ls", "-a", "-l", (char*)NULL);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <stdio.h>
    #include <unistd.h>

    int main()
    {
    printf("hello\n");

    execlp("ls", "ls", "-a", "-l", (char*)NULL);
    printf("world\n");
    return 0;
    }
    • 结果:
    1
    2
    3
    4
    5
    6
    7
    8
    jiege@ubuntu:~/Desktop/test$ vim main.c
    jiege@ubuntu:~/Desktop/test$ gcc -o main main.c
    jiege@ubuntu:~/Desktop/test$ ./main
    hello
    总用量 24
    -rwxrwxr-x 1 jiege jiege 16736 12月 9 20:12 main
    -rw-rw-r-- 1 jiege jiege 148 12月 9 20:12 main.c
    jiege@ubuntu:~/Desktop/test$

  • int execle(const char *pathname, const char *arg, .../* (char*) NULL, char *const envp[] */);

    参数解释:给这个目标进程传入指定envp的环境变量

    • pathname:目标程序的路径

    • arg:替换后如何执行的方式,envp表示要导入的环境变量

    • 例:

      • 程序myenv输出未设置环境变量的变量
      1
      2
      3
      4
      5
      6
      7
      8
      #include <stdio.h>
      #include <stdlib.h>

      int main()
      {
      printf("myenvp: %s\n", getenv("MYENVP"));
      return 0;
      }
      • 结果

      • 程序b再使用进程替换给a程序传入环境变量
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      #include <stdio.h>
      #include <unistd.h>

      int main()
      {
      char* buff[] = {"MYENVP=/home/jiege/awei", NULL};
      execle("./myenv", "myenv", NULL, buff);
      perror("execle err");
      return 0;
      }
      • 结果

    注意:envp数组前的参数是NULL,envp数组最后一个元素为NULL,并且该操作会覆盖原环境变量的值


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

    参数详解:

    • pathname:目标程序路径名/usr/...
    • argv:将所有的执行方式存放在argv指针数组中
    • 例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <stdio.h>
    #include <unistd.h>

    int main(int argc, char* argv[])
    {
    char* buff[] = {"ls", "-l", NULL};

    execv("/usr/bin/ls", buff);
    perror("execv err");
    return 0;
    }
    • 结果:


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

    参数详解:给目标程序传入参数

    • file:被执行的目标程序
    • argv:传给file程序的参数

    • 例:使用程序a中替换成程序b,b将得到的参数打印

      • a.c

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        #include <stdio.h>
        #include <unistd.h>

        int main()
        {
        printf("A start execvp\n");
        char* argv[] = {"hello", "world", NULL};

        execvp("./b", argv);
        perror("execvp err");
        return 0;
        }
    • b.c

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      #include <stdio.h>
      #include <unistd.h>

      int main(int argc, char* argv[])
      {
      int i = 0;
      while(argv[i] != NULL)
      {
      printf("%s\n", argv[i++]);
      }
      return 0;
      }
    • 结果:


  • int execvpe(const char *file, char *const argv[], char *const envp[]);

    参数详解:

    • file:目标程序
    • argv:函数参数
    • envp:环境变量

(四)进程替换和fork的结合使用案例

  • 一个例子:就比如bash窗口中输入ps -f指令,查看当前进程的完整格式
1
2
3
4
5
jiege@ubuntu:~$ ps -f
UID PID PPID C STIME TTY TIME CMD
jiege 2680 2669 0 14:28 pts/0 00:00:00 bash
jiege 11269 2680 0 17:07 pts/0 00:00:00 ps -f
jiege@ubuntu:~$
  • 分析:

    可以看到ps -f这个进程的进程号PID是11269,它的父进程的进程号PPID是2680;而2680就是bash这个进程。

  • 结论:

    bash就是这个shell窗口的进程的名字,当你输入ps -f时,bash这个进程就fork一个子进程,子进程进程替换执行ps -f命令,将此时的结果输出给父进程bash,父进程输出打印结果

(五)进程替换测试

  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
printf("pid of main: %d\n", getpid());

//进程替换
execl("/bin/ps", "ps", "-f", (char*)NULL);
perror("exec err");

exit(0);
}
  • 执行结果
1
2
3
4
5
6
7
jiege@ubuntu:~/Desktop/code/exec$ ./main
pid of main: 11556
UID PID PPID C STIME TTY TIME CMD
jiege 9970 2669 0 14:28 pts/1 00:00:00 bash
jiege 11556 9970 0 17:50 pts/1 00:00:00 ps -f
jiege@ubuntu:~/Desktop/code/exec$

  • 结果分析:

    • 在bash中执行./main时,bash进程fork出子进程,子进程替换成main程序,main程序中再进行进程替换成ps -f程序

(六)进程替换API总结

函数名参数传递形式路径是否导入环境变量
execl列表需要可执行程序路径不导入 使用当前环境变量
execlp列表默认在环境变量中找不导入 使用当前环境变量
execle列表需要可执行程序路径导入 使用导入的环境变量
execv数组需要可执行程序路径不导入 使用当前环境变量
execvp数组默认在环境变量中找不导入 使用当前环境变量
execve数组需要可执行程序路径导入 使用导入的环境变量