0


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

1.前言

进程程序替换是指一个进程用另一个新的可执行程序来替换当前正在执行的程序,这个过程通过通过

exec

系列函数完成。在Linux或UNIX系统中,进程程序替换通常发生在一个进程通过

fork()

创建了子进程之后,子进程用

exec()

函数加载和执行另一个程序。
也就是说,进程程序替换就是在不改变进程的PID(进程ID)的情况下,用一个全新的程序来替换当前的内存空间和执行内容。
当程序调用一种

exec

函数时,该进程的用户空间代码和数据完全被新的程序替换,从新程序的启动例程开始执行。

进程程序替换

2.替换函数

exec

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

exec

函数包括:

#include<unistd.h>intexecl(constchar* path,constchar* arg,...);intexeclp(constchar* file,constchar* arg,...);intexecle(constchar* path,constchar* arg,...,char*const envp[]);intexecv(constchar* path,char*const argv[]);intexecvp(constchar* file,char*const argv[]);intexecve(constchar* 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>intmain(){pid_t id =fork();if(id <0){perror("fork failed");exit(1);}if(id ==0){//childprintf("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{//fatherwait(NULL);//等待子进程结束printf("child process finished\n");}return0;}

执行结果:

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>intmain(){pid_t id =fork();if(id ==0){execlp("ls","ls","-a","-l",NULL);exit(1);}elseif(id>0){int ret =wait(NULL);if(ret<0){perror("wait failed");}else{printf("wait success\n");}}else{perror("fork failed");exit(1);}return0;}

执行结果

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>intmain(){externchar** environ;int i =0;for(;environ[i];i++){printf("%s\n",environ[i]);}return0;}

execle测试

#include<stdio.h>#include<sys/wait.h>#include<sys/types.h>#include<stdlib.h>#include<unistd.h>intmain(){pid_t id =fork();if(id ==0){char*const myEnv[]={"MYVAL1 = 123","MYVAL2 = 456","MYVAL3 = 789",NULL};execle("./myexe","./myexe",NULL,myEnv);exit(1);}elseif(id>0){int ret =wait(NULL);if(ret>0){printf("wait success\n");}else{perror("wait failed\n");}}else{perror("fork failed");exit(1);}return0;}

结果:

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>intmain(){pid_t id =fork();if(id <0){perror("fork failed");exit(1);}if(id ==0){//childchar*const argv[]={"ls","-a","-l",NULL};execv("/bin/ls",argv);//如果execv执行成功,下行将不会被执行。perror("execl failed");exit(1);}else{//fatherwait(NULL);//等待子进程结束printf("child process finished\n");}return0;}

执行结果:

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

的各个结果。其他函数只需要在此基础上进行配合就是了。
exec函数

4. 总结

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

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

标签: linux windows 运维

本文转载自: https://blog.csdn.net/2303_79015671/article/details/143092797
版权归原作者 Yui_ 所有, 如有侵权,请联系我们删除。

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

还没有评论