Linux系统编程之进程(exec族函数)

一、exec()族函数说明
二、exec族函数(execl, execlp, execle, execv, execvp, execvpe)
1、带 L的一类exac函数(execl、execlp、execle)
2、带 P的一类exec函数(execlp、execvp、execvpe)
3、带 V不带 L的一类exec函数(execv、execvp、execve)
4、带 E的一类exec函数(execle、execvpe)
三、exec配合fork使用

一、exec()族函数说明

exec()函数是一组函数族

在C语言中,exec()函数是一组函数族,用于执行其他程序或者替换当前进程的映像(image)的内容。这些函数允许你在C程序中执行外部程序,通常是可执行文件。

以下是C语言中的exec函数族的主要成员:

  1. int execl(const char *path, const char arg0, … / (char *)0 */);

execl函数用于执行一个新的程序,并且你需要为它提供可执行文件的路径以及一系列的参数,以及一个以空指针结尾的参数列表。通常,arg0是程序的名称,后面的参数是传递给新程序的命令行参数。

  1. int execv(const char *path, char *const argv[]);

execv函数也用于执行一个新的程序,但它接受参数作为一个字符串数组。argv是一个以空指针结尾的字符串数组,包含了程序的名称和参数。

  1. int execlp(const char *file, const char arg0, … / (char *)0 */);

execlp函数与execl类似,但是它会在系统的路径变量中查找可执行文件,不需要提供完整的路径。

  1. int execvp(const char *file, char *const argv[]);

execvp函数类似于execv,但它也会在系统的路径变量中查找可执行文件,不需要提供完整的路径。

  • 这些exec函数在执行成功时不会返回,因为它们会替换当前进程的映像。如果执行失败,它们将返回-1,并设置errno以指示错误的类型。
  • 这些函数通常与fork()函数一起使用,用于创建新的子进程,然后在子进程中使用exec函数执行其他程序。这是一种常见的模式,用于在C语言中执行其他程序或切换到不同的程序上下文。

exec族函数函数的作用:

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

exec族函数定义:

  1. 功能:
      在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
  2. 函数族:
      exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
  3. 函数原型:
SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *pathname, const char *arg, ... /* (char  *) NULL */);
                     
       int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
                      
       int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
                      
       int execv(const char *pathname, char *const argv[]);
       
       int execvp(const char *file, char *const argv[]);
       
       int execvpe(const char *file, char *const argv[], char *const envp[]);                     
  1. 返回值:
      exec函数族的函数执行成功后不会返回,调用失败时,会设置errno并返回-1,然后从原程序的调用点接着往下执行。
  2. 参数说明:
    path:可执行文件的路径名字
    arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
    file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

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

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

二、exec族函数(execl, execlp, execle, execv, execvp, execvpe)

下面将exec函数归为带 l、带 p、带 v、带 e 四类来说明参数特点。

1、带 L的一类exec函数(execl、execlp、execle)

带 l 的一类exec函数( l 表示list),包括execl、execlp、execle,要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针结尾。

execl函数为例子来说明:

//文件execl.c(demo18.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execl("./bin/echoarg","echoarg","abc",NULL) == -1)
    {
        printf("execl failed!\n");   

		perror("why failed");   
    }
    printf("after execl\n");
    return 0;
}
//文件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;
}

插入一个perror函数

perror()是一个C标准库函数,用于打印与上一个系统调用相关的错误信息。这个函数通常用于调试和错误处理,以便在程序发生错误时输出有关错误的信息。perror()函数的原型如下:

void perror(const char *s);

	perror("why failed");
  • 参数 s 是一个可选的自定义错误消息,通常是一个字符串,用于提供关于错误的额外信息。如果 s 参数为NULL,则perror()将只打印系统调用的错误信息。
  • perror()函数通常与全局变量errno一起使用,errno用于保存上一个系统调用的错误代码。perror()函数会解释errno中的错误代码并将相应的错误消息打印到标准错误流(stderr)。
  • 程序尝试打开一个不存在的文件并尝试读取文件。如果文件打开失败或读取失败,perror()函数将输出相关的错误消息,帮助程序员诊断问题。
运行结果

请添加图片描述

说明echoarg可执行程序文件不在./bin/echoarg路径下
//文件execl.c(demo18.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execl("./echoarg","echoarg","abc",NULL) == -1)
    {
        printf("execl failed!\n");   

		perror("why failed");   
    }
    printf("after execl\n");
    return 0;
}

请添加图片描述
实验说明:
我们先用gcc编译echoarg.c,生成可执行文件echoarg并放在当前路径bin目录下。文件echoarg的作用是打印命令行参数。然后再编译execl.c并执行execl可执行文件。用execl 找到并执行echoarg,将当前进程main替换掉,所以”after execl” 没有在终端被打印出来。

那么可以通过execl 函数来实现一下指令的调用

请添加图片描述

execl("/bin/ls","ls",NULL,NULL)
execl("/bin/ls","ls","-l",NULL)
execl("/bin/date","date",NULL,NULL)
//文件execl.c(demo19.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("execl this process get system ls:\n");
    if(execl("/bin/ls","ls",NULL,NULL) == -1)
    {
        printf("execl failed!\n");     

	perror("why failed");	
    }
    printf("after execl\n");
    return 0;
}
运行如下:

请添加图片描述
请添加图片描述
请添加图片描述

2、带 P的一类exec函数(execlp、execvp、execvpe)

带 p 的一类exec函数,包括execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子,PATH=/bin:/usr/bin

//文件execl.c(demo22.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("execl this process get system ps:\n");
    if(execl("ps","ps",NULL,NULL) == -1)
    {
        printf("execl failed!\n");     

	perror("why failed");	
    }
    printf("after execl\n");
    return 0;
}
实验结果:

请添加图片描述
上面这个例子因为参数没有带路径,所以execl找不到可执行文件。
下面再看一个例子对比一下:

//文件execlp.c(demo22.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

int main(void)
{
    printf("execl this process get system ps:\n");
    if(execlp("ps","ps",NULL,NULL) == -1)
    {
        printf("execl failed!\n");     

	perror("why failed");	
    }
    printf("after execl\n");
    return 0;
}
实验结果:

请添加图片描述
从上面的实验结果可以看出,上面的exaclp函数带 p ,所以能通过环境变量PATH查找到可执行文件ps

环境变量

环境变量是一种在计算机操作系统中用于存储配置信息和系统参数的机制。它们是以键-值对的形式存储的,其中键是环境变量的名称,值是与该名称相关联的配置或参数值。环境变量通常用于控制操作系统和应用程序的行为,以及在不同的计算环境中配置和定制软件。

在Unix/Linux系统中,$PATH是一个非常常见的环境变量。它用于指定操作系统在搜索可执行文件时应该查找的目录路径。具体来说,$PATH环境变量包含一系列目录路径,这些目录路径以冒号分隔(在Windows中可能以分号分隔)。当您在终端中输入命令时,操作系统将会按顺序查找这些目录,以查找与命令匹配的可执行文件。
例如,当您在终端中输入ls命令时,操作系统会搜索$PATH中列出的目录,以查找名为ls的可执行文件,然后执行它。如果ls可执行文件存在于$PATH中的某个目录,系统将执行它,否则会报错,指示找不到命令。
您可以使用echo $PATH命令来查看当前系统中$PATH环境变量的值。这将输出一个以冒号分隔的目录路径列表,每个目录路径对应一个操作系统在查找可执行文件时要搜索的位置。

echo $PATH
当前的环境变量

请添加图片描述

修改环境变量

没修改环境变量之前

请添加图片描述

修改环境变量之后

请添加图片描述

其他路径也能调用的到

请添加图片描述

找环境变量里的ps

请添加图片描述
请添加图片描述

3、带 V不带 L的一类exec函数(execv、execvp、execve)

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

如char *arg[ ]这种形式,且arg最后一个元素必须是NULL,例如char *arg[ ] = {“ls”,”-l”,NULL};
下面以execvp函数为例说明:

//文件execlp.c(demo23.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execvp(const char *file, char *const argv[]);

int main(void)
{
    printf("execl this process get system ps:\n");

    char *argv[] = {"ps",NULL,NULL};

    if(execvp("ps",argv) == -1)
    {
        printf("execl failed!\n");     

	perror("why failed");	
    }
    printf("after execl\n");
    return 0;
}
实验结果:

请添加图片描述

4、带 E的一类exec函数(execle、execvpe)

带e的一类exec函数,包括execle、execvpe,可以传递一个指向环境字符串指针数组的指针。

参数例如char *env_init[] = {“AA=aa”,”BB=bb”,NULL}; 带e表示该函数取envp[]数组,而不使用当前环境。
下面以execle函数为例:

//文件execle.c(demo24.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execle(const char *path, const char *arg,..., char * const envp[]);

char *env_init[] = {"AA=aa","BB=bb",NULL};
int main(void)
{
    printf("before execle****\n");
        if(execle("./echoenv","echoenv",NULL,env_init) == -1)
        {
                printf("execle failed!\n");

		perror("why failed");
        }       
    printf("after execle*****\n");
    return 0;
}
//文件echoenv.c
#include <stdio.h>
#include <unistd.h>
extern char** environ;
int main(int argc , char *argv[])
{
    int i;
    char **ptr;
    for(ptr = environ;*ptr != 0; ptr++)
        printf("%s\n",*ptr);
    return 0;
}
实验结果:

请添加图片描述
我们先写一个显示全部环境表的程序,命名为echoenv.c,然后编译成可执行文件放到当前目录下。然后再运行可执行文件execle,发现我们设置的环境变量确实有传进来。

三、exec配合fork使用

为什么要用exec族函数,有什么作用

exec函数族的主要作用是在一个进程内启动另一个程序。这些函数用于取代当前进程的映像,也就是将当前运行的程序替换为新的程序。以下是一些使用exec函数族的主要目的和作用:

  1. 执行其他程序:
    通过使用exec函数,你可以在一个程序中执行另一个程序。这对于在C语言中启动其他程序非常有用,例如,你可以通过exec函数来启动Shell命令、脚本、编译器等其他程序。

  2. 程序切换:
    exec函数可以用于切换到不同的程序上下文。当一个程序达到了它的目标并且不再需要执行时,它可以使用exec函数切换到一个完全不同的程序,从而释放资源并执行新任务。

  3. 定制进程环境:
    通过exec函数,你可以自定义新程序的环境。你可以指定不同的参数、工作目录、文件描述符等,以适应不同的需求。这使得你能够灵活地配置新程序的运行环境。

  4. 脚本解释:
    在某些情况下,exec函数可用于执行脚本文件或解释器。你可以通过exec来启动脚本语言的解释器,比如Python、Perl或Shell脚本,从而执行脚本中的代码。

  5. 资源共享:
    一个常见的用例是在父子进程之间共享文件描述符或其他资源。子进程可以通过exec函数来继承父进程的资源,以便它们可以在相同的上下文中运行。

总之,exec函数族提供了一种机制,使程序能够动态地启动和执行其他程序,从而扩展了程序的功能和灵活性。这在编写Shell脚本、进程管理、多进程通信等许多领域都非常有用。

exec配合fork使用

exec函数通常与fork函数一起使用,以创建子进程并在子进程中执行另一个程序。这是一种在Unix/Linux系统中常见的编程模式,用于实现进程的替换。以下是一个简单示例,演示了fork和exec的配合使用:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t child_pid = fork();

    if (child_pid == -1) {
        perror("fork");
        return 1; // Failed to create a child process
    }

    if (child_pid == 0) {
        // This code is executed in the child process

        // Use exec to replace the child process with a new program
        char *const args[] = {"ls", "-l", NULL};
        execvp("ls", args);

        // If exec fails, this code will run
        perror("exec");
        return 1;
    } else {
        // This code is executed in the parent process

        // Wait for the child process to finish
        int status;
        waitpid(child_pid, &status, 0);

        if (WIFEXITED(status)) {
            printf("Child process exited with status %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}

在这个示例中,fork函数用于创建一个新的子进程,然后在子进程中使用execvp函数来执行ls -l命令,替换子进程的映像。如果execvp执行成功,子进程的映像将被ls命令替代,并在新程序中继续执行。如果execvp执行失败,perror(“exec”)将打印出错误信息。

在父进程中,我们使用waitpid函数等待子进程完成,并获取子进程的退出状态。通过这种方式,父进程可以控制和监视子进程的执行。

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

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>


int main()
{
	pid_t pid;
	int changedata = 10;
	
	while(1){
		printf("please input a changedata.\n");
		scanf("%d",&changedata);
		if(changedata == 1){
			int fdSrc;

			pid = fork();

			if(pid > 0){
				wait(NULL);
			}

			if(pid == 0){
				char *readBuf=NULL;

				fdSrc = open("protest.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,"LENGTH=");
				if(p==NULL){
					printf("not found\n");
					exit(-1);
				}

				p = p+strlen("LENGTH=");
				*p = '5';

				lseek(fdSrc,0,SEEK_SET);
				int n_write = write(fdSrc,readBuf,strlen(readBuf));
				
				close(fdSrc);
				exit(0);	
			}
		}
		else{
                	printf("wait,do nothing\n");
        	}
	}

	return 0;
}
SPEED=3
LENGTH=8
SCORE=9
LEVEL=5
实验结果:

请添加图片描述
请添加图片描述

exec族函数配合fork使用

请添加图片描述
请添加图片描述

gcc ../FILE/demo14.c -o ./changData
#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 argc,char **argv)
{
	int fdSrc;
	char *readBuf = NULL;

	//参数错误
	if(argc != 2){
		printf("param error\n");
		exit(-1);
	}
	
	//打开原文件Src	
	fdSrc = open(argv[1],O_RDWR);
	
	//计算文件大小并让光标回到头
	int size = lseek(fdSrc,0,SEEK_END);
	lseek(fdSrc,0,SEEK_SET);

	//开辟空间给buf并读取原文件Src
	readBuf = (char *)malloc(sizeof(char)*size + 8);
	int n_read = read(fdSrc,readBuf,size);

	//查找并修改数据
//char *strstr(const char *haystack, const char *needle);
	char *p = strstr(readBuf,"LENGTH=");
	if(p == NULL){
		printf("not found\n");
		exit(-1);
	}

	p = p+strlen("LENGTH=");
	*p = '5';
	lseek(fdSrc,0,SEEK_SET);

	int n_write = write(fdSrc,readBuf,strlen(readBuf));

	//关闭文件
	close(fdSrc);

	return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>


int main()
{
	pid_t pid;
	int changedata = 10;
	
	while(1){
		printf("please input a changedata.\n");
		scanf("%d",&changedata);
		if(changedata == 1){
			int fdSrc;

			pid = fork();

			if(pid > 0){
				wait(NULL);
			}

			if(pid == 0){
				execl("./changeData","changeData","protest.txt",NULL);
			}
		}
		else{
                	printf("wait,do nothing\n");
        	}
	}

	return 0;
}
实验结果:

请添加图片描述

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值