exec族函数的作用:
在使用fork函数创建新的进程后,我们经常会在新进程中调用exec函数取执行另一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的id并没有改变。
功能:
在调用进程内部执行一个可执行文件。可执行文件可以是二进制文件,也可以是任何linux下可执行的脚本文件。
函数族:
exec函数族分别是:execl、execlp、execle、execv、execvp、execvpe
函数原型:
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 execvpe(const char *file, char *const argv[],char *const envp[]);
返回值:
exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从源程序的调用点接着往下执行。
参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件的名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按PATH环境变量,在它所指定的各目录中搜索可执行文件。
execl
接下去我们开始编写代码进行execl的使用,首先我们创建一个echoarg.c用于打印参数:
//echoarg.c
#include <stdio.h>
int main(int argc,char *argv[]){
int i=0;
for(i=0;i<argc;i++){
printf("argv[%d]:%s\n",i,argv[i]);
}
return 0;
}
编译为可执行程序echoarg:gcc echoarg.c -o echoarg;
之后我们在编写一个程序调用execl:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("before execl\n");
//int execl(const char *path, const char *arg, ...);
if(execl("./bin/echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed\n");
}
printf("after execl\n");
return 0;
}
如果我们成功调用execl函数就不会执行printf(“before execl\n”);之后的代码,转而执行新的可执行文件,让我们运行看看,运行结果:
before execl
execl failed
after execl
可以发现三个打印函数都被执行了,说明execl被没有调用成功,但是我们不知道原因是什么,此时需要调用一个perror来返回错误信息:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("before execl\n");
//int execl(const char *path, const char *arg, ...);
if(execl("./bin/echoarg","echoarg","abc",NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行后结果:
before execl
execl failed
why: No such file or directory
after execl
错误信息显示,在我们指定的路径下并没有这个文件,那么我们就可以知道是文件路径指定错误,此时echoarg可执行文件就在我们的当前路径下而不是bin文件夹下,因此需要修改路径:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("before execl\n");
//int execl(const char *path, const char *arg, ...);
if(execl("./echoarg","echoarg","abc",NULL) == -1) //参数列表的结尾以NULL结束
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:
before execl
argv[0]:echoarg
argv[1]:abc
此时execl调用成功,调用execl后并不会执行后面的代码转而去执行我们的echoarg可执行文件。
小玩法,如何用C语言调取当前的时间
根据上述代码,我们可以调用execl执行一个新的可执行文件,那么其实像ls,date之类的指令在linux中也是一个可执行文件,首先我们通过whereis 指令 来获取指令在系统中的位置,之后将该路径写入上述代码即可完成我们需要的功能,比如调取当前的时间:
首先找到date的路径:
whereis date
date: /bin/date /usr/share/man/man1/date.1.gz
修改路径:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("before execl\n");
//int execl(const char *path, const char *arg, ...);
if(execl("/bin/date","date",NULL) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:
before execl
Fri May 10 16:06:28 CST 2024
成功获取当前时间
execlp
上述代码中,我们如果想使用系统中的某些参数比如ls,date,ps我们都需要首先通过whereis 来查找路径位置比较麻烦,因此我们可以考虑使用execlp来进行调用,它能通过环境变量PATH查找到我们需要的可执行文件。系统会先从当前文件去找,如果找不到就去环境变量找:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("before execl\n");
//int execl(const char *path, const char *arg, ...);
if(execlp("date","date",NULL) == -1) //无需指定路径,只需要写我们需要的,它自己会去找到路径
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:
./a.out
before execl
Fri May 10 16:27:31 CST 2024
如何通过环境变量去寻找我们需要的文件
首先我们可以通过echo
P
A
T
H
去查看我们的环境变量:
/
u
s
r
/
l
o
c
a
l
/
s
b
i
n
:
/
u
s
r
/
l
o
c
a
l
/
b
i
n
:
/
u
s
r
/
s
b
i
n
:
/
u
s
r
/
b
i
n
:
/
s
b
i
n
:
/
b
i
n
:
/
u
s
r
/
g
a
m
e
s
:
/
o
p
t
/
F
r
i
e
n
d
l
y
A
R
M
/
t
o
o
l
s
c
h
a
i
n
/
4.5.1
/
b
i
n
因此每当我们需要使用的可执行文件在这些路径下,我们都可以不用管当前处于什么路径直接使用,比如
l
s
存在于
b
i
n
中而环境变量中有
b
i
n
这个目录,因此我们在任何路径下都可以使用
l
s
来查看当前文件。但是像我们之前编译的
e
c
h
o
a
r
g
函数只能在我们当时编译的那个路径下才可以使用,如何让
e
c
h
o
a
r
g
也像
l
s
一样可以在任何情况下都调用呢?答案是添加环境变量:
1.
首先通过
p
w
d
指令查看我们的当前路径
/
h
o
m
e
/
C
L
C
/
p
r
o
c
e
s
s
2.
将当前的路径添加到环境变量
e
x
p
o
r
t
P
A
T
H
=
PATH去查看我们的环境变量: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/FriendlyARM/toolschain/4.5.1/bin 因此每当我们需要使用的可执行文件在这些路径下,我们都可以不用管当前处于什么路径直接使用,比如ls存在于bin中而环境变量中有bin这个目录,因此我们在任何路径下都可以使用ls来查看当前文件。 但是像我们之前编译的echoarg函数只能在我们当时编译的那个路径下才可以使用,如何让echoarg也像ls一样可以在任何情况下都调用呢? 答案是添加环境变量: 1.首先通过pwd指令查看我们的当前路径 /home/CLC/process 2.将当前的路径添加到环境变量 export PATH=
PATH去查看我们的环境变量:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/FriendlyARM/toolschain/4.5.1/bin因此每当我们需要使用的可执行文件在这些路径下,我们都可以不用管当前处于什么路径直接使用,比如ls存在于bin中而环境变量中有bin这个目录,因此我们在任何路径下都可以使用ls来查看当前文件。但是像我们之前编译的echoarg函数只能在我们当时编译的那个路径下才可以使用,如何让echoarg也像ls一样可以在任何情况下都调用呢?答案是添加环境变量:1.首先通过pwd指令查看我们的当前路径/home/CLC/process2.将当前的路径添加到环境变量exportPATH=PATH:/home/CLC/process
此时再查看环境变量我们的路径已经被加入
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/opt/FriendlyARM/toolschain/4.5.1/bin:/home/CLC/process
这是我们就可以在任何路径下使用我们process目录下的可执行文件,也不需要加./:
CLC@Embed_Learn:~$ echoarg 11 22 33
argv[0]:echoarg
argv[1]:11
argv[2]:22
argv[3]:33
execvp
其实就是将重新写一个数组,将参数放到数组里,之后调用execvp里面只需要加入我们访问的可执行文件和argv数组即可。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void){
printf("before execl\n");
//int execl(const char *path, const char *arg, ...);
char *argv[]={"date",NULL};
if(execvp("date",argv) == -1)
{
printf("execl failed\n");
perror("why");
}
printf("after execl\n");
return 0;
}
运行结果:
./a.out
before execl
Fri May 10 16:30:46 CST 2024
exec族函数配合fork使用
实现功能:当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉。
具体实现流程跟之前博文中修改配置文件操作类似,代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
//pid_t getpid(void);
pid_t pid;
int data;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
int fdSrc;
char *readBuf=NULL;
fdSrc = open("config.txt",O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET); //将光标恢复到头
readBuf = (char *)malloc(sizeof(char)*size+8);
int n_read = read(fdSrc,readBuf,size);
char *p = strstr(readBuf,"LENG=");
if(p == NULL){
printf("not found\n");
exit(-1);
}else{
printf("change success\n");
}
p=p+strlen("LENG=");
*p = '5';
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readBuf,strlen(readBuf));
close(fdSrc);
exit(0);
}
}else{
printf("wait,no request\n");
}
}
return 0;
}
代码功能:当我们输入一个非1的数时会提醒wait,no request,当我们输入1时,会将config.txt中LENG的值修改为5。
之后我们可以结合今天学的execl的方式,将我们的代码简化:
1.首先编译上述代码为一个可执行程序
gcc demo20.c -o myChange
//demo20.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
int fdSrc;
char *readBuf=NULL;
fdSrc = open("config.txt",O_RDWR);
int size = lseek(fdSrc,0,SEEK_END);
lseek(fdSrc,0,SEEK_SET); //将光标恢复到头
readBuf = (char *)malloc(sizeof(char)*size+8);
int n_read = read(fdSrc,readBuf,size);
char *p = strstr(readBuf,"LENG=");
if(p == NULL){
printf("not found\n");
exit(-1);
}else{
printf("find success\n");
}
p=p+strlen("LENG=");
*p = '5';
lseek(fdSrc,0,SEEK_SET);
int n_write = write(fdSrc,readBuf,strlen(readBuf));
printf("change success!\n");
close(fdSrc);
exit(0);
return 0;
}
2.拷贝一个新文件demo18.c
将判断子进程之后的代码全部删除,通过加入execl(“./dataChange”,“dataChange”,NULL)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
//pid_t getpid(void);
pid_t pid;
int data;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data == 1){
pid = fork();
if(pid > 0){
wait(NULL);
}
if(pid == 0){
execl("./myChange","myChange",NULL);
}
}else{
printf("wait,no request\n");
}
}
return 0;
}
成功修改config.txt中的内容