学习exec系列接口

Last Updated: 2023-11-05 08:21:23 Sunday

-- TOC --

对于exec系列函数接口来说,它的作用就是用另一个可执行程序替换当前的进程,正如exec命令

比如当进程A正在执行时,通过调用exec函数执行B,此时系统用可执行程序B的数据段、代码段和堆栈段取代当前进程A的数据段、代码段和堆栈段,当前的进程重新开始执行程序B,这一过程不会创建新的进程,PID没有改变。常用的场景是:先fork后exec!

exec接口是一个函数族:

// execl, execlp, execle, execv, execvp, execvpe - execute a file
// man 3 exec
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, .../* (char*)NULL */);
int execlp(const char *file, const char *arg, .../* (char*)NULL */);
int execle(const char *pathname, const char *arg, .../*, (char*)NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

execvpe这个接口在编译的时候有warning,manpage里写了execvpe(): _GNU_SOURCE,可能有可移植方面的问题,关于_GNU_SOURCE

execve属于系统调用

// man 2 execve
// execve - execute program
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);

写点代码,做点测试,先测试execl

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    execl("/usr/bin/ls", "ls", "-alh", NULL);
    /* if error */
    perror("execl");
    return errno;
}

执行效果:

$ gcc -Wall -Wextra test_exec.c -o test_exec
$ ./test_exec
total 76K
drwxrwxr-x  2 xinlin xinlin 4.0K 7月   7 11:06 .
drwxr-xr-x 26 xinlin xinlin 4.0K 7月   7 11:06 ..
-rwxrwxr-x  1 xinlin xinlin  17K 7月   7 11:06 test_exec
-rw-rw-r--  1 xinlin xinlin  182 7月   7 11:06 test_exec.c

由于execl成功调用后这个进程的代码段会被替换,自然下面的代码就不会再执行了,所以也就不用关心返回值了。但是当调用失败后就会返回-1并设置errno值。

代码如果写成下面这样,效果完全一样:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    execlp("ls", "ls", "-alh", NULL);
    /* if error */
    perror("execlp");
    return errno;
}

ls总是出现两次!第2次出现表示0号参数。

execl(), execlp(), execle()

The const char *arg and subsequent ellipses can be thought of as arg0, arg1, ..., argn. Together they describe a list of one or more pointers to null-terminated strings that represent the argument list available to the executed program. The first argument, by convention, should point to the filename associated with the file being executed. The list of arguments must be terminated by a null pointer, and, since these are variadic functions, this pointer must be cast (char *) NULL. (所谓null-terminated string,就是标准的C定义的字符串格式,\0就是NULL)

v系列的写法如下:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    char *argv[] = {"ls", "-alh", NULL};
    execv("/usr/bin/ls", argv);
    /* if error */
    perror("execv");
    return errno;
}

或者:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    char *argv[] = {"ls", "-alh", NULL};
    execvp("ls", argv);
    /* if error */
    perror("execvp");
    return errno;
}

下面是execle和Python配合的示例,展示了execle最后一个参数的效果:

$ cat pp.py
import os
print('in python3')
print(os.environ)
$ cat test_exec.c
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    char *env[] = {"PATH=/bin", "AA=abc123", NULL};
    execle("/usr/bin/python3", "python3", "pp.py", NULL, env);
    perror("execle");
    return errno;
}

env变量也要用NULL来结束!

执行效果如下:

$ gcc -Wall -Wextra test_exec.c -o test_exec
$ ./test_exec
in python3
environ({'PATH': '/bin', 'AA': 'abc123', 'LC_CTYPE': 'C.UTF-8'})

成功调用pyton,并打印出了python进程中所有的环境变量。

最后,Python的os模块也包含了一组类似的接口:

os.execl(path, arg0, arg1, ...)
os.execle(path, arg0, arg1, ..., env)
os.execlp(file, arg0, arg1, ...)
os.execlpe(file, arg0, arg1, ..., env)
os.execv(path, args)
os.execve(path, args, env)
os.execvp(file, args)
os.execvpe(file, args, env)

本文链接:https://cs.pynote.net/sf/linux/prog/202202042/

-- EOF --

-- MORE --