C/C++中调用外部程序的接口:exec系列函数、system、popen
一、exec库函数
基于系统调用execve()
,提供了一系列冠以exec来命名的上层库函数,虽然接口方式各异,但功能一致。
1.1 执行新程序:execve()
系统调用execve()
可以将新的程序加载到调用者进程的内存空间。这一操作过程中,进程的栈、数据以及堆段会被新的程序相应部件替换。一般由fork()
生成的子进程对execve()
的调用最常见。
execve()
系统调用定义如下:
1 | #include <unistd.h> |
execve()
系统调用参数说明:
- pathname是准备载入当前进程空间的新程序的路径名。pathname可以是绝对路径,也可以是相对调用进程当前工作目录(current working directory)的相对路径
- argv指定了传递给新进程的命令行参数。argv是由字符串指针组成的列表,并且以NULL值结尾。其中argv[0]的值对应于命令名,通常来说与pathname中的basename(路径的最后部分)值相同。
- envp指定了新程序的环境列表。对应于新程序的environ数组,也是由字符串指针组成的列表,格式为
name=value
,并且以NULL值结尾。
调用execve()之后,因为同一进程依然存在,所以进程ID不变。
1.2 exec()库函数
基于系统调用,库函数提供了多种API选择,这些API都构建于execve()
之上,只是在为新程序指定程序名,参数列表以及环境变量的方式上有所不同。
exec()库函数API如下:
1 | #include <unistd.h> |
exec()
库函数之间的差异总结
函数 | 对程序文件的描述(-,p) | 对参数的描述(v,l) | 环境变量来源(e,-) |
---|---|---|---|
execve() | 路径名 | 数组 | envp参数 |
execle() | 路径名 | 列表 | envp参数 |
execlp() | 文件名+PATH | 列表 | 调用者的environ |
execvp() | 文件名+PATH | 数据 | 调用者的environ |
execv() | 路径名 | 数组 | 调用者的environ |
execl() | 路径名 | 列表 | 调用者的environ |
fexecve()
执行由文件描述符指定的程序。有些应用程序需要打开某个文件,通过执行校验和之后再运行该程序,这一场景就比较适合使用fexecve()
,该接口定义如下:
1 | #define _GNU_SOURCE |
1.3 解释器脚本
解释器(interpreter)是能够读取并执行文本格式命令的程序。(相形之下,编译器是将源代码译为可在真实或者虚拟机上执行的机器语言)解释器通常可以从被称为脚本的文件中读取和执行命令。
UNIX内核运行解释器脚本的方式与二进制程序无异,前提是:1、必须赋予脚本可执行权限;2、文件的起始行必须执行运行脚本的解释器路径名,格式如下:
1 | #! interpreter |
当调用execve()
来运行脚本时,execve()
如果检测到传入的文件以两字节序列开始,就会析取该行剩余的部分(路径名和参数)。然后按照如下格式执行解释器程序:
1 | interpreter-path [optional-arg] script-path arg |
interpreter-path(解释器路径)和optional-arg(可选参数)都取自脚本的#!
行,script-path时传递给execve()
的路径名,arg是通过argv传递给execve()
的参数列表(argv[0]排除在外)。
二、执行shell命令:system
程序可以通过调用system()
函数来执行任意的shell命令,函数定义如下:
1 | #include <stdlib.h> |
函数system()
创建一个子进程来运行shell,并以之执行命令command。所以使用system()
函数运行命令至少会创建两个进程,一个用于运行shell,另一个或者多个用于运行shell所执行的命令(如果对效率或者速度有要求,最好直接调用fork()
和exec()
来执行既定程序)。
三、通过管道与shell命令通信popen
管道的一个常见的用途是执行shell命令并读取或向其发送一些输入。popen()
和pclose()
简化了这个任务,函数定义如下:
1 | #include <stdio.h> |
popen()
函数创建了一个管道,然后创建了一个子进程来执行shell,而shell又创建了一个子进程来执行command字符串。其中mode是一个字符串,它确定调用进程是从管道中读取数据(mode是r)或者是写入到管道中(mode是w),由于管道是单向的,所以无法在执行的command中进行双向通信。
与使用pipe()创建的管道一样,当从管道中读取数据时,调用进程在command关闭管道的写入端之后会看到文件结束;当向管道写入数据时,如果command已经关闭了管道的读取端,那么调用进程会收到SIGPIPE并的到EPIPE错误。
当IO结束之后可以使用pclose()
函数关闭管道并等待子进程中的shell终止(不应该使用fclose()
函数,因为它不会等待子进程)。pclose()
在成功时会返回子进程中shell的终止状态(即shell命令执行最后一条命令的终止状态,除非是被信号杀死的)。与system()
一样,如果无法执行shell,pclose()
会返回一个值就像是子进程中的shell调用_exit(127)来终止一样。如果发生了其它错误,那么pclose()
返回-1。
参考文献
【0】Linux/UNIX系统编程手册
原文链接: https://www.delta1037.cn/2021/C_C++/C-C++调用外部程序/
版权声明: 转载请注明出处.