【Linux】从 fork() 到 exec():理解 Linux 进程程序替换的魔法

举报
Yui_ 发表于 2024/12/07 16:10:07 2024/12/07
【摘要】 【Linux】从 fork() 到 exec():理解 Linux 进程程序替换的魔法

1.前言

进程程序替换是指一个进程用另一个新的可执行程序来替换当前正在执行的程序,这个过程通过通过exec系列函数完成。在Linux或UNIX系统中,进程程序替换通常发生在一个进程通过fork()创建了子进程之后,子进程用exec()函数加载和执行另一个程序。
也就是说,进程程序替换就是在不改变进程的PID(进程ID)的情况下,用一个全新的程序来替换当前的内存空间和执行内容。
当程序调用一种exec函数时,该进程的用户空间代码和数据完全被新的程序替换,从新程序的启动例程开始执行。

image.png

2.替换函数

exec函数是一个系列函数,负责替换当前进程的映像。常见的exec函数包括:

#include <unistd.h>
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 execve(const char* path,char* const argv[],char* const envp[]);

2.1 函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不在返回。
  • 如果调用出错则返回-1。
  • 所以exec函数只有出错的返回值而没有成功的返回值。
    使用execl测试
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork failed");
		exit(1);
	}
	if(id == 0)
	{
		//child
		printf("i am a child process,replacing myself with /bin/ls\n");
		execl("/bin/ls","ls",NULL);//使用exec替换为ls程序
		//如果execl执行成功,下行将不会被执行。
		perror("execl failed");
		exit(1);
	}
	else 
	{
		//father
		wait(NULL);//等待子进程结束
		printf("child process finished\n");
	}
	return 0;
}

执行结果:

i am a child process,replacing myself with /bin/ls
a.out  pReplaceTest.c
child process finished

在这个例子中:

  • 父进程创建了一个子进程。
  • 子进程使用execl()来替换自己,将当前的进程映像替换为/bin/ls程序。
  • 替换成功后,子进程开始执行ls程序的代码,将不在执行原理的代码。
  • 父进程调用wait()等待子进程结束,执行ls命令将结果输出到控制台。
    在程序的替换函数,通常不会让父进程去执行,而是交给子进程去执行,因为父进程还需要进行完成它的时,同时也防止替换的程序造成程序崩溃。由于进程间的独立性,即使子进程去执行execl函数时候,替换的也是子进程的代码和数据,而父进程的代码和数据是不会被影响的。
    提问:发生了子进程的程序替换,此时:父进程的代码还是共享的吗?
    回答:

不是,此时发生了写时拷贝,也就是会拷贝一份代码和数据出来,然后子进程再被它的execl还是进行程序替换。

2.2 命名理解

这些函数的名称还是太相似了,为了方便记忆,可以按一下规律记忆一下。

  • l(list):表示参数采用列表。
  • v(vector):参数用数组。
  • p(path):有p自动搜索环境变量PATH。
  • e(env):表示自己维护环境变量。
函数名 参数格式 是否带路径 是否使用当前环境变量
execl 列表 不是
execlp 列表
execle 列表 不是 不是,需要自己组装环境变量
execv 数组 不是
execvp 数组
execve 数组 不是 不是,需要自己组装环境变量

2.3 exec系列函数介绍

execl函数在上文已经介绍,这里就跳过了。

2.3.1 execlp函数

execlp函数和execl函数的区别在于,execlp在第一个参数时候,不需要全路径,只需要写上执行命令的文件名即可,表示你需要执行谁,往后也就是和execl的参数一样。

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        execlp("ls","ls","-a","-l",NULL);
        exit(1);
    }
    else if(id>0)
    {
        int ret = wait(NULL);
        if(ret<0)
        {
            perror("wait failed");
        }
        else
        {
            printf("wait success\n");
        }
    }
    else
    {
        perror("fork failed");
        exit(1);
    }

	return 0;
}

执行结果

drwxrwxr-x  2 ubuntu ubuntu  4096 Oct 20 14:18 .
drwxr-x--- 11 ubuntu ubuntu  4096 Oct 20 14:18 ..
-rwxrwxr-x  1 ubuntu ubuntu 16168 Oct 20 12:39 a.out
-rwxrwxr-x  1 ubuntu ubuntu 16168 Oct 20 14:18 execlp
-rw-rw-r--  1 ubuntu ubuntu   519 Oct 20 14:18 execlpTest.c
-rw-rw-r--  1 ubuntu ubuntu   552 Oct 20 12:39 pReplaceTest.c
-rw-rw-r--  1 ubuntu ubuntu     1 Oct 20 14:11 test.c
wait success

2.3.2 execle函数

execle函数比execl函数多一个e,按照上的设定。需要在最后一个参数需要给execle传入自定义的环境变量数组。
它的使用情况:如果你需要给你执行的一个新的程序,加载一些自定义的环境变量给新的程序时候,以可以使用该函数。
exeTest.c文件:打印环境变量的值,这个文件假如自己执行自己的话那么会打印默认的环境变量。假如其他文件使用execle传参给exeTest.c的话,exeTest.c就会执行该execle传递过来的环境变量。

#include <stdio.h>
int main()
{
    extern char** environ;
    int i = 0;
    for(;environ[i];i++)
    {
        printf("%s\n",environ[i]);
    }
    return 0;
}

execle测试

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        char* const myEnv[] = {
            "MYVAL1 = 123",
            "MYVAL2 = 456",
            "MYVAL3 = 789",
            NULL
        };
        execle("./myexe","./myexe",NULL,myEnv);
        exit(1);
    }
    else if(id>0)
    {
        int ret = wait(NULL);
        if(ret>0)
        {
            printf("wait success\n");
        }
        else
        {
            perror("wait failed\n");
        }
    }
    else 
    {
        perror("fork failed");
        exit(1);
    }

    return 0;
}

结果:

MYVAL1 = 123
MYVAL2 = 456
MYVAL3 = 789
wait success

2.3.3 execv函数

execv和execl函数没什么区别。execv函数把execl的以列形式的传参,变成了以数组的形式的传参。

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

int main()
{
    pid_t id = fork();
	if(id < 0)
	{
		perror("fork failed");
		exit(1);
	}
	if(id == 0)
	{
		//child
        char* const argv[]={
            "ls",
            "-a",
            "-l",
            NULL
        };
        execv("/bin/ls",argv);
		//如果execv执行成功,下行将不会被执行。
		perror("execl failed");
		exit(1);
	}
	else
	{
		//father
		wait(NULL);//等待子进程结束
		printf("child process finished\n");
	}
    return 0;
}

执行结果:

drwxrwxr-x  2 ubuntu ubuntu  4096 Oct 20 14:54 .
drwxr-x--- 11 ubuntu ubuntu  4096 Oct 20 14:54 ..
-rwxrwxr-x  1 ubuntu ubuntu 16216 Oct 20 14:54 a.out
-rwxrwxr-x  1 ubuntu ubuntu 16216 Oct 20 14:41 execle
-rw-rw-r--  1 ubuntu ubuntu   672 Oct 20 14:38 execleTest.c
-rwxrwxr-x  1 ubuntu ubuntu 16168 Oct 20 14:18 execlp
-rw-rw-r--  1 ubuntu ubuntu   519 Oct 20 14:18 execlpTest.c
-rw-rw-r--  1 ubuntu ubuntu   575 Oct 20 14:54 execvTest.c
-rw-rw-r--  1 ubuntu ubuntu   162 Oct 20 14:40 exeTest.c
-rwxrwxr-x  1 ubuntu ubuntu 16024 Oct 20 14:40 myexe
-rw-rw-r--  1 ubuntu ubuntu   552 Oct 20 12:39 pReplaceTest.c
-rw-rw-r--  1 ubuntu ubuntu     1 Oct 20 14:11 test.c
child process finished

如此一来,我们就讲完了:l v p e的各个结果。其他函数只需要在此基础上进行配合就是了。
image.png

4. 总结

  • 进程程序替换是指用一个新的可执行程序替换当前进程的内存空间和执行内容,但进程ID不变。
  • 常用的替换函数是 exec 系列函数(如 execl()execvp())。
  • 它常用于父进程通过 fork() 创建子进程后,子进程用 exec() 替换为新的程序来执行指定任务。
  • 替换后的进程将完全抛弃原来的代码和数据,并开始执行新加载的程序。

如果我文章对你有所帮助的话,点个关注吧~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。