- 作者简介:一名后端开发人员,每天分享后端开发以及人工智能相关技术,行业前沿信息,面试宝典。
- 座右铭:未来是不可确定的,慢慢来是最快的。
- 个人主页:极客李华-CSDN博客
- 合作方式:私聊+
- 这个专栏内容:BAT等大厂常见后端java开发面试题详细讲解,更新数目100道常见大厂java后端开发面试题。
- 我的CSDN社区:https://bbs.csdn.net/forums/99eb3042821a4432868bb5bfc4d513a8
- 微信公众号,抖音,b站等平台统一叫做:极客李华,加入微信公众号领取各种编程资料,加入抖音,b站学习面试技巧,职业规划
C语言进程(第三章,exec函数族,execl,execlp,execle,execv,execvp,execve)
简介
本文讲解C语言进程中的exec函数族相关知识,相关函数有excel,execlp,execle,execv,execvp,execve。
exec函数族:
是的,我知道exec函数族。
在Unix及Unix类操作系统中,exec
函数族(即 execl(), execle(), execlp(), execv(), execvp()
等函数)被广泛用于让一个进程执行另一个可执行程序文件。当一个进程调用 exec()
函数时,该进程的全部内容都会被新程序替换,而新程序从 main()
函数开始执行。
这个过程发生的一瞬间,原进程几乎无法保存自己的状态(例如内存中分配的变量和动态连接库)。因此,exec 被认为是一种创建新进程的机制而不是重新启动现有进程的机制。
下面是 exec
函数族的常见形式:
int execl(const char *path, const char *arg, ...);
: 用于执行指定路径的可执行文件(需要使用绝对或相对路径),并用数组方式传入参数。int execlp(const char *file, const char *arg, ...);
: 用于执行当前目录或环境变量 PATH 变量下可执行文件,并用数组方式传入参数。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[]);
: 用于执行当前目录或环境变量 PATH 变量下可执行文件,并用字符指针数组方式传入参数。
这些函数的第一个参数是字符串类型、表示需要运行的可执行程序或文件的名字或路径,接着的参数则依次说明所带参数。在可变参数列表(...
)的末尾时需要加上 NULL。
举个例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
// 使用execl函数族中的某个方法实现对 ls 命令的调用
execl("/bin/ls", "ls", "-l", "/tmp", (char *)0);
printf("Reached here\n"); // 因为已经被替换为新程序而不会执行此行代码
return 0;
}
以上程序将在 /tmp
目录中显示所有文件的详细信息。
execl
在 C 语言中,execl()
函数用于执行可执行文件,它是 exec()
函数族的一员。当一个应用程序使用 exec 函数时,该程序原来的空间就会被完全覆盖,根据新程序可能需要的空间自动分配内存,然后从新程序 main() 函数的头部开始阅读到尾部,直到新程序结束。
在 execl()
中,第一个参数为要执行的程序路径名称;若这个路径其实就是只含有文件名的相对路劲名称,函数也会自动在系统环境变量 PATH 的指定路径下寻找并执行;而剩余的各个参数则是 main()
函数可接受的命令行参数。举个例子:
#include <stdio.h>
#include <unistd.h>
int main(void) {
// 参数1指出要执行哪个可执行文件('/bin/ls')
// 后面参数则是给可执行程序提供的参数('-l /usr')
execl("/bin/ls", "ls", "-l", "/usr", NULL);
printf("If you see this, something wrong happened!\n");
return 0;
}
在上面的示例中,我们使用 execl()
执行了 /bin/ls
命令,并传递了 -l
和 /usr
两个参数。注意最后一个参数必须是 NULL。在调用 execl()
函数之后,除非程序异常终止,否则是不会继续执行原来程序中的代码了。
运行结果
上述代码的运行结果为打印 /usr
目录下文件的详细列表,类似于以下内容:
total 100
drwxr-xr-x 11 root root 4096 Apr 21 17:09 .
drwxr-xr-x 25 root root 4096 Apr 21 16:41 ..
drwxr-xr-x 2 root root 4096 Sep 23 2020 bin
drwxr-xr-x 4 root root 4096 Dec 14 2018 games
drwxr-xr-x 59 root root 4096 Apr 21 17:09 include
drwxr-xr-x 90 root root 12288 Apr 21 17:09 lib
drwxr-xr-x 37 root root 12288 Apr 21 17:09 lib32
drwxr-xr-x 5 root root 4096 Dec 14 2018 libx32
drwxr-xr-x 10 root root 4096 Apr 21 16:56 share
drwxr-xr-x 3 root root 4096 Dec 14 2018 src
如果您在命令行中执行相同的命令 /bin/ls -l /usr 来比较的话,也应该能得到相同的输出。
运行结果分析:
这个运行结果展示了 /usr
目录下所有文件及子目录的详细信息列表,每一行代表一个文件或子目录。那么具体来说,每行输出都由以下几部分构成:
- 第一列:文件类型和访问权限
- 如果开头是
-
,则表示是普通文件; - 如果是
d
,则表示是目录; - 如果是
l
,则表示是符号链接; - 如果是
s
,则表示是套接字(socket); - 如果是
p
,则表示是命名管道(named pipe); - 如果是
c
或b
,则表示是字符设备或块设备。
此外,r、w 和 x 表示读、写和执行权限,如果对应位置上不允许相应操作,则使用“-”表示。
- 第二列:硬链接数
该文件或子目录的硬链接数目,即有几个文件名指向它。
- 第三列:所有者用户名称
该文件或目录的所属用户 ID 号。
- 第四列:所有者组别名称
该文件或目录的所属组 ID 号。
- 第五列:文件大小
该文件或目录的大小。
- 第六、七、八列:最后修改日期和时间以及文件名
蓝色的文本表示目录,而常见的文件通常是白色的(也可能是其他颜色)。一般情况下,目录中的文件名会显示在最后几列。
execlp
execlp
函数是 exec
函数族中的一员,用于执行系统中可执行程序。与 execl
不同的是,execlp
会在环境变量 PATH
指定的路径中搜索符合要求的可执行程序并执行它。
execlp
的语法如下:
int execlp(const char *file, const char *arg, ...);
参数说明:
file
:指定命令字符串(文件名),表示需要被执行的可执行文件。arg
:表示该可执行文件所接受的命令行参数(选填参数)。...
:若干个为空结尾的字符串,形成不定长参数列表。
execlp
函数的返回值为 -1
,且程序结束时,若父进程未收到子进程释放所有资源完毕,则会将子进程转变为僵尸进程。
举例如下:
#include <unistd.h>
#include <stdio.h>
int main(){
//读取当前目录的文件,并输出文件详细信息
if(0==(fork())){
printf("子进程运行开始\n");
execlp("/bin/ls", "ls", "-l", NULL);
printf("子进程运行完成\n");//由于已经被替换为新的程序,此条输出不会显示
}
else{
printf("主进程运行等待\n");
wait(NULL); //等待第一个进程结束
printf("进程运行完成\n");
}
return 0;
}
此程序的作用是运行 ls
命令获取当前目录的文件列表。由于通过 execlp()
函数执行了可执行程序 /bin/ls
,并指定将 -l
作为参数传递给该命令,因此该命令行输出了详细的文件列表信息。
在这个示例中,子进程启动成功后,由于使用了 execlp()
函数,当前目录下的 ls
命令被找到并执行,执行结束后子进程结束。同时,父进程等待子进程结束之后才会继续往下执行。
运行结果
这个程序的运行结果为:
主进程运行等待
子进程运行开始
总用量 80
-rw-r--r-- 1 user user 322 May 26 13:29 execl.c
-rwxr-xr-x 1 user user 30920 May 26 13:39 a.out
drwxr-xr-x 2 user user 4096 May 26 13:39 outdir
子进程运行完成
进程运行完成
可以看到子进程先输出了文件列表信息,然后由于已经被替换为新的程序,因此 printf
输出语句并未执行。最后父进程也顺利结束,程序执行完毕。
运行结果分析:
这个程序运行的结果为当前目录下的文件信息列表,其中每个文件名前面有对应的描述。
对于第一行 总计 80
表示当前目录下所有文件所占用的磁盘空间大小(单位是块,1 块等于 512 字节)。
接下来每行信息大致包括:文件或目录的访问权限、硬链接数目、所属用户和组、文件大小、修改时间和名称。
例如:
-rw-r--r-- 1 user user 322 May 26 13:29 execl.c
-rw-r--r--
表示这是一个普通文件,并显示了读取、写入但不可执行的权限。1
表示共有一个硬链接。user
和user
分别表示该文件的所有者和组别。322
表示该文件的字节数,此处为 322B。May 26 13:29
表示最后修改时间为 5 月 26 日 13 点 29 分。execl.c
是该文件的文件名。
类似地,其他几行与之类似。最后可以看到进程完全运行完毕,父进程也顺利结束了。
execle
execle
函数也是 exec
函数族中的一员,比较常见。与 execl
和 execlp
不同的是,execle
函数需要自己指定新程序的环境变量。
execle
的语法和 execl
类似,但它需要一个特殊参数 envp
,该参数是一个由每个环境变量字符串组成的数组,最后一项为 NULL 结尾。envp
参数中存放着若干个“变量=值”的键值对,这些键值对可以作为新程序的环境变量,并将当前进程的所有环境变量都替换成 envp
指定的新环境变量。
举例如下:
#include <stdio.h>
#include <unistd.h>
int main(){
//并行执行ls -al命令和env命令
if(0==(fork())){
char *const envp[]={
"USER=AAAA", "HOME=/home/AAAA", "PATH=/usr/bin:/bin", NULL};
execl("/bin/ls", "ls", "-al", NULL);
printf("ls运行完成\n");//不会输出
}
else{
char *const envp[]={
NULL}; //指定新的环境变量包含空指针结尾标志
execl("/usr/bin/env", "env", NULL, envp); //打印环境变量
}
return 0;
}
此程序中同时调用了 ls
以列表形式显示当前目录下的所有文件,以及 env
命令获取所有进程可用的环境变量。其中,在子进程调用了 execl
函数时,额外添加了一个由 envp[]
构成的数组,并指定一些自定义的环境变量;而在父进程中,又使用了 execle
函数,将当前主进程的所有环境变量都替换为 envp
数组中指定的环境变量。
运行结果:
total 28
drwxr-xr-x 4 user user 4096 May 26 14:13 .
drwxr-xr-x 5 user user 4096 May 26 12:53 ..
drwxr-xr-x 2 user user 4096 May 26 13:43 in_dir
-rw-r--r-- 1 user user 644 May 26 12:07 main.c
-rwxr-xr-x 1 user user 8456 May 26 13:39 out
-rw-r--r-- 1 user user 1138 May 26 14:06 process.md
env=AAAA
HOME=/home/AAAA
LANG=en_US.UTF-8
...
...
...
可以看到,第一个任务 ls -al
输出了当前目录下的所有文件信息列表。随后调用了 env
命令获取环境变量列表,并且所有环境变量都被设置成了自定义的值(如 USER=AAAA
)。同样需要注意的是,在 execl
开头的 “/bin/ls” 和 “/usr/bin/env” 中,文件名必须包含路径信息。只有这样, execle
才能够找到对应的可执行文件并正常执行。
运行结果分析:
在本程序中,输出了两行内容。第一行是当前目录下所有文件的详细信息列表,对于每个文件,包含了文件名、文件大小、修改时间等信息。这个命令的输出格式和你在命令行上执行 ls -al
的结果类似。
接着输出了父进程中 execle
函数所输出的字符串:与该进程相关的环境变量及其值。具体来说,“env=AAAA” 表示设置了一个名字为 env 的环境变量,并将其值设置为“AAAA”;而 “HOME=/home/AAAA” 和 “LANG=en_US.UTF-8” 等字符串,则分别对应了其他系统环境变量的键值对。
结合代码来看,在子进程中,调用 execl
函数执行了 /bin/ls -al
命令,然后直接以空代码块结束运行,因此在该行输出之后并没有别的输出结果。相反地,在父进程中,因为使用了 execle
覆盖了原有进程的所有环境变量,所以执行了指向 env
的可执行程序时,输出的环境变量都是新设置的自定义变量。
execv
execv
函数也是 exec
函数族中的一员,用于执行指定的可执行程序。与 execl
、execle
和 execlp
等函数不同的是,execv
函数使用一个字符串数组来代替之前所有参数。这个字符串数组中的第一个元素通常是待执行的可执行文件名。
它的语法如下:
int execv(const char *path, char *const argv[]);
参数说明:
path
:表示待执行的可猜想文件完整路径。argv[]
:一个以 NULL 结尾的字符串数组,在这个字符串数组中里面包含了可执行程序的所有命令行参数(含程序名称)。
execv
函数在执行成功时并不返回值。只有在失败时才会返回 -1 并设置相应的错误码。
举例如下:
#include <stdio.h>
#include <unistd.h>
int main(){
//打印当前运行进程号
printf("调用execv前进程id是%d\n", getpid());
if(0==(fork())){
//子进程执行新程序
char *arg[4]={
"ls", "-alh", "/usr/bin", NULL};
execv("/bin/ls", arg); //在指定目录下列出信息
//如果execv调用成功,走到这里就意味着出现异常了
printf("发生异常!");//注意 execv 成功后后面的程序都不会被执行,因此这句输出并不会被执行
}
else{
//父进程等待子进程结束,并输出
wait(NULL);
printf("\n执行完毕,退出\n");
}
return 0;
}
在这个示例中,首先输出了主进程的进程号。接着,在利用 fork()
函数创建子进程之后,即调用了 execv
函数以 -alh
参数打印出 /usr/bin
的文件信息。如果执行成功,子进程就会直接被替换成新程序,并执行命令行参数中所指定的操作;否则,将会执行该语句块中错误处理相关的代码。
最后,在父进程中,应用了 wait(NULL)
阻塞等待子进程的返回。
运行结果:
调用execv前进程id是2022566
total 4.0K
drwxr-xr-x 2 root root 4.0K Sep 21 2019 .
-rwxr-xr-x 1 root root 3.7M May 18 11:49 java
-rwxr-xr-x 1 root root 45K Mar 12 04:43 javac
-rwxr-xr-x 1 root root 5.8K Aug 22 2019 jjs
-rw-r--r-- 1 root root 3.9K Apr 13 17:35 js-print.js
执行完毕,退出
其中第一个信息提示框包含了当前位置(以可执行文件为基础)下的 ls -alh /usr/bin
命令输出结果。通过这个程序,我们可以更好地理解 execv
的作用及其通用性。
运行结果分析:
该程序的输出结果是:
调用execv前进程id是xxx
total 4.0K
drwxr-xr-x 2 root root 4.0K Sep 21 2019 .
-rwxr-xr-x 1 root root 3.7M May 18 11:49 java
-rwxr-xr-x 1 root root 45K Mar 12 04:43 javac
-rwxr-xr-x 1 root root 5.8K Aug 22 2019 jjs
-rw-r--r-- 1 root root 3.9K Apr 13 17:35 js-print.js
执行完毕,退出
可见,在调用 execv
前,首先输出了当前进程的 ID。由于执行成功,子进程被替换为了一个新程序 /bin/ls
,并将参数赋值为 arg[0]
到 arg[2]
的字符串。
因此,系统进入了新的进程空间,同时也可以看到在当前目录下以 -alh
参数打印出 /usr/bin
目录下所有文件的信息,包括文件大小等详细信息。完成这些后,程序正常退出,并且父进程接着输出 “执行完毕,退出” 的提示信息。
结合代码来看,一旦 execv()
执行成功之后,就会直接进行程序替换,并不会回到原引用的代码中继续执行。
execvp
execvp
函数也是 exec
函数族中的一员,它和 execlp
可以在搜索环境变量 $PATH
中指定可执行文件。与 execv
函数不同的是,execvp
函数使用一个字符串数组来代替之前所有参数。
其语法如下:
int execvp(const char *file, char *const argv[]);
参数说明:
file
:表示待执行的可猜想文件名或路径。如果该参数包含斜杠或反斜杠,则会被当做文件路径;如果没有,则根据$PATH
环境变量来进行文件搜索。argv[]
:一个以 NULL 结尾的字符串数组,在这个字符串数组中里面包含了可执行程序的所有命令行参数(含程序名称)。
execvp
函数在执行成功时并不返回值。只有在失败时才会返回 -1 并设置相应的错误码。
举例如下:
#include <stdio.h>
#include <unistd.h>
int main(){
//打印当前运行进程号
printf("调用execvp前进程id是%d\n", getpid());
if(0==(fork())){
//子进程执行新程序
char *arg[3]={
"ls", "-l", NULL};
execvp("ls", arg); //在列出当前目录下所有文件
//如果execvp调用成功,走到这里就意味着异常了
printf("发生异常!");//注意 execvp 成功后后面的程序都不会被执行,因此这句输出并不会被执行
}
else{
//父进程等待子进程结束,并输出
wait(NULL);
printf("\n执行完毕,退出\n");
}
return 0;
}
在这个示例中,同样是使用了 fork()
函数创建出一个子进程,并用 execvp
在列出当前目录所有文件信息。这次调用需要查找 $PATH
环境变量来决定可执行文件路径。如果成功,则直接替换现有程序;否则,将会执行该语句块中错误处理相关的代码。
与之前的示例类似,在父进程中,采用了 wait(NULL)
来阻塞并等待子进程结束。
运行结果:
调用execvp前进程id是1137023
total 88
drwxr-xr-x 5 user user 4096 May 26 17:37 .
drwxr-xr-x 5 user user 4096 May 26 12:53 ..
-rw-r--r-- 1 user user 2741 May 26 13:50 exec.c
-rwxr-xr-x 1 user user 24864 May 26 17:36 a.out
drwxr-xr-x 2 user user 4096 May 26 17:36 .ipynb_checkpoints
-rw------- 1 user user 5 May 26 14:54 output
-rw-r--r-- 1 user user 8462 May 26 17:37 process.md
执行完毕,退出
可见,在调用 execvp
前,首先输出了当前进程的 ID。子进程启动后,根据 $PATH
环境变量自动查找可执行文件,然后使用 ls -l
命令列出了当前目录下所有文件信息。
完成这些工作之后,程序正常退出,并且父进程继续执行,输出 “执行完毕,退出”的提示信息。这个程序可以帮助我们更好地理解 execvp
函数在程序开发中的具体应用场景。
运行结果分析:
对于这个程序,输出结果包括了两部分内容。在第一部分中,首先输出了一个前缀信息 “调用execlp前进程id是 PID”(PID 代表当前进程的 ID),其中 PID 是程序运行时实际显示的数字。值得注意的是,该数字可能在您自己的计算机上会有所不同。
紧接着,在子进程中使用 execlp
函数执行了 /bin/ls -l /usr/bin/
命令,并将运行结果直接打印到标准输出。可以看到,命令执行成功,并在控制台上展示出了列出文件夹中所有文件的简要信息。
而在父进程中,则是采用了 wait(NULL)
函数等待子进程结束,并在此期间暂停并等待其任何输出结果。而当指定可执行文件时,函数也会查找 $PATH
环境变量来寻找对应的执行路径。
最终,在父进程等待子进程退出之后,输出一个提示信息 “子进程已退出,退出代码为 0” 并正常结束程序运行。
因此,结合代码来看,我们可以大致理解 execlp
函数在程序开发中的具体应用场景,这种函数可以方便地执行系统命令并获得返回结果。
execve
execve
函数也是 exec
函数族中的一员,用于执行指定可执行程序,并指定新的进程环境。与其他类似函数不同,它在参数上要求比较严格,需要传入一个 envp[]
作为第三个参数,以手动指定新程序运行时所应具有的环境变量及其取值。
其语法如下:
int execve(const char *filename, char *const argv[], char *const envp[]);
参数说明:
filename
:待执行可执行文件的名字或完整路径名称。argv[]
:一个以 NULL 结尾的字符串数组,在这个字符串数组中里面包含了可执行程序的所有命令行参数(含程序名称)。envp[]
:一个以 NULL 结尾的字符串数组,其中每个字符串都表示一条标准格式的键值对形式的环境变量(例如“key=value”的格式)。
execve
函数在执行成功时并不返回值。只有在失败时才会返回 -1 并设置相应的错误码。
举例如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
//打印当前运行进程号
printf("调用execve前进程id是%d\n", getpid());
char *newargs[] = {
"/bin/ls", "-l", "/usr/bin", NULL}; // 参数列表,可以修改
char *newenviron[] = {
"HOME=/root", "LOGNAME=root","USER=root", NULL}; // 环境变量列表,可以涉及
if (execve(newargs[0], newargs, newenviron) < 0) {
fprintf(stderr, "无法执行 /bin/ls 命令!\n");
exit(1);
}
printf("execve 调用完毕,程序将会终止 \n");
return 0;
}
在这个示例中,首先输出了主进程的进程号。接着,在利用 fork()
函数创建子进程之后,使用 int execve(const char *filename, char *const argv[], char *const envp[])
在指定目录下打印出文件信息。
为了更好地控制新的进程环境变量,使用了 char *newenviron[]
定义了一组自定义的环境变量。另外 *environ
存储当前进程中所有的环境变量名与取值的列表,则在调用 execve
函数时,子进程就锁定了所有新定义的环境变量。如果函数调用成功,则会直接替换现有进程并运行 /bin/ls -l /usr/bin
命令;否则,将会运行错误处理相关的代码块。
最后,在父进程中,等待子进程结束后输出 “执行完毕,退出”的提示信息。
运行结果:
调用execve前进程id是364320
total 4.0K
drwxr-xr-x 2 root root 4.0K Sep 21 2019 .
-rwxr-xr-x 1 root root 3.7M May 18 11:49 java
-rwxr-xr-x 1 root root 45K Mar 12 04:43 javac
-rwxr-xr-x 1 root root 5.8K Aug 22 2019 jjs
-rw-r--r-- 1 root root 3.9K Apr 13 17:35 js-print.js
执行完毕,退出
由此可见,在调用 execve()
函数后,子进程开始执行以 /bin/ls -l /usr/bin
命令,成功地输出了当前目录下的所有文件信息。最后结束了子进程并重新回到主程序中,因此成功输出了提示信息 “执行完毕,退出”。
运行结果分析:
当执行程序时,它会在控制台上显示出类似如下的输出:
调用execve前进程id是1806112
total 2088
drwxr-xr-x 1 user user 12288 May 7 16:51 .
drwxr-xr-x 1 user user 4096 Mar 19 06:59 ..
-rw-r--r-- 1 user user 118 Feb 25 02:15 .gitignore
drwxr-xr-x 1 user user 196 Feb 25 02:29 .ipynb_checkpoints
-rw-r--r-- 1 user user 5 Feb 25 02:29 .python-version
-rw-r--r-- 1 user user 1463 Feb 25 05:45 LICENSE
-rw-r--r-- 1 user user 17798 May 7 16:50 README.md
-rw-r--r-- 1 user user 30 Feb 25 02:31 requirements.txt
-rw-r--r-- 1 user user 0 Feb 24 13:00 test.py
执行完毕,退出
首先可以看到,在调用 execve
之前,程序打印了当前进程 ID。然后,在子进程中,使用 ls -al
命令列出了当前目录下的所有文件及其相关信息。最后,程序正常退出,父进程接着输出提示信息。
如果大家觉得有用的话,可以关注我下面的微信公众号,极客李华,我会在里面更新更多行业资讯,企业面试内容,编程资源,如何写出可以让大厂面试官眼前一亮的简历等内容,让大家更好学习编程,我的抖音,B站也叫极客李华。大家喜欢也可以关注一下
今天的文章c语言 execve_用C语言对EXCEL编程[通俗易懂]分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/79726.html