文章目录
之前我们进行的程序演示里,都只能运行自己的代码。那我们怎么样才能执行其他程序的代码呢?(例如在程序里使用ls之类的指令)就可以使用进程程序替换,一开始我们先只看单进程的情况。后面在引入多进程的情况
进程程序替换是指在运行过程中将一个进程的地址空间中的代码、数据和堆栈等内容完全替换为另一个程序的代码、数据和堆栈的过程。这个过程通常是由操作系统提供的 exec
系列函数来实现的:
exec
函数族:exec
函数族是一组系统调用,用于执行程序替换操作。这些函数包括 execl
, execv
, execle
, execve
等,它们允许以不同的方式传递参数给新程序,并执行地址空间替换。
我们要改变内存,那肯定是要调用系统调用接口的,这些函数会封装相应的接口
main
函数或其他指定的入口函数。替换完成后,控制权将转移到程序入口点,开始执行新程序的代码。
exec
系列函数execl函数是Linux系统中用于执行新程序的函数之一,它属于exec函数族的一部分。这个函数的作用是在当前进程的上下文中启动一个新的程序,并替换当前进程的映像为新的程序映像。调用execl函数后,当前进程将停止执行,并由新的程序开始执行。
#include<unistd.h>
int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
参数说明:
path
:要执行的程序的路径。arg0
:新程序的参数列表的开始,通常这会是新程序的名称(尽管这不是强制的,但它通常用于错误消息和程序内部)。...
:一个可变参数列表(参数的数量不固定),新程序的参数列表,必须以NULL结尾。execl函数会根据提供的路径
path
找到并执行相应的程序,同时将arg0
及其后面的参数作为新程序的命令行参数传递。注意,参数列表必须以NULL结尾,这是告诉execl参数列表结束的标志。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
printf("I'm a process, pid: %d\n", getpid());
printf("execl begin...\n");
int a=execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
printf("execl end...\n");
return 0;
}
如果execl函数调用成功,那么它实际上不会返回,因为当前进程的映像已经被新程序替换。如果调用失败,它会返回-1,并设置全局变量errno
以指示错误原因。常见的错误原因可能包括文件未找到、权限不足等。
execl函数和其他exec函数一样,不会创建新的进程。它们只是在当前进程的上下文中启动另一个程序。
因此,调用execl前后,进程的ID(PID)不会改变。同时,由于execl会替换整个进程映像,所以在调用execl之前,通常需要确保当前进程的所有打开的文件描述符、内存分配等都被适当地处理或释放,因为这些资源不会被新程序继承。
exec
后面的代码不在执行。因为被替换掉了,这也是什么代码没有输出execl end
的原因了
exec
函数调用成功,那么它实际上不会有返回值;调用失败,它会返回-1
exec
函数不会创建新的进程。它们只是在当前进程的上下文中启动另一个程序
exec
函数时,操作系统会加载新程序的可执行文件,并将其代码、数据和堆栈等部分加载到进程的地址空间中。这个过程涉及将新程序的内容从磁盘加载到内存中,为进程提供执行所需的资源。因此,虽然我们常说是“程序替换”,但实际上更准确地说是将新程序加载到内存中,替换掉原有的程序,以实现进程的功能切换和更新。
我们可以创建一个子进程,由子进程来进行程序替换,父进程来等待结果就可以。为什么? 父进程能得到子进程的执行结果
我们知道父进程与子进程映射到同一块代码,那么子进程进行程序替换后,不是会覆盖吗,替换为什么不影响父进程?
进程具有独立性,在进行程序替换时要进行写时拷贝 写时拷贝的本质就是开辟新的空间
shell是如何运行起来一个指令的?
首先创建子进程,shell会waitpid()等待进程结果,子进程会继承shell的代码,但是不影响。子进程进行程序替换,替换为我们输入的指令
int main()
{
pid_t id = fork();
if (id == 0)
{
printf("I'm a process, pid: %d\n", getpid());
printf("execl begin...\n");
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
printf("execl end...\n");
exit(1);
}
pid_t rid = waitpid(id, NULL, 0);
if (rid > 0)
{
printf("wait successfully\n");
}
return 0;
}
execl
:该函数允许通过提供可变数量的参数来执行指定的可执行文件。它的原型如下:
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
path
是要执行的可执行文件的路径,arg0
是第一个参数,后续参数都是传递给可执行文件的命令行参数,以 NULL
结尾。
execlp
:该函数与 execl
类似,但是它会在系统的环境变量 PATH
指定的目录中查找可执行文件。它的原型如下:
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
file
是要执行的可执行文件的文件名,arg0
是第一个参数,后续参数都是传递给可执行文件的命令行参数,以 NULL
结尾。
相比于execl函数,execlp函数的第一个参数能直接写文件名,系统会PATH环境变量里去查找 多的字母
p
:PATH环境变量
int main()
{
pid_t id = fork();
if (id == 0)
{
printf("I'm a process, pid: %d\n", getpid());
printf("execl begin...\n");
execl("ls", "ls", "-a", "-l", NULL);
printf("execl end...\n");
exit(1);
}
pid_t rid = waitpid(id, NULL, 0);
if (rid > 0)
{
printf("wait successfully\n");
}
return 0;
}
execv
:类似于 execl
,但是允许传递一个参数数组给被执行的程序。它的原型如下:
int execv(const char *path, char *const argv[]);
path
是要执行的可执行文件的路径,argv
是一个以 NULL
结尾的参数数组,其中每个元素都是一个字符串,表示命令行参数。
相比于exec多个字母
v
:代表vector
int main()
{
pid_t id = fork();
if (id == 0)
{
printf("I'm a process, pid: %d\n", getpid());
printf("execl begin...\n");
char* argv[] = { "ls","-a","-l",NULL};
execv("/usr/bin/ls",argv);
printf("execl end...\n");
exit(1);
}
pid_t rid = waitpid(id, NULL, 0);
if (rid > 0)
{
printf("wait successfully\n");
}
return 0;
}
execvp
:类似于 execv
,但是它会在系统的环境变量 PATH
指定的目录中查找可执行文件。它的原型如下:
int execvp(const char *file, char *const argv[]);
file
是要执行的可执行文件的文件名,argv
是一个以 NULL
结尾的参数数组,其中每个元素都是一个字符串,表示命令行参数。
既有字母
p
又有v
,结合上面那两种就行
execle
:函数与 execl
函数类似,但允许在启动新程序时传递额外的环境变量。它的原型如下:
int execle(const char *path, const char *arg, ..., char *const envp[]);
path
是要执行的可执行文件的路径,arg
是要传递给新程序的命令行参数,后面的参数是额外的环境变量,以 NULL
结尾。
进程程序替换不会替换环境变量的
putenv()
函数,会影响子进程
putenv
是 C 语言中的一个库函数,它定义在 <stdlib.h>
头文件中。这个函数用于将字符串添加到环境变量中,或者修改已经存在的环境变量的值。
int putenv(const char *string);
execle()
函数,那么替换后的代码切换后的环境变量就只是我们传入的表里的内容code.c里:
int main()
{
char* const env[] = {
(char*)"first",
(char*)"second",
NULL };
pid_t id = fork();
if (id == 0)
{
printf("I'm a process, pid: %d\n", getpid());
printf("execl begin...\n");
execle("./mytest", "mytest", NULL, env)
printf("execl end...\n");
exit(1);
}
pid_t rid = waitpid(id, NULL, 0);
if (rid > 0)
{
printf("wait successfully\n");
}
return 0;
}
test.cpp里:
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
for (int i = 0; environ[i]; i++)
{
printf("env[%d]: %s\n", i, environ[i]);
}
cout << "This is C++" << endl;
return 0;
}
当然我们也能传系统环境变量,但是没必要,这样的话直接默认就行
execle("./mytest", "mytest", NULL, environ)//传入这个全局变量
.PHONY:all
all:mycode mytest
mycode:code.c
gcc -o $@ $^
mytest:test.cpp
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f mycode mytest
.PHONY
:声明一个或多个目标是伪目标(phony targets)通过声明伪目标,你可以确保 make
总是执行相应的命令,而不会因为同名的文件或目录的存在而跳过这些命令make
命令时(没有指定具体目标),make
会首先查找 Makefile
中的第一个目标,并尝试构建它。在这个过程中,make
会检查该目标的所有依赖项,并递归地处理这些依赖项,直到所有必要的依赖项都被构建或确认为是最新的make
工具被调用以构建某个目标时,它会检查该目标的所有依赖项,并根据需要构建这些依赖项。然而,对于 clean
这样的伪目标,它并没有列出任何依赖项,因此其他目标的构建状态不会影响 clean
的执行