我们在讲,文件I/O的时候,简单提到过 exec 函数,讲到 vfork 的时候,也有用到。下面我们来详细介绍下它。
一、exec 函数族概述
与 fork 或 vfork 函数不同,exec 函数不是创建调用进程的子进程,而是创建一个新的进程取代调用进程自身。新进程会用自己的全部地址空间,覆盖调用进程的地址空间,但进程的 PID 保持不变。exec 只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
exec 不是一个函数而是一堆函数,一般称为 exec 函数族。它们的功能是一样的,用法也很相近,只是参数的形式和数量略有不同。
exec函数族的作用:根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
#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 *path, char *const argv[], char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
7 个函数返回值:若出错,返回 -1;若成功,不返回
这些函数之间的第一个区别:
4 个函数取路径名作为参数,2 个函数则取文件名作为参数,最后一个取文件描述符作为参数。当指定 file 作为参数时:如果 file 中包含 /,则就将其视为路径名。否则就按 PATH 环境变量,在它所指定的各目录中搜索可执行文件。
环境变量,我们之前专门讲过,参看:UNIX再学习 -- 环境变量
PATH 变量包含了一张目录表(称为路径前缀),目录之间用冒号(:)分隔。例如,下面 name = value 环境字符串指定在 4 个目录中进程搜索。
PATH=/bin:/usr/bin:/usr/local/bin:.
最后的路径前缀 . 表示当前目录。(零长前缀也表示当前目录。在 value 的开始处可用 : 表示,在行中间则要用 :: 表示,在行尾以 : 表示)。
第二个区别与参数表的传递有关(l 表示表 (list), v 表示矢量(vector))。
函数 execl、execlp 和 execle 要求将新程序的每个命令行参数都说明为一个单独的参数,这种参数以空指针结尾。对于另外 4 个函数 (execv、execvp、execve、fexecve),则应先构造一个指向各参数的指针数组,然后将该数组地址作为这 4 个函数的参数。
第三个区别与向新程序传递环境表相关。
以 e 结尾的 3 个函数(execle 和 execve 和 fexecve)可以传递一个指向字符串指针数组的指针。其他四个函数则使用调用进程中的 environ 变量为新程序复制现存的环境。
这些函数,它们的函数名都是在 exec 后面加上一到两个字符后缀,不同的字符后缀代表不同的含义。
-l: 即 list ,新进程的命令行参数以字符指针列表 (const char *arge, ...) 的形式传入,列表以空指针结束。
-p: 即 path,若第一个参数中不包含“/”,则将其视为文件名,并根据 PATH 环境变量搜索该文件。
-e: 即 environment,新进程的环境变量以字符指针数组 (cahr *const envp[]) 的形式传入,数组以空指针结束,不指定环境变量则从调用进程复制。
-v: 即 vector,新进程的命令行参数以字符指针数组 (char *const argv[]) 的形式传入,数组以空指针结束。
到此,将 exec 函数族 7 个函数的区别和关系,简单讲了一下。下面我们就一一介绍:
二、execl 函数
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
若出错,返回 -1, 若成功,不返回
1、函数解析
2、示例说明
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execl ("/bin/ls", "ls", "-l", NULL) == -1)
perror ("execl"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
输出结果:
父进程开始执行
子进程开始执行
总用量 16
-rwxr-xr-x 1 root root 7380 Apr 20 10:22 a.out
-rw-r--r-- 1 root root 383 Apr 20 10:22 test.c
-rw-r--r-- 1 root root 151 Apr 20 09:56 test.c~
父进程执行结束
3、示例解析
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execl ("/bin/ls", "ls", "-l", 0) == -1)
perror ("execlp"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
编译:
警告: 函数调用中缺少哨兵 [-Wformat]
三、execlp 函数
#include <unistd.h>
int execlp(const char *file, const char *arg, ...);
若出错,返回 -1, 若成功,不返回
1、函数解析
2、示例说明
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execlp ("ls", "ls", "-l", NULL) == -1)
perror ("execlp"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
输出结果:
父进程开始执行
子进程开始执行
总用量 16
-rwxr-xr-x 1 root root 7381 Apr 26 15:02 a.out
-rw-r--r-- 1 root root 480 Apr 26 15:02 test.c
-rw-r--r-- 1 root root 846 Apr 25 17:01 test.c~
父进程执行结束
3、示例解析
开始就讲到,当指定 file 作为参数时,如果 file 中包含 /,则就将其视为路径名。否则就按 PATH 环境变量,在它所指定的各目录中搜索可执行文件。 这也就是 execlp 和 execl 的区别了。四、execle 函数
#include <unistd.h>
int execle(const char *path, const char *arg, ..., char * const envp[]);
若出错,返回 -1, 若成功,不返回
1、函数解析
2、示例说明
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execle ("/bin/ls", "ls", "-l", NULL, NULL) == -1)
perror ("execle"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
输出结果:
父进程开始执行
子进程开始执行
total 16
-rwxr-xr-x 1 root root 7381 Apr 26 15:32 a.out
-rw-r--r-- 1 root root 491 Apr 26 15:32 test.c
-rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~
父进程执行结束
3、示例解析
倒数第二个参数为,传递一个指向字符串指针数组的指针。五、execv 函数
#include <unistd.h>
int execv(const char *path, char *const argv[]);
若出错,返回 -1, 若成功,不返回
1、函数解析
2、示例说明
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
char *arg[] = {"ls", "-l", NULL};
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execv ("/bin/ls", arg) == -1)
perror ("execv"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
输出结果:
父进程开始执行
子进程开始执行
总用量 16
-rwxr-xr-x 1 root root 7380 Apr 26 15:48 a.out
-rw-r--r-- 1 root root 505 Apr 26 15:48 test.c
-rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~
父进程执行结束
3、示例解析
六、execvp 函数
#include <unistd.h>
int execvp(const char *file, char *const argv[]);
若出错,返回 -1, 若成功,不返回
1、函数解析
2、示例说明
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
char *arg[] = {"ls", "-l", NULL};
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execvp ("ls", arg) == -1)
perror ("execvp"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
输出结果:
父进程开始执行
子进程开始执行
总用量 16
-rwxr-xr-x 1 root root 7381 Apr 26 15:54 a.out
-rw-r--r-- 1 root root 502 Apr 26 15:54 test.c
-rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~
父进程执行结束
3、示例解析
当指定 file 作为参数时,如果 file 中包含 /,则就将其视为路径名。否则就按 PATH 环境变量,在它所指定的各目录中搜索可执行文件。这也就是 execvp 和 execv 的区别了。七、execve 函数
#include <unistd.h>
int execvpe(const char *file, char *const argv[], char *const envp[]);
若出错,返回 -1, 若成功,不返回
1、函数解析
execve() 用来执行参数 file 字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。2、示例说明
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
char *arg[] = {"ls", "-l", NULL};
printf ("父进程开始执行\n");
pid_t pid = vfork ();
if (pid == -1)
perror ("vfork"), exit (1);
if (pid == 0)
{
printf ("子进程开始执行\n");
if (execve ("/bin/ls", arg, NULL) == -1)
perror ("execve"), _exit (1);
}
sleep (1);
printf ("父进程执行结束\n");
return 0;
}
输出结果:
父进程开始执行
子进程开始执行
total 16
-rwxr-xr-x 1 root root 7381 Apr 26 16:12 a.out
-rw-r--r-- 1 root root 513 Apr 26 16:12 test.c
-rw-r--r-- 1 root root 488 Apr 26 15:13 test.c~
父进程执行结束
3、示例解析
第一个参数为 file,最后一个参数为 NULL,这没什么讲的了。八、fexecve 函数
#include <unistd.h>
int fexecve(int fd, char *const argv[], char *const envp[]);
若出错,返回 -1, 若成功,不返回
1、函数解析
九、find 和 xargs 命令相关内容
1、更改命令执行方式。
2、修改 ARG_MAX 的大小 (了解,系统不对实现不了)
(2)getconf ARG_MAX 查看ARG_MAX设置值大小,2097152
(3)调整ncargs占用字节:chdev -l sys0 -a ncargs=8 表示设置ncargs占用8字节,增加这个值就可以修改ARG_MAX参数的设置了
查找所有jpg文件,并重命名
root@zslf-virtual-machine:/mnt/test# ls
abc.txt def.txt ef.jpg hh.jpg images.tar.gz sd.jpg
root@zslf-virtual-machine:/mnt/test# find ./ -name "*.jpg" -type f | xargs -i cp {} {}.old
root@zslf-virtual-machine:/mnt/test# ls
abc.txt def.txt ef.jpg ef.jpg.old hh.jpg hh.jpg.old images.tar.gz sd.jpg sd.jpg.old
或使用 -exec
root@zslf-virtual-machine:/mnt/test# ls
abc.txt def.txt ef.jpg hh.jpg images.tar.gz sd.jpg
root@zslf-virtual-machine:/mnt/test# find ./ -name "*.jpg" -exec cp {} {}.old \;
root@zslf-virtual-machine:/mnt/test# ls
abc.txt def.txt ef.jpg ef.jpg.old hh.jpg hh.jpg.old images.tar.gz sd.jpg sd.jpg.old
十、最常见的错误
找不到文件或路径,此时errno被设置为ENOENT;
数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
没有对要执行文件的运行权限,此时errno被设置为EACCES。