进程替换:exec 函数族

写在前面

Windows 平台下,我们可以通过双击运行可执行程序,让这个可执行程序成为一个进程;
Linux 平台,我们可以通过 ./ 运行,让一个可执行程序成为一个进程。

  但是,如果我们本来就运行着一个程序(进程),我们如何在这个进程内部启动一个外部程序,由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过 exec 函数族实现。
exec 函数族,顾名思义,就是一簇函数,在 Linux 中,并不存在 exec() 函数,exec 指的是一组函数,一共有 6 个

#include <unistd.h>
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 execve(const char *path, char *const argv[], char *const envp[]);

  其中只有 execve() 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。exec 函数族提供了六种在进程中启动另一个程序的方法。exec 函数族的作用是根据指定的文件名或目录名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

  fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行一个外部程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始(main函数)执行。调用 exec 函数被并不会创建新进程,所以调用 exec族函数前后该进程的pid 不变

  将当前进程的 .text.data等替换为所要加载的程序的.text.data等,然后让进程从新的.text第一条执行开始执行。

本文主要介绍execlexeclp 两个函数,通过这两个函数基本可以实现所有exec族函数的功能

execl

作用:自己指定待执行程序路径
函数原型:int execl(const char *path, const char *arg, ...);
参数:

  • path:待执行的外部程序的路径
  • arg:要执行的外部程序所需要的参数
    • 在参数末尾要添加一个哨兵NULL,代表参数结束

返回值:
exec 函数族与一般的函数不同,exec 函数族中的函数执行成功后不会返回,而且,exec 函数族下面的代码执行不到。只有调用失败了,它们才会返回 -1,失败后从原程序的调用点接着往下执行。

案例

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[]){
    pid_t pid;
    pid = fork();
    if(pid < 0){ 
        perror("fork");
        exit(1);
    } else if(pid == 0) {
        printf("This is child, will print hello world\n");
      //execl("a.out",NULL);错误写法 
        execl("a.out", "a.out",NULL); //通过execl调用a.out,输出hello world
        printf("execl exit\n"); //如果execl函数正常退出则不会执行下面的内容
        perror("execl");
        exit(0);
    } else {
        wait(NULL);
        printf("This is parent\n");
    }   
    return 0;
}

程序执行结果如图,事实正如分析。
image.png

execlp

函数原型:int execlp(const char *file, const char *arg, ...);
借助 PATH 环境变量找寻待执行程序

案例

创建子进程,让其执行ls -lh命令

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[]){
    pid_t pid;
    pid = fork();
    if(pid < 0){ 
        perror("fork");
        exit(1);
    } else if(pid == 0) {
        printf("This is child, will print ls-lh\n");
        execlp("ls", "ls","-l","-h",NULL);
        printf("execl exit\n");
        perror("execl");
        exit(0);
    } else {
        wait(NULL);
        printf("This is parent\n");
    }   
    return 0;
}

image.png

execv() 和 execl() 的用法基本是一样的,无非将列表传参,改为用指针数组.

#include<stdio.h>
#include <unistd.h>
#include <error.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[]){
    pid_t pid;
    pid = fork();
    if(pid < 0){ 
        perror("fork");
        exit(1);
    } else if(pid == 0) {
        printf("This is child, will print hello world\n");
        //execl("a.out", "a.out",NULL);
	char *arg[]={"ls","ls", "-a", "-l", "-h", NULL};
	execv(arg)
        printf("execl exit\n"); //如果execl函数正常退出则不会执行下面的内容
        perror("execl");
        exit(0);
    } else {
        wait(NULL);
        printf("This is parent\n");
    }   
    return 0;
}

一张总结图