执行新程序:execve()

系统调用execve()可以将新程序加载到某一进程的内存空间。在这一操作过程中,将丢弃就有程序,而进程的栈、数据以及堆段会被新程序的相应不见所替换。在执行了各种C语言函数库的运行时启动代码以及程序的初始化代码后,比如,C++静态构造函数,或者以gcc constructor属性生命的C语言函数,新程序会从main()函数处开始执行

由fork()生成的子进程对execve()的调用最为频繁,不以fork()调用为先导而单独调用execve()的做法在应用中实属罕见。

当进程fork出另外一个进程之后,子进程可以调用一种exec函数,exec会用磁盘上的一个新程序替换当前程序的正文段、数据段、堆栈和栈转,转而运行一个新程序,而不是用父进程的(调用exec并不创建新进程,所以新进程的进程ID仍然是子进程的进程ID)

基于系统调用 execve(),还提供了一系列冠以 exec 来命名的上层库函数,虽然接口方式各异,但功能相同。通常将调用这些函数加载一个新程序的过程称作 exec 操作,或是简单地以 exec()来表示。下面将先描述 execve(),然后再对相关库函数进行说明。

/*
* 功能: execve 执行文件
* 参数: argv[] 利用数组指针来传递给执行文件。
*        envp[] 环境变量数组
* 返回值:  如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
*/
int execve (const char *pathname, char *const argv[], char *const envp[])
  • 参数 pathname 包含准备载入当前进程空间的新程序的路径名,既可以是绝对路径,也可以是相对于调用进程当前工作目录(current working directory)的相对路径
  • 参数 argv 则指定了传递给新进程的命令行参数。该数组对应于 C 语言 main()函数的第 2个参数(argv),且格式也与之相同:是由字符串指针所组成的列表,以 NULL 结束。argv[0]的值则对应于命令名。通常情况下,该值与 pathname 中的 basename(路径名的最后部分)相同。
  • 最后一个参数 envp 指定了新程序的环境列表。参数 envp 对应于新程序的 environ 数组:也是由字符串指针组成的列表,以 NULL 结束,所指向的字符串格式为 name=value

Linux所特有的/proc/PID/exe文件是一个符号链接,包含PID对应进程中正在执行可执行文件的绝对路径名。

调用execve()之后,因为同一进程依然存在,所以进程ID扔保持不变。还有少量其他的进程属性也未发生变化。

  • 如果对 pathname 所指定的程序文件设置了 set-user-ID(set-group-ID)权限位,那么系统调用会在执行此文件时将进程的有效(effective)用户(组)ID 置为程序文件的属主(组)ID。利用这一机制,可令用户在运行特定程序时临时获取特权
  • 无论是否更改了有效 ID,也不管这一变化是否生效,execve()都会以进程的有效用户 ID 去覆盖已保存的(saved)set-user-ID,以进程的有效组 ID 去覆盖已保存的(saved)set-group-ID

由于是将调用程序取而代之,对execve()的成功调用将永不返回,而且也无需检查execve()的返回值,因为该值总是是雷打不动地等于-1。实际上,一旦函数返回,就表明发生了错误。通常,可以通过errno来判断出错原因。可能将errno返回的错误如下:

  • EACCES

    • 参数pathname没有指向一个常规(regular)文件,未对该文件赋予可执行权限,或者因为pathname中某一级目录不可搜索索(not searchable)(即,关闭了该目录的可执行权限)。
    • 欲执行的文件所属的文件系统是以MS_NOEXEC 方式挂载(mount)上。
  • ENOEXEC:
    • 尽管对pathname所指代文件赋予了可执行权限,但系统却无法识别其文件格式。
    • 脚本文件,如果没有包含用于指定脚本解释器(interpreter)(以字符#!开头)的起始行,就可能导致这一错误
  • ETXTBSY :欲执行的文件已被其他进程打开而且正把数据写入该文件中
  • E2BIG:参数列表和环境列表所需空间总和超出了允许的最大值
  • EPERM
    • 进程处于被追踪模式,执行者并不具有root权限,欲执行的文件具有SUID 或SGID 位。
    • 欲执行的文件所属的文件系统是以nosuid方式挂上,欲执行的文件具有SUID 或SGID 位元,但执行者并不具有root权限。
  • EFAULT:参数filename所指的字符串地址超出可存取空间范围。
  • ENAMETOOLONG:参数filename所指的字符串太长。
  • ENOENT :pathname 所指代的文件并不存在。
  • ENOMEM:核心内存不足
  • ENOTDIR:参数filename字符串所包含的目录路径并非有效目录
  • ELOOP:过多的符号连接
  • EIO I/O:存取错误
  • ENFILE:已达到系统所允许的打开文件总数。
  • EMFILE:已达到系统所允许单一进程所能打开的文件总数。
  • EINVAL:欲执行文件的ELF执行格式不只一个PT_INTERP节区
  • EISDIR:ELF翻译器为一目录
  • ELIBBAD:ELF翻译器有问题。

下面我们来看个例子:

  • 这个程序实现为新程序创建参数列表和环境列表,接着调用execve()来执行行由命令行参数(argv[1])所指定的程序路径名
#include <cstring>
#include <stdio.h>
#include <cstdlib>
#include <zconf.h>int main(int argc, char *argv[])
{char *argVec[10];           /* Larger than required */char *envVec[] = { "GREET=salut", "BYE=adieu", NULL };if (argc != 2 || strcmp(argv[1], "--help") == 0){printf("%s pathname\n", argv[0]);exit(EXIT_FAILURE);}/* Create an argument list for the new program */argVec[0] = strrchr(argv[1], '/');      /* Get basename from argv[1] */if (argVec[0] != NULL)argVec[0]++;elseargVec[0] = argv[1];argVec[1] = "hello world";argVec[2] = "goodbye";argVec[3] = NULL;           /* List must be NULL-terminated *//* Execute the program specified in argv[1] */execve(argv[1], argVec, envVec);perror("execve");          /* If we get here, something went wrong */exit(EXIT_FAILURE);
}
  • 下面程序是设计专供上面程序来执行的。该程序只是简单显示一下自身的命令行参数以及环境列表
extern char **environ;
int main(int argc, char *argv[])
{int j;char **ep;/* Display argument list */for (j = 0; j < argc; j++)printf("argv[%d] = %s\n", j, argv[j]);/* Display environment list */for (ep = environ; *ep != NULL; ep++)printf("environ: %s\n", *ep);exit(EXIT_SUCCESS);
}
  • 运行上面两个程序:

exec()库函数

下面的库函数为执行exec()提供了多种API选择,所有这些函数均构建于execve()调用之上,只是在为新程序指定程序名、参数列表以及环境变量的方式上有所不同:

#include <unistd.h>
/*
* __path: 路径名
*/
/*
*
* 功能:  execl()执行文件,
* 参数:  代表执行该文件时传递过去的argv(0),argv[1], ..., 最后一个参数必须用空指针(NULL)作结束.
* 返回值:  如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
*/
int execl (const char *__path, const char *__arg, ...)
/*
* 功能: execv 执行文件
* 参数: 利用数组指针来传递给执行文件。
* 返回值:  如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
*/
int execv (const char *__path, char *const __argv[])
/*
* 功能: execve 执行文件
* 参数: __argv[] 利用数组指针来传递给执行文件。
*        __envp[] 环境变量数组
* 返回值:  如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
*/
int execle (const char *__path, const char *__arg, ...)/*
* * __file:
*   * 如果__file中包含/,则是路径名
*   * 如果__file不包含/,则按照PATH环境变量,从指定的个目录中查找可执行文件
*           如果找到了一个可执行文件,但是该文件不是由连接编辑器尝试的机器可执行文件,则就认为该文件是一个shell脚本,就会调用/bin/sh,并将该file作为shell的输入
*/
int execvp (const char *__file, char *const __argv[])/*
* 功能: execlp() 从PATH 环境变量中查找文件并执行
* 参数:  代表执行该文件时传递过去的argv(0),argv[1], ..., 最后一个参数必须用空指针(NULL)作结束.
* 返回值:  如果执行成功则函数不会返回,执行失败则直接返回-1, 失败原因存于errno 中.
* **/
int execlp (const char *__file, const char *__arg, ...)
int fexecve (int __fd, char *const __argv[], char *const __envp[])

  • 大部分exec()函数要求提供预加载新程序的路径名。而execlp()和execvp()则允许只提供程序的文件名。系统会在由环境变量PATH所指定的目录列表中寻找相应的执行文件。这与 shell 对键入命令的搜索方式一致。这些函数名都包含字母 p(表示PATH),以示在操作上有所不同。如果文件名中包含“/”,则将其视为相对或绝对路径名,不再使用变量 PATH 来搜索文件
  • 函数execle()、execlp()和execl()要求开发者在调用中以字符串列表形式来指定参数,而不是使用数组来描述argv列表。首个参数对应于main()函数的 argv[0],因而通常与参数 filename 或 pathname 的 basename 部分相同。必须以 NULL 指针来终止参数列表,以便于各调用定位列表的尾部。这些函数的名称都包含字母 l(表示 list),以示与那些将以 NULL 结尾的数组作为参数列表的函数有所区别。后者(execve()、execvp()和 execv())名称中则包含字母 v(表示 vector)。
  • 函数execve()和execle()则允许开发者通过envp为新程序显式指定环境变量,其中envp是一个以 NULL 结束的字符串指针数组。这些函数命名均以字母 e(environment)结尾。其他 exec()函数将使用调用者的当前环境(即 environ 中内容)作为新程序的环境

execl函数 : 执行文件函数

#include <unistd.h>int main(int argc, char *argv[])
{// 执行/bin/ls,参数是 ls -al /etc/passwdexecl("/bin/ls","ls", "-al", "/etc/passwd", (char *)0);exit(0);
}

execv

#include <unistd.h>
int main(int argc, char *argv[])
{// 先从PATH中找到/bin/ls,参数是 ls -al /etc/passwdchar * myargv[ ]={"ls","-al","/etc/passwd",(char*)0};execv("/bin/ls",myargv);exit(0);
}

execve

#include <unistd.h>
int main(int argc, char *argv[])
{char * myargv[ ]={"ls","-al","/etc/passwd",(char*)0};char * myenvp[ ]={"PATH=/bin",0};execve("/bin/ls",myargv, myenvp);exit(0);
}

execlp()函数:从PATH环境变量中查找文件并执行

#include <unistd.h>int main(int argc, char *argv[])
{// 先从PATH中找到/bin/ls,参数是 ls -al /etc/passwdexeclp("ls", "ls","-al", "/etc/passwd", (char *)0);exit(0);
}

execvp

#include <unistd.h>
int main(int argc, char *argv[])
{char * myargv[ ]={"ls","-al","/etc/passwd",0};execvp("ls",myargv);exit(0);
}

fork子进程之后如何运行另外一个程序

  1. another_process
#include "stdlib.h"
#include "stdio.h"int main(int argc, char *argv[])
{int        i;for (i = 0; i < argc; i++)      /* echo all command-line args */printf("argv[%d]: %s\n", i, argv[i]);exit(0);
}
  1. fork_process
#include "apue.h"
#include <sys/wait.h>char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };int main(void)
{pid_t  pid;if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) {if (execle("/home/oceanstar/CLionProjects/apue/build/another_process", "another_process", "myarg1","my_arg2", (char *)0, env_init) < 0){err_sys("execle error");}}if (waitpid(pid, NULL, 0) < 0){err_sys("wait error");}if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) {if (execlp("ls","ls","-al","/etc/passwd",(char *)0)){err_sys("execlp error");}}exit(0);
}

环境变量PATH

函数execvp()和execlp()允许调用者只提供欲执行程序的文件名。二者均使用环境变量PATH来搜索文件。PATH的值是一个以:分割,由多个目录名,也将其称为路径前缀组成的字符串:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

对一个登录shell而言,由PATH值将由系统级和特定用户的shell启动脚本来设置。由于子进程继承其父进程的环境变量,shell执行每个命令时所创建的进程也就继承了shell的PATH

  • PATH 中指定的路径名既可以是绝对路径名(以/开始),也可以是相对路径名。对相对路径名的诠释是基于调用进程的当前工作目录(current working directory)。自SUSv3起, 当前工作目录应该用.(点)来显式指定
  • 如果没有定义变量 PATH,那么 execvp()和 execlp()会采用默认的路径列表:.:/usr/bin:/bin
  • 出于安全方面的考虑,通常会将当前工作目录排除在超级用户(root)的 PATH 之外

函数 execvp()和 execlp()会在 PATH 包含的每个目录中搜索文件,从列表开头的目录开始,直至成功执行了既定文件。

应该避免在设置了 set-user-ID 或 set-group-ID 的程序中调用 execvp()和 execlp(),至少应当慎用。需要特别谨慎地控制 PATH 环境变量,以防运行恶意程序。

将程序参数指定为列表

如果已知每个exec()的参数个数,调用execle()、execlp()或者execl()时就可以将参数作为列表传入。

下面程序与第一个例子相同,只是调用了 execle()而非 execve():

int
main(int argc, char *argv[])
{char *envVec[] = { "GREET=salut", "BYE=adieu", NULL };char *filename;if (argc != 2 || strcmp(argv[1], "--help") == 0){printf("%s pathname\n", argv[0]);exit(EXIT_FAILURE);}/* Execute the program specified in argv[1] */filename = strrchr(argv[1], '/');       /* Get basename from argv[1] */if (filename != NULL)filename++;elsefilename = argv[1];execle(argv[1], filename, "hello world", "goodbye", (char *) NULL, envVec);errExit("execle");          /* If we get here, something went wrong */
}

将调用者的环境传递给新程序

函数 execlp()、execvp()、execl()和 execv()不允许开发者显式指定环境列表,新程序的环境继承自调用进程。这一举措的后果可以说是喜忧参半。出于安全方面的考虑,有时希望确保程序能够在一个个已知(安全)的环境列表下运行

下面演示了如何运用函数 execl()使新程序继承调用者的环境。对于通过 fork()从 shell 处所继承的环境,程序首先用函数 putenv()进行了修改,接着执行 printenv 程序来显示环境变量 USER 和 SHELL 的值。运行程序的输出如下:

#include <cstring>
#include <stdio.h>
#include <cstdlib>
#include <zconf.h>int
main(int argc, char *argv[])
{printf("Initial value of USER: %s\n", getenv("USER"));if (putenv("USER=britta") != 0){perror("putenv");exit(EXIT_FAILURE);}/* exec printenv to display the USER and SHELL environment vars */execl("/usr/bin/printenv", "printenv", "USER", "SHELL", (char *) NULL);perror("execl");           /* If we get here, something went wrong */exit(EXIT_FAILURE);
}

执行由文件描述符指代的程序:fexecve()

glibc 自版本 2.3.2 开始提供函数 fexecve(),其行为与 execve()类似,只是指定将要执行的程序是以打开文件描述符 fd 的方式,而非通过路径名。有些应用程序需要打开某个程序文件,通过执行校验和(checksum)来验证文件内容,然后再运行该程序,这一场景就较为适宜使用函数 fexecve()

当然,即便没有 fexecve()函数,也可以调用 open()来打开文件,读取并验证其内容,并最终运行。然而,在打开与执行文件之间,存在将该文件替换的可能性(持有打开文件描述并不能阻止创建同名新文件),最终造成验证者并非执行者的情况。

Unix/Linux编程:exec()族函数相关推荐

  1. linux进程---exec族函数(execl, execlp, execv, execvp, )解释和配合fork的使用

    exec族函数函数的作用:         exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件.这里的可执行文件既可以是二 ...

  2. Linux进程—exec族函数 execl, execlp, execle, execv, execvp

    exec族函数的作用: 我们用fork函数创建新进程后,经常会在新进程中调用exec族函数去执行新的程序:当该进程调用exec族函数时,该进程被替代为新程序,因为exec族函数并不创建新进程,所以前后 ...

  3. Linux进程 exec族函数(execl,execlp,execle,execv,execvp,execcvpe)

    exec族函数的作用 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序.当进程调用exec函数时,该进程被完全替换为新程序.因为调用exec函数并不创建新进程,所以前 ...

  4. linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)

    原文链接:https://blog.csdn.net/u014530704/article/details/73848573 exec族函数函数的作用: 我们用fork函数创建新进程后,经常会在新进程 ...

  5. 10.linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)

    1.exec族函数函数的作用: 1.exec族函数的作用: 我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序.当进程调用exec函数时,该进程被完全替换为新程序.因为 ...

  6. Unix/Linux编程:fcntl函数总结

    fcntl VS ioctl fcntl函数,也就是file control,提供了对文件描述符的各种操作.另一个常见的控制文件描述符的属性和行为的系统调用是ioctl,而且ioctl比fcntl能够 ...

  7. Linux 进程11【exec族函数(execl, execlp, execle, execv, execvp, execvpe)】

    linux进程-exec族函数(execl, execlp, execle, execv, execvp, execvpe) 原文链接:https://blog.csdn.net/u014530704 ...

  8. Linux进程编程(PS: exec族函数、system、popen函数)

    目录 1.进程相关概念 程序和进程 查看系统中的进程 ps指令 top指令 进程标识符 使用getpid()获取 父进程,子进程 2.创建进程fork 进程创建发生了什么--C程序的存储空间如何分配 ...

  9. linux:exec族函数, exec族函数配合fork使用,system 函数,popen 函数

    1.exec族函数 精彩博文: https://blog.csdn.net/u014530704/article/details/73848573 ​ ​ ​ path:   比如说 ./a.out ...

  10. Linux进程5:exec族函数(execl, execlp, execle, execv, execvp, execvpe)总结及exec配合fork使用

    exec族函数(execl, execlp, execle, execv, execvp, execvpe)及exec配合fork使用 exec族函数函数的作用: 我们用fork函数创建新进程后,经常 ...

最新文章

  1. 矩阵的特征值、特征向量及其代码求解实现
  2. Gartner魔力象限到底有何“魔力”?
  3. MySQL DELETE 语句的一个简单介绍
  4. [恢]hdu 2147
  5. python可变类型和不可变深浅拷贝类型_python3笔记十四:python可变与不可变数据类型+深浅拷贝...
  6. 装饰器python的通俗理解_2道极好的Python算法题 | 带你透彻理解装饰器的妙用
  7. 在线2-36任意进制转换工具
  8. form表单中的name属性
  9. windows设置定时任务(win10任务计划程序)
  10. 斐讯路由器怎么设置虚拟服务器,斐讯无线路由器设置教程图解
  11. 雅虎终于死了:从1000亿到破产贱卖,最后连名字都没
  12. CANoe 入门 Step by step系列(二)CAPL编程
  13. 示例-Luat示例-HTTP
  14. EasyExcel实现Excel文件导入导出功能
  15. windows10将耳机当作麦克风
  16. 2006年9月15日
  17. fastdb 简介 查询语言
  18. 计算机与S7-200 PLC通信的步骤,建立与S7-200 CPU在线联系设置修改PLC通信参数
  19. 完美解决Github上下载项目失败或速度太慢的问题
  20. NSA组网--后台指标定义

热门文章

  1. 伦理是智慧的内核驱动
  2. 【新星计划】如何写好你的博客,涨粉技巧总结
  3. 关于unity debug.log日志不出现的问题
  4. 腾讯浏览服务X5内核集成
  5. Office Tips 3 - 如何设置电脑屏幕背景色为淡绿色
  6. 光滑曲线_光滑流形(4)
  7. 焦虑症应该怎么办?这六个缓解方法建议试试
  8. 恨一个人要比爱一个人付出更多的情感……
  9. [Kaggle]泰坦尼克号沉没预测
  10. 对于因果模型的常见评估函数:SHD 和 FDR