Linux 系统调用 exec

exec

exec 函数族的作用是执行参数所指定的可执行文件或脚本。这个函数会替换掉当前进
程的内存镜像,也就是执行这个函数成功后,调用该函数的程序就不存在了,像是移
花接木一样。Linux下的的 exec 并不是一个函数,而是一族函数,一共有如下几个函
数,原型为:

1
2
3
4
5
6
7
8
9
10
11
12
13
int execve(const char *filename, char *const argv[],
char *const envp[]);

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

这 7 个函数中只有execve()是真正的系统调用函数,其它几个最终都会调用它。
exec 后面的字母的含义为:

字母 助记 含义
l list 参数逐个列举
v vector 用向量(字符串数组)传递参数
p path (I guess~) $PATH中查找可执行文件
e environment 设置环境变量

Special semantics for execlp() and execvp()
The execlp(), execvp(), and execvpe() functions duplicate the actions of
the shell in searching for an executable file if the specified filename
does not contain a slash (/) character. The file is sought in the
colon-separated list of directory pathnames specified in the PATH
environment variable. If this variable isn’t defined, the path list
defaults to the current directory followed by the list of directories
returned by confstr(_CS_PATH). (This confstr(3) call typically returns
the value “/bin:/usr/bin”.)

意思是:如果指定的文件名不包含/的话,execlp()`execvp()execvpe()会重 复 shell 查找可执行文件的行为(即从环境变量PATH所指定的目录中寻找文件)。 如果变量PATH没有设置,会使用当前目录加上confstr(_CS_PATH, …)`的输出作
为目录集并从中寻找可执行文件。

当目录里没有/且没有设置PATH时,man exec中说会将当前目录加入查找可执行
文件的目录集,但我测试似乎并没有当前目录,也可能是我理解错了。测试代码如下:

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

int main(int argc, char *argv[])
{
if (execlp("argv", 0)) {
perror("execlp failed");
exit(1);
}
return 0;
}

命令如下:

1
2
3
4
5
6
$ clang execlp.c -o execlp
$ ./execlp
execlp failed: No such file or directory
$ unset PATH
$ ./execlp
execlp failed: No such file or directory

全都提示找不到文件。以下是一些例子。

例子 1

先写一个辅助程序,打印出程序执行的一些信息(argv 和 envp):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* argv.c
* $ clang argv.c -o argv
*/
#include <stdio.h>

int main(int argc, char *argv[], char *envp[])
{
extern char **environ;
int i;

printf("pid: %d\n", getpid());
/* print argv */
printf("%s\n", "argv");
for (i=0; i < argc; i++) {
printf(" argv[%d]: %s\n", i, argv[i]);
}

/* print envp */
printf("%s\n", "envp(0~4)");
for (i=0; envp[i] && i != 5; i++) {
printf(" envp[%d]: %s\n", i, envp[i]);
}

/* print environ, should be the same with envp */
printf("%s\n", "environ(0~4)");
for (i=0; environ[i] && i != 5; i++) {
printf(" environ[%d]: %s\n", i, environ[i]);
}
return 0;
}

由于环境变量太多,这时只打印出前 5 个。下面是execve()的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* execve.c */
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

int main(int argc, char *argv[], char *envp[])
{
char *const pg_argv[] = {"./argv", "-dummy-param", NULL};
char *pg_envp[] = {"some_key_val_pair", NULL};

printf("0 -----------------------------\n");
if (fork()) {
wait(0);
} else {
if (execve("./argv", pg_argv, envp) == -1)
perror("execve error: ");
}

printf("1 -----------------------------\n");
if (fork()) {
wait(0);
} else {
if (execve("./argv", pg_argv, pg_envp) == -1)
perror("execve error: ");
}
return 0;
}

execve()执行成功是不会返回的,只有在失败的时候会返回-1,并设置相应的
errno。在我电脑上的执行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ clang argv.c -o argv
$ clang execve.c -o execve
$ ./execve
pid of parent: 11900
0 -----------------------------
pid: 11901
argv
argv[0]: ./argv
argv[1]: -dummy-param
envp(0~4)
envp[0]: XAUTHORITY=/tmp/xauth-1000-_0
envp[1]: LC_PAPER=en_US.UTF-8
envp[2]: QT_ACCESSIBILITY=1
envp[3]: LC_MEASUREMENT=en_US.UTF-8
envp[4]: PAM_KWALLET_LOGIN=/tmp/kwallet_qi.socket
environ(0~4)
environ[0]: XAUTHORITY=/tmp/xauth-1000-_0
environ[1]: LC_PAPER=en_US.UTF-8
environ[2]: QT_ACCESSIBILITY=1
environ[3]: LC_MEASUREMENT=en_US.UTF-8
environ[4]: PAM_KWALLET_LOGIN=/tmp/kwallet_qi.socket
1 -----------------------------
pid: 11902
argv
argv[0]: ./argv
argv[1]: -dummy-param
envp(0~4)
envp[0]: some_key_val_pair
environ(0~4)
environ[0]: some_key_val_pair

例子 2

还有一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* execve_pid.c */

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

int main(int argc, char *argv[], char *envp[])
{
char *const pg_argv[] = {"./argv", "-dummy-param", NULL};
char *pg_envp[] = {"some_key_val_pair", NULL};

printf("pid: %d\n", getpid());

printf("---------- before execve -----------\n");
if (execve("./argv", pg_argv, pg_envp) == -1)
perror("execve error: ");

return 0;
}

执行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
$ clang execve_pid.c -o execve_pid
$ ./execve_pid
pid: 11956
---------- before execve -----------
pid: 11956
argv
argv[0]: ./argv
argv[1]: -dummy-param
envp(0~4)
envp[0]: some_key_val_pair
environ(0~4)
environ[0]: some_key_val_pair

可以看到执行了execve()后进程 ID 没有变化,可知原进程被替换了。而且在指定
envp参数时,进程不再继承 shell 的环境变量。

References

0%