exec 家族库函数以及系统调用(execl,execle,execlp and execv,execvp,execve)
(1)exec函数说明
fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。
(2)在Linux中使用exec函数族主要有以下两种情况
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
(3)exec函数族语法
实际上,在Linux中并没有exec函数,而是有6个以exec开头的函数族,下表列举了exec函数族的6个成员函数的语法。
所需头文件 |
#include <unistd.h> |
函数说明 |
执行文件 |
函数原型 |
int execl(const char *pathname, const char *arg, ...) |
int execv(const char *pathname, char *const argv[]) |
|
int execle(const char *pathname, const char *arg, ..., char *const envp[]) |
|
int execve(const char *pathname, char *const argv[], char *const envp[]) |
|
int execlp(const char *filename, const char *arg, ...) |
|
int execvp(const char *filename, char *const argv[]) |
|
函数返回值 |
成功:函数不会返回 |
出错:返回-1,失败原因记录在error中 |
这6 个函数在函数名和使用语法的规则上都有细微的区别,下面就可执行文件查找方式、参数表传递方式及环境变量这几个方面进行比较说明。
① 查找方式:上表其中前4个函数的查找方式都是完整的文件目录路径(pathname),而最后2个函数(也就是以p结尾的两个函数)可以只给出文件名,系统就会自动从环境变量“$PATH”所指出的路径中进行查找。
前4个取路径名做参数,后两个则取文件名做参数。
当指定filename做参数时:
a. 如果filename中包含/,则将其视为路径名
b. 否则就按PATH环境变量搜索可执行文件。
② 参数传递方式:exec函数族的参数传递有两种方式,一种是逐个列举(l)的方式,而另一种则是将所有参数整体构造成指针数组(v)进行传递。
在这里参数传递方式是以函数名的第5位字母来区分的,字母为“l”(list)的表示逐个列举的方式,字母为“v”(vertor)的表示将所有参数整体构造成指针数组传递,然后将该数组的首地址当做参数传给它,数组中的最后一个指针要求是NULL。读者可以观察execl、execle、execlp的语法与execv、execve、execvp的区别。
③ 环境变量:exec函数族使用了系统默认的环境变量,也可以传入指定的环境变量。这里以“e”(environment)结尾的两个函数execle、execve就可以在envp[]中指定当前进程所使用的环境变量替换掉该进程继承的所以环境变量。
(3)PATH环境变量说明
PATH环境变量包含了一张目录表,系统通过PATH环境变量定义的路径搜索执行码,PATH环境变量定义时目录之间需用用“:”分隔,以“.”号表示结束。PATH环境变量定义在用户的.profile或.bash_profile中,下面是PATH环境变量定义的样例,此PATH变量指定在“/bin”、“/usr/bin”和当前目录三个目录进行搜索执行码。
PATH=/bin:/usr/bin:.
export $PATH
(4)进程中的环境变量说明
在Linux中,Shell进程是所有执行码的父进程。当一个执行码执行时,Shell进程会fork子进程然后调用exec函数去执行执行码。Shell进程堆栈中存放着该用户下的所有环境变量,使用execl、execv、execlp、execvp函数使执行码重生时,Shell进程会将所有环境变量复制给生成的新进程;而使用execle、execve时新进程不继承任何Shell进程的环境变量,而由envp[]数组自行设置环境变量。
变量是计算机系统用于保存可变值的数据类型,我们可以直接通过变量名称来提取到对应的变量值。在 Linux 系统中,环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录(HOME)、邮件存放位置(MAIL)等。
值得一提的是,Linux 系统中环境变量的名称一般都是大写的,这是一种约定俗成的规范。
我们可以使用 env 命令来查看到 Linux 系统中所有的环境变量,执行命令如下:
[root@localhost ~]# env
ORBIT_SOCKETDIR=/tmp/orbit-root
HOSTNAME=livecd.centos
GIO_LAUNCHED_DESKTOP_FILE_PID=2065
TERM=xterm
SHELL=/bin/bash
......
Linux 系统能够正常运行并且为用户提供服务,需要数百个环境变量来协同工作,但是,我们没有必要逐一学习每个变量,这里给大家列举了 10 个非常重要的环境变量,如表 1 所示。
环境变量名称 | 作用 |
---|---|
HOME | 用户的主目录(也称家目录) |
SHELL | 用户使用的 Shell 解释器名称 |
PATH | 定义命令行解释器搜索用户执行命令的路径 |
EDITOR | 用户默认的文本解释器 |
RANDOM | 生成一个随机数字 |
LANG | 系统语言、语系名称 |
HISTSIZE | 输出的历史命令记录条数 |
HISTFILESIZE | 保存的历史命令记录条数 |
PS1 | Bash解释器的提示符 |
邮件保存路径 |
Linux 作为一个多用户多任务的操作系统,能够为每个用户提供独立的、合适的工作运行环境,因此,一个相同的环境变量会因为用户身份的不同而具有不同的值。
例如,使用下述命令来查看 HOME 变量在不同用户身份下都有哪些值:
[root@localhost ~]# echo $HOME
/root
[root@localhost ~]# su - user1 <--切换到 user1 用户身份
[user1@localhost ~]$ echo $HOME
/home/user1
这里的 su 命令可以临时切换用户身份。
(5)exec函数族关系
第4位 |
统一为:exec |
|
第5位 |
l:参数传递为逐个列举方式 |
execl、execle、execlp |
v:参数传递为构造指针数组方式 |
execv、execve、execvp |
|
第6位 |
e:可传递新进程环境变量 |
execle、execve |
p:可执行文件查找方式为文件名 |
execlp、execvp |
事实上,这6个函数中真正的系统调用只有execve,其他5个都是库函数,它们最终都会调用execve这个系统调用,调用关系如下图12-11所示:
图12-11 exec函数族关系图
(6) exec调用举例如下
-
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
-
-
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
-
-
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
-
-
execv("/bin/ps", ps_argv);
-
-
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
-
-
execve("/bin/ps", ps_argv, ps_envp);
-
-
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
-
-
execvp("ps", ps_argv);
请注意exec函数族形参展开时的前两个参数,第一个参数是带路径的执行码(execlp、execvp函数第一个参数是无路径的,系统会根据PATH自动查找然后合成带路径的执行码),第二个是不带路径的执行码,执行码可以是二进制执行码和Shell脚本。
(7)exec函数族使用注意点
在使用exec函数族时,一定要加上错误判断语句。因为exec很容易执行失败,其中最常见的原因有:
① 找不到文件或路径,此时errno被设置为ENOENT。
② 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT。
③ 没有对应可执行文件的运行权限,此时errno被设置为EACCES。
下面分别是对6个exec家族函数的调用
execl
#include<stdio.h> #include<unistd.h> int main(int argc,char *argv[]){ printf("exercise execl\n"); int rc=fork(); if(rc==0){ if((execl("/bin/ls","ls","./",NULL))<0){ return -1; } } return 0; }
execle
#include<stdio.h> #include<unistd.h> int main(int argc,char *argv[]){ char *envp1[]={"PATH=/bin:/bin/usr",NULL} ; //environment variable printf("exercise execl\n"); int rc=fork(); if(rc==0){ if((execle("/bin/ls","ls","./",NULL,envp1))<0){ return -1; } }else{ printf("output or not output"); } return 0; }
[root@localhost codec5]# ./t4
exercise execl
output or not output[root@localhost codec5]# t1 t1.c t2 t2.c t3 t3.c t4 t4.c
execlp
#include<stdio.h> #include<unistd.h> int main(int argc,char *argv[]){ //char *envp1[]={"PATH=/bin:/bin/usr",NULL} ; //environment variable printf("exercise execl\n"); int rc=fork(); if(rc==0){ if((execlp("ls","ls",NULL))<0){ //execlp 第五个p代表可以自己查找地 return -1; //址,只需要输入文件名子即可 } }else{ printf("output or not output"); } return 0; } [root@localhost codec5]# ./t4 exercise execl output or not output[root@localhost codec5]# t1 t1.c t2 t2.c t3 t3.c t4 t4.c
execv
#include<stdio.h> #include<sys/wait.h> #include<unistd.h> int main(int argc,char *argv[]){ char *argv1[]={"ls","-l",NULL}; //char *envp1[]={"PATH=/bin:/bin/usr",NULL} ; //environment variable printf("exercise execl\n"); int rc=fork(); if(rc==0){ if((execv("/bin/ls",argv1))<0){ //execv 第4个v代表传入字符串指针数组 return -1; } }else{ wait(NULL); printf("output or not output"); } return 0; } 运行结果如下 [root@localhost codec5]# ./t4 exercise execl 总用量 48 -rwxr-xr-x. 1 root root 5416 10月 17 22:41 t1 -rw-r--r--. 1 root root 570 10月 17 22:41 t1.c -rwxr-xr-x. 1 root root 5338 10月 17 23:53 t2 -rw-r--r--. 1 root root 443 10月 17 23:53 t2.c -rwxr-xr-x. 1 root root 5083 10月 15 23:48 t3 -rw-r--r--. 1 root root 284 10月 15 23:48 t3.c -rwxr-xr-x. 1 root root 5175 10月 18 20:09 t4 -rw-r--r--. 1 root root 456 10月 18 20:09 t4.c output or not output[root@localhost codec5]#
execve
#include<stdio.h> #include<sys/wait.h> #include<unistd.h> int main(int argc,char *argv[]){ char *argv1[]={"ls","-l",NULL}; char *envp1[]={"PATH=/bin:/bin/usr",NULL} ; //environment variable printf("exercise execl\n"); int rc=fork(); if(rc==0){ if((execve("/bin/ls",argv1,envp1))<0){ //execve 第6个e代表可以改变环境变量 return -1; } }else{ wait(NULL); printf("output or not output"); } return 0; } [root@localhost codec5]# ./t4 exercise execl total 48 -rwxr-xr-x. 1 root root 5416 Oct 17 22:41 t1 -rw-r--r--. 1 root root 570 Oct 17 22:41 t1.c -rwxr-xr-x. 1 root root 5338 Oct 17 23:53 t2 -rw-r--r--. 1 root root 443 Oct 17 23:53 t2.c -rwxr-xr-x. 1 root root 5083 Oct 15 23:48 t3 -rw-r--r--. 1 root root 284 Oct 15 23:48 t3.c -rwxr-xr-x. 1 root root 5212 Oct 18 20:14 t4 -rw-r--r--. 1 root root 459 Oct 18 20:14 t4.c output or not output[root@localhost codec5]# cat t4.c
execvp
#include<stdio.h> #include<unistd.h> int main(int argc,char *argv[]){ char *argv1[]={"ls","-l",NULL}; char *envp1[]={"PATH=/bin:/bin/usr",NULL} ; //environment variable printf("exercise execl\n"); int rc=fork(); if(rc==0){ if((execvp("ls",argv1))<0){ //execvp 第4个v代表传入字符串指针数组 return -1; } }else{ printf("output or not output"); } return 0; } [root@localhost codec5]# ./t4 exercise execl output or not output[root@localhost codec5]# 总用量 48 -rwxr-xr-x. 1 root root 5416 10月 17 22:41 t1 -rw-r--r--. 1 root root 570 10月 17 22:41 t1.c -rwxr-xr-x. 1 root root 5338 10月 17 23:53 t2 -rw-r--r--. 1 root root 443 10月 17 23:53 t2.c -rwxr-xr-x. 1 root root 5083 10月 15 23:48 t3 -rw-r--r--. 1 root root 284 10月 15 23:48 t3.c -rwxr-xr-x. 1 root root 5120 10月 18 20:06 t4 -rw-r--r--. 1 root root 418 10月 18 20:06 t4.c
参考的有一篇特别好的博客:https://blog.csdn.net/amoscykl/article/details/80354052
时间: 2020-10-18 20:33:50