进程程序替换目的
首先我们要知道,创建子进程的目的是什么?
- 想让子进程执行父进程代码的一部分
- 想让子进程执行一个全新的代码
我们之前所写的程序,子进程都是在执行父进程代码的一部分,而要想让子进程执行全新的代码,就需要进行进程程序替换
了解程序替换
先来看看进程程序替换是什么
上面这个父进程中fork了一个子进程,然后使用程序替换接口,替换了子进程的程序,父进程等待子进程结束,回收子进程
我们看一下程序替换的结果
这里我们看到子进程进行程序替换成了ls进程
此时,使用我们自己的程序同样可以实现ls -al的功能,因为子进程执行的就是ls -a -l程序。因为程序替换成功了,所以返回ls程序的退出码,如果替换失败,就会执行exit(1)。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>int main() {pid_t child_pid;if ((child_pid = fork()) == 0) { // 子进程if (execl("/bin/ls", "ls", "-a", "-l", NULL) == -1) {perror("execl");exit(1);}} else if (child_pid > 0) { // 父进程int status;waitpid(child_pid, &status, 0);if (WIFEXITED(status)) {printf("Child exited with status: %d\n", WEXITSTATUS(status));} else {printf("Child process terminated abnormally.\n");}} else {perror("fork");exit(1);}return 0;
}
程序替换的原理
在子进程刚创建的时候,子进程和父进程通过页表映射到物理内存中空间是同一块空间,父子进程的代码段,数据段,堆,栈等区域都同一个。
当子进程中执行exec*()函数的时候,会发生写时拷贝,将原本物理内存中的数据段和代码段拷贝一份,放在新的物理内存中。
将磁盘中要替换的可执行程序覆盖到新的物理内存中,并且改变子进程原本的页表映射关系。
仅程序发生了替换(数据段和代码段),子进程的PCB中的task_struct仍然不变。
而且写时拷贝不仅在数据段发生,在代码段也可以发生,写时拷贝的目的同样是为了保证进程的独立性。程序替换之后,子进程执行的代码也不再是原本父进程中的代码,而是全新的代码,比如上诉例子中的ls程序。
程序替换函数
第一个参数path表示要执行的程序的路径,第二个参数arg表示要执行的程序的名称,后面的参数是一系列字符串类型的参数,用于指定程序的参数。这里需要注意的是,最后一个参数必须是NULL,表示参数列表的结束。
#include <unistd.h>
#include <stdio.h>int main()
{if (fork() == 0){ // 子进程execl("/bin/echo", "echo", "Hello", "World!", NULL);} else { // 父进程wait(NULL);}return 0;
}
第一个参数file表示要执行的程序的文件名(不包括路径),第二个参数arg表示要执行的程序的名称,后面的参数是一系列字符串类型的参数,用于指定程序的参数。最后一个参数必须是NULL,表示参数列表的结束。
#include <unistd.h>
#include <stdio.h>int main()
{if (fork() == 0) { // 子进程execlp("echo", "echo", "Hello", "World!", NULL);} else { // 父进程wait(NULL);}return 0;
}
注意:
execlp
函数的第一个参数是要执行的可执行程序的路径或名称。具体取决于使用的是相对路径还是绝对路径。
如果可执行程序位于当前工作目录(当前路径)中,你可以直接提供可执行程序的名称作为第一个参数。
如果可执行程序位于其他目录中,可以提供它的绝对路径或相对路径作为第一个参数。
- 绝对路径:完整的文件系统路径,例如
/home/user/myprogram
。 - 相对路径:相对于当前工作目录的路径,例如
./myprogram
或../folder/myprogram
。
在调用 execlp
函数时,操作系统会根据给定的路径或名称去查找可执行程序,并在新的进程中执行它。
需要注意的是,execlp
函数会在系统的 PATH
环境变量定义的路径中查找可执行程序。因此,如果提供的是可执行程序的名称而不是完整路径,操作系统会根据 PATH
环境变量去寻找该程序。
execle 函数与 execlp 类似,但它需要显式地指定可执行程序的路径,并允许传递环境变量。下面是 execle 函数的参数说明:
path: 可执行程序的路径。可以使用绝对路径或相对路径来指定。例如,/usr/bin/myprogram 或者 ./myprogram。
arg0, arg1, …: 命令行参数,用于传递给可执行程序。常见的约定是将第一个参数作为程序的名称。例如,myprogram。
envp: 带有环境变量的指针数组。环境变量的格式为 name=value。数组最后一个元素必须为 NULL,表示环境变量列表的结束。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {char* args[] = {"echo", "$MY_VAR", NULL};char* env[] = {"MY_VAR=my_value", NULL};execle("/bin/echo", "echo", "$MY_VAR", NULL, env); // 运行 echoprintf("Exec failed\n"); // 如果运行程序失败,这行代码将不会被执行return 0;
}
在上述代码中,我们使用 execle 来运行 echo 命令,并将 $MY_VAR 作为参数传递给它。
同时,我们将一个名为 MY_VAR 的环境变量设为 my_value,使用 env 数组将其传递给 execle。此时,当 echo 命令执行时,它将打印 $MY_VAR,而不是实际的值。但是由于我们提供了 MY_VAR=my_value 的环境变量,因此 echo 命令可以获取到 MY_VAR 的实际值,所以输出将是 my_value。
需要注意的是,execle 会替换当前进程,所以在执行成功之后,程序就不会再执行下面的代码。如果 execle 执行失败,则会继续执行下面的代码,这时我们可以根据自己的需求进行错误处理。
第二个参数的指针数组,和mian命令函数中的char* argv[]一样,argv[0]是程序名,argv[1]等之后的是选项,最后一个是NULL。