linux C之exec函数族

版权声明:转载请注明出处,谢谢。 https://blog.csdn.net/butterfly5211314/article/details/84945108

exec函数族用来执行一个程序(execute program).
下述中的引用内容如无特别说明, 均来自man page
一共有6个, 其函数原型为:

#include <unistd.h>

extern char **environ;

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 *filename, char *const argv[], char *const envp[]);

其中execve为系统调用, 另外5个为c库函数.

The functions described in this manual page are front-ends for execve(2).

其中

  • l的意思是list args, 即列出参数
  • p的意思是使用环境变量PATH
  • v的意思是argument vector, 参数向量, 也就是参数用数组来表示(argv, envp)
  • e的意思传入环境变量(由envp指定)

exec函数族中的函数会用新进程的映像替换当前进程的映像.

The exec() family of functions replaces the current process image with a new process image.

所以exec**执行成功后, 后面的代码不会执行, 因为已经被新进程的代码替换了.

下面用这6个函数来实现ls -l的功能.

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

int main(int argc, const char *argv[])
{
    pid_t pid;
    int ret;

    pid = fork();

    if (pid < 0) {
        perror("fork() error");
        exit(1);
    }

    if (pid == 0) { // child
        char* const argv[] = {"ls", "-l", NULL}; // 1
        char* const envp1[] = {"PATH=.", "XX=YY", NULL}; // 2
        char* const envp2[] = {"PATH=/bin:/sbin", "XX=YY", NULL}; // 3
        char* const envp3[] = {"AA=BB", "XX=YY", NULL}; // 4

        // execlp("ls", "ls", "-l", NULL);
        // execl("/bin/ls", "ls", "-l", NULL);
        // execle("ls", "ls", "-l", NULL, envp);

        // execvp("ls", argv);
        // execv("/bin/ls", argv);
        // https://stackoverflow.com/questions/33598869/execve-not-taking-environment-parameters
        // execve("ls", argv, envp2); // 5 evnp is useless, why?
        // execve("/bin/ls", argv, envp2); // 6
        // execve("envp.out", argv, envp3); // 7

        // 如果exec**执行失败, 才会到这来
        perror("child exec error occurred");
        exit(2);
    } else { // parent
         ret = wait(NULL); // wait for child change state
         if (ret == -1) {
             perror("wait() error");
         }
    }
    return 0;
}

我们重点关注这个execve, 因为它是系统调用, 其他几个都是调用这个函数实现的.

  • 句子5(注释5对应的那一句)
    execve("ls", argv, envp2), 在envp2中指定了PATH环境变量,
    看它能不能找到ls, 然而执行会报错, 说是没有找到ls在哪里.
    然后SF上有人说, 这个envp不会起实质的作用, 参见链接: https://stackoverflow.com/questions/33598869/execve-not-taking-environment-parameters.
    所以用execve执行系统命令, 还是要写绝对路径.

  • 句子7
    execve("envp.out", argv, envp3), 这里执行了自己写的一个程序, 源码如下:

// envp.c
#include <stdio.h>

// https://stackoverflow.com/questions/10321435/is-char-envp-as-a-third-argument-to-main-portable
int main(int argc, char* argv[], char* env[])
{
    int i = 0;
    for (i = 0; i < argc; i++) {
        printf("evnp.c, argv[%d], %s\n", i, argv[i]);
    }

    for (i = 0; env[i]; i++) {
        printf("evnp.c, env[%d], %s\n", i, env[i]);
    }

    return 0;
}

envp.c文件只是为了打印传入的argvenv的值.
注意这个env不是POSIX标准的, 但是却被广泛使用. 参见: https://stackoverflow.com/questions/10321435/is-char-envp-as-a-third-argument-to-main-portable

打开语句7, 执行exec.out, 输出如下:

evnp.c, argv[0], ls
evnp.c, argv[1], -l
evnp.c, env[0], AA=BB
evnp.c, env[1], XX=YY

也就是说, envp.out在被执行时, 收到了传入的char* const argv[] = {"ls", "-l", NULL}char* const envp3[] = {"AA=BB", "XX=YY", NULL}.

此时我们就明白了这个argv和envp是如何传入的了.

事实上, 这个execve我们经常用到, 不过它是系统调用, 一般看不出来, 用strace命令可以打印出执行shell命令时产生的系统调用, 如ls的产生的系统调用可以使用strace ls来查看(只截取了部分):
strace ls

发现, 执行ls产生的第一个系统调用就是execve("/bin/ls", ["ls"], [/* 28 vars */]), 看它传入的也是绝对路径/bin/ls.

总结:

  • exec函数族中的函数执行成功时会用新进程的映像替换当前进程的映像.
  • p: PATH, l: list args, e: env, v: vector, 这样好记.
  • main函数还有第三个参数, 虽然不标准,但是广泛支持.

参考:

欢迎补充指正!

猜你喜欢

转载自blog.csdn.net/butterfly5211314/article/details/84945108