Linux进程——exec族函数、exec族函数与fork函数的配合

exec族函数解析

作用

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

功能

在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

函数族

exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

函数原型

cs 复制代码
#include <unistd.h>
extern char **environ;

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环境变量,在它所指定的各目录中搜寻可执行文件 |

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:

|-------|----------------------------------------|
| l | 使用参数列表 |
| p | 使用文件名,并从PATH环境进行寻找可执行文件 |
| v | 先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数 |
| e | 多了envp\[\]数组,使用新的环境变量代替调用进程的环境变量 |

将exac函数归为带l、带p、带v三类来说明参数特点

一、带l的一类exac函数(l表示list),包括execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。
1.新建一个echoarg并使用execl函数调用
cs 复制代码
//文件17.c
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("before execl\n");
	if(execl("./echoarg","echoarg","hello","word",NULL) == -1)
	{
		printf("open execl failed\n");//调用execl失败会返回-1,则会执行if里的代码
		perror("why");
	}
	printf("after execl\n");
	return 0;
}
cs 复制代码
//文件echoarg.c
#include <stdio.h>

int main(int agrc,char *argv[])
{
	int i;
	for(i = 0;i < agrc;i++)
	{
		printf("argv[%d]:%s\n",i,argv[i]);
	}
	return 0;
}

先用gcc编译echoarg.c,生成可执行文件echoarg并放在当前路径下。文件echoarg的作用是打印命令行参数。然后再调用execl,用execl 找到并执行echoarg,将当前进程main替换成进程echoarg,就会执行ehcoarg里面的代码,所以"after execl" 没有在终端被打印出来。

路径对应不上的情况:

cs 复制代码
//文件16.c
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("before execl\n");
	if(execl("/bin/echoarg","echoarg","hello","word",NULL) == -1)
	{
		printf("open execl failed\n");
		perror("why");//perror函数会打印错误原因,输入内容后结尾会自动加冒号(:)并换行
	}
	printf("after execl\n");
	return 0;
}

可见echoarg存在于当前路径并不存在于bin路径下,所以调用execl函数无法找到echoarg并执行echoarg,调用失败返回值为-1,则会继续执行if函数里的代码。

注:当前文件格式是./+可执行文件名字,其他路径为 /其他路径/可执行文件名字

2.使用execl函数调用ls指令
cs 复制代码
//文件18.c
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("before execl\n");
	if(execl("/bin/ls","ls",NULL) == -1)//不需要参数则找到并使用ls后以NULL结尾
	{
		printf("open execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}
cs 复制代码
whereis ls//查看ls的路径

可见打印完"before execl"后使用execl函数使用execl函数找到并调用date可执行文件,编译运行代码会直接显示出当前所有文件,实现功能调用。不会执行if中的代码。

3.使用execl函数调用date指令
cs 复制代码
//文件19.c
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("we can use execl to know the time\n");//不需要参数则找到并使用ls后以NULL结尾
	if(execl("/bin/date","date",NULL) == -1)
	{
		printf("open execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}
cs 复制代码
date//查看系统时间

可见一开始直接用date指令可以看到系统时间,在上述代码中通过使用execl函数找到并调用date可执行文件,编译运行代码会直接显示出系统时间,实现功能调用。

二、带p的一类exac函数,包括execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
1.使用execlp函数调用date指令
cs 复制代码
//文件20.c
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("we can use execl to know the time\n");
	if(execlp("date","date",NULL) == -1)
	{
		printf("open execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}

上述代码如果调用execl函数则会调用失败,原因是没有输入可执行文件的正确路径。

而在file参数位置直接输入可执行文件名字,调用execlp函数能通过环境变量PATH查找到可执行文件date并执行。

2.环境变量PATH解读

如果使用的可执行文件在PATH里,则使用execlp函数可以不用详细描述绝对路径。

打印环境变量

cs 复制代码
echo $PATH

修改环境变量

cs 复制代码
export PATH=$PATH:/home/CLC/Linux1
//通过pwd查看执行文件的路径并将其添加进环境变量PATH路径

修改完毕后结果是:运行/hone/CLC/Linux1下的可执行文件不需要加./便可直接运行

在当前Linux1文件下运行可执行文件不需要加./,使用cd回到目录文件夹也是输出结果一样

三、带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。

如char *arg\[\]这种形式,且arg最后一个元素必须是NULL,例如char *arg\[\] = {"ls",NULL};

1.execvp

cs 复制代码
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("we can use execl to know the time\n");
	char *agrv[] = {"ls",NULL};//定义一个指针数组
	if(execvp("ls",agrv) == -1)//将指针数组的地址作为参数
	{
		printf("open execl failed\n");
		perror("why");
	}
	printf("after execl\n");
	return 0;
}

定义一个指针数组并将其地址作为execvp函数后面的参数,同时要以NULL结尾,便可以找到并执行ls可执行文件。同时file参数位置可直接输入可执行文件名字,不需要加路径。

2.execv

cs 复制代码
#include <unistd.h>
#include <stdio.h>

int main()
{
	printf("before execv\n");
	char *agrv[] = {"date",NULL};
	if(execv("/bin/date",agrv) == -1)
	{
		printf("open execl failed\n");
		perror("why");
	}
	printf("after execvp\n");
	return 0;
}

跟execvp一样需要定义一个指针数组并将其地址作为参数部分,但需要加上可执行文件路径名字,date在bin下,所以需要加上/bin/才能正确使用execv函数找到并执行date可执行文件。

exec族函数与fork函数的配合

流程图如下:

当父进程检测到输入为1的时候,创建子进程把配置文件的字段值修改掉。

代码如下:

cs 复制代码
#include<stdio.h>
#include <unistd.h>
int main()
{
        int a= 0;
        int fork_r=0;
        while(1)
        {
                printf("please input a num\n");
                scanf("%d",&a);
                if(a==1)//输入为1,创建子进程
		        {
                        fork_r=fork();
                        if(fork_r==0)//返回值为两次,等于0为子进程
			            {
               			    execl("./changeData","changeData",NULL);
                            //找到并执行changeData
                        }
                }
                else
		        {
                        printf("no change success\n");
                }
        }
        return 0;
}

上述代码调用execl函数找到并执行的changeData函数如下

cs 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc,char **argv)
{
	int fdSrc;

	char *readBuf = NULL;

	fdSrc = open("config",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);
	}
	p = p+strlen("leng=");
	*p = '9';
	lseek(fdSrc,0,SEEK_SET);
	int n_write = write(fdSrc,readBuf,strlen(readBuf));

	close(fdSrc);

	return 0;
}

编译格式是:

cs 复制代码
gcc changeData.c -o changeData//-o后面的可执行文件才能被execl调用

原配置文件config的值如下:

cs 复制代码
peed=5
leng=1
SCORE=90
LEVEL=95

只有当输入的值为1时,才会创建子进程并使用execl函数找到并执行changeData可执行文件,从而将原配置文件config中"leng=1"改成"leng=9"。

相关推荐
☆凡尘清心☆24 分钟前
CentOS Stream 9 源码编译搭建 Zabbix7.0.29 完整部署方案(部署成功)
linux·运维·centos
Escalating_xu27 分钟前
【Linux】进程间通信:从匿名管道、FIFO 到共享内存、消息队列与信号量
java·linux·面试
予昊36 分钟前
Linux 资源控制实战分析:用 LXC 亲手造一个 “迷你虚拟机“
linux·运维·服务器
allforgood44 分钟前
管理网络安全
linux
灵晔君1 小时前
【Linux】进程控制(一)——进程创建、进程终止与进程等待
linux·c语言·算法
wsad05321 小时前
CentOS 10 上使用 Docker Compose 部署 Discuz X5.0 论坛完整教程
linux·docker·centos·discuz
张宇Joaquin1 小时前
openEuler编译和更换内核的方法
java·linux·服务器
小此方1 小时前
Re:Linux系统篇(五十六)线程篇 · 九:基于阻塞队列的生产者消费者模型与条件变量
linux·运维·服务器
迷途之人不知返1 小时前
自动化工程的构建
linux·自动化
Escalating_xu2 小时前
【Linux】库制作与链接原理:从静态库、动态库到 ELF、重定位、GOT 与 PLT
linux·运维·服务器