exec 族的组成:
在 Linux 中, 并不存在一个 exec() 的函数形式, exec 指的是一组函数, 一共有 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 *path, char *const argv[], char *const envp[]);
其中只有 execve 是真正意义上的系统调用, 其它都是在此基础上经过包装的库函数.
exec 族函数的作用:
exec 函数族的作用是根据指定的文件名找到可执行文件, 并用它来取代调用进程的内容, 换句话说, 就是在调用进程内部执行一个可执行文件. 这里的可执行文件既可以是二进制文件, 也可以是任何 Linux 下可执行的脚本文件.
与一般情况不同, exec 函数族的函数执行成功后不会返回, 因为调用进程的实体, 包括代码段, 数据段和堆栈等都已经被新的内容取代, 只留下进程 ID 等一些表面上的信息仍保持原样, 颇有些神似 "三十六计" 中的 "金蝉脱壳". 看上去还是旧的躯壳, 却已经注入了新的灵魂. 只有调用失败了, 它们才会返回一个 - 1, 从原程序的调用点接着往下执行.
现在我们应该明白了, Linux 下是如何执行新程序的, 每当有进程认为自己不能为系统和用户做出任何贡献了, 他就可以发挥最后一点余热, 调用任何一个 exec, 让自己以新的面貌重生; 或者, 更普遍的情况是, 如果一个进程想执行另一个程序, 它就可以 fork 出一个新进程, 然后调用任何一个 exec, 这样看起来就好像通过执行应用程序而产生了一个新进程一样.
事实上第二种情况被应用得如此普遍, 以至于 Linux 专门为其作了优化, 我们已经知道, fork 会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去, 这些拷贝的动作很消耗时间, 而如果 fork 完之后我们马上就调用 exec, 这些辛辛苦苦拷贝来的东西又会被立刻抹掉, 这看起来非常不划算, 于是人们设计了一种 "写时拷贝 (copy-on-write)" 技术, 使得 fork 结束后并不立刻复制父进程的内容, 而是到了真正实用的时候才复制, 这样如果下一条语句是 exec, 它就不会白白作无用功了, 也就提高了效率.
exec 函数族关系:
事实上, 这 6 个函数中真正的系统调用只有 execve, 其他 5 个都是库函数, 它们最终都会调用 execve 这个系统调用, 调用关系如下图所示:
exec 函数族使用举例:
- #ifdef HAVE_CONFIG_H
- #include <config.h>
- #endif
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <string.h>
- #include <errno.h>
- int main(int argc, char *argv[])
- {
- // 以 NULL 结尾的字符串数组的指针, 适合包含 v 的 exec 函数参数
- char *arg[] = {"ls", "-a", NULL};
- /**
- * 创建子进程并调用函数 execl
- * execl 中希望接收以逗号分隔的参数列表, 并以 NULL 指针为结束标志
- */
- if( fork() == 0 )
- {
- // in clild
- printf( "1------------execl------------\n" );
- if( execl( "/bin/ls", "ls","-a", NULL ) == -1 )
- {
- perror( "execl error" );
- exit(1);
- }
- }
- /**
- * 创建子进程并调用函数 execv
- *execv 中希望接收一个以 NULL 结尾的字符串数组的指针
- */
- if( fork() == 0 )
- {
- // in child
- printf("2------------execv------------\n");
- if( execv( "/bin/ls",arg) < 0)
- {
- perror("execv error");
- exit(1);
- }
- }
- /**
- * 创建子进程并调用 execlp
- *execlp 中
- *l 希望接收以逗号分隔的参数列表, 列表以 NULL 指针作为结束标志
- *p 是一个以 NULL 结尾的字符串数组指针, 函数可以 DOS 的 PATH 变量查找子程序文件
- */
- if( fork() == 0 )
- {
- // in clhild
- printf("3------------execlp------------\n");
- if( execlp( "ls", "ls", "-a", NULL ) < 0 )
- {
- perror( "execlp error" );
- exit(1);
- }
- }
- /**
- * 创建子里程并调用 execvp
- *v 望接收到一个以 NULL 结尾的字符串数组的指针
- *p 是一个以 NULL 结尾的字符串数组指针, 函数可以 DOS 的 PATH 变量查找子程序文件
- */
- if( fork() == 0 )
- {
- printf("4------------execvp------------\n");
- if( execvp( "ls", arg ) < 0 )
- {
- perror( "execvp error" );
- exit( 1 );
- }
- }
- /**
- * 创建子进程并调用 execle
- *l 希望接收以逗号分隔的参数列表, 列表以 NULL 指针作为结束标志
- *e 函数传递指定参数 envp, 允许改变子进程的环境, 无后缀 e 时, 子进程使用当前程序的环境
- */
- if( fork() == 0 )
- {
- printf("5------------execle------------\n");
- if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 )
- {
- perror("execle error");
- exit(1);
- }
- }
- /**
- * 创建子进程并调用 execve
- * v 希望接收到一个以 NULL 结尾的字符串数组的指针
- * e 函数传递指定参数 envp, 允许改变子进程的环境, 无后缀 e 时, 子进程使用当前程序的环境
- */
- if( fork() == 0 )
- {
- printf("6------------execve-----------\n");
- if( execve( "/bin/ls", arg, NULL ) == 0)
- {
- perror("execve error");
- exit(1);
- }
- }
- return EXIT_SUCCESS;
- }
运行结果:
- 1------------execl------------
- . .. .deps exec exec.o .libs Makefile
- 2------------execv------------
- . .. .deps exec exec.o .libs Makefile
- 3------------execlp------------
- . .. .deps exec exec.o .libs Makefile
- 4------------execvp------------
- . .. .deps exec exec.o .libs Makefile
- 5------------execle------------
- . .. .deps .libs Makefile exec exec.o
- 6------------execve-----------
- . .. .deps .libs Makefile exec exec.o
整理于百度百科 & https://blog.csdn.net/zjwson/article/details/53337212
来源: http://www.bubuko.com/infodetail-2873903.html