Linux:进程替换(进程控制四)

在了解完进程等待之后,你是否思考过一个问题,我们如果想在一个进程里面执行其他的进程,该怎么办呢,等我们今天学习了进程替换,我们就会有结果啦

1.替换原理

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀ 种 exec 函数以执⾏另⼀个程序。当进程调⽤⼀种 exec 函数时,该进程的⽤⼾空间代码和数据完全被 新程序替换,从新程序的启动例程开始执⾏。调⽤ exec 并不创建新进程,所以调⽤ exec 前后该进程 的 id 并未改变(我们后面将用实验验证这个)

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[]);

3.函数解释

1.execl函数(着重讲这个,其他的和这个几乎类似)

我们创建一个新的目录,下面提供code.c与Makefile函数

Makefile:

cpp 复制代码
code : code.c
	gcc code.c -o code 
.PHONY : clean
clean :
	rm code

code.c:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    printf("main start!\n");
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    printf("main end()!\n");
    return 0;
}

运行结果如上,为什么只有start没有end呢?

这是因为一旦程序替换成功,就会执行新代码,后半部分就没了,所以execl是覆盖式的,会将后面的所有数据替换为execl的进程数据

这是一个最简单的函数啦,对于execl我们来查看一下man里面的定义

这里...采用了c语言里面的可变参数列表,最后以NULL结束

那具体的参数该怎么传入呢,下面给一份图:

通俗易懂,前面写路径,后面命令行就怎么写你就怎么写,最后加一个NULL结尾

对于返回值,exec*函数,只有失败返回值,如下图

可是我们有一个问题,我不想让这个进程将我的原始进程给破坏掉,我想达到的效果是都执行,那该怎么办呢?--子进程来帮助我们,我们创造一个子进程(fork),让子进程执行我们需要替换的进程,父进程等待接收子进程就可以啦,这样完成了两个都实现

下面对code.c的代码进行修改

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("main start!\n");
    if(fork() == 0)
    {
        execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
        exit(1);
    }
    waitpid(-1, NULL, 0);
    printf("main end()!\n");
    return 0;
}

运行一下

完美实现了我们想要的功能

现在我们来提问一下,为什么子进程对父进程没有影响呢??

答:因为进程具有独立性

我们不经会提出另一个问题,子进程不是和父进程共享数据与代码吗,这里执行execl不是会对代码数据进行覆盖吗,为什么父进程没有事情呢??

答:写实拷贝,对的,写实拷贝直接将父进程的代码数据拷贝给了子进程,直接解决了这个问题

我们之前讲过一切可运行的皆是进程,那么是不是可以使用exec*来运行所有的进程呢,比如java的,c++等等的进程?

答:是的,都可以!

下面我们创建一个other.cc文件,并且编译他形成可执行文件,将code.c也更改一下

other.cc:

cpp 复制代码
#include <iostream>

int main()
{
    std::cout << "hello c++" << std::endl;
    return 0;
}

code.c:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("main start!\n");
    if(fork() == 0)
    {
        execl("./other", "./other", NULL);
        exit(1);
    }
    waitpid(-1, NULL, 0);
    printf("main end()!\n");
    return 0;
}

运行结果:

同理其他就不演示了

回到我们最开始的一个问题,我们通过实验来验证原进程与execl覆盖之后的进程是同一个进程,如果他们两个pid相同,就可以证明这个了

下面我们将code.c与other.cc再次进行修改

other.cc:

cpp 复制代码
#include <iostream>
#include <unistd.h>

int main()
{
    std::cout << "我是execl之后的进程,我的pid : "  << getpid() << std::endl;
    return 0;
}

code.c:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("main start!\n");
    if(fork() == 0)
    {
        printf("我是原进程,我的pid : %d\n", getpid());
        execl("./other", "./other", NULL);
        exit(1);
    }
    waitpid(-1, NULL, 0);
    printf("main end()!\n");
    return 0;
}

运行结果:

证明了,前后是同一个进程!!

2.execlp:

看名字就是比execl多一个p,那这个p是什么意思呢,其实就是PATH(环境变量)的意思,说明只需告诉文件名就行,不需要告诉路径,因为他自己会去环境变量PATH路径中查找

code.c:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("main start!\n");
    if(fork() == 0)
    {
        //printf("我是原进程,我的pid : %d\n", getpid());
        //execl("./other", "./other", NULL);
        execlp("ls", "ls", "-a", "-l", NULL);
        exit(1);
    }
    waitpid(-1, NULL, 0);
    printf("main end()!\n");
    return 0;
}

运行成功

3.execv:

与execl类似,将l换为了v,其实我们可以将l理解为list,传入的是参数列表,像List一样一个一个链接起来,而v可以理解为vector,将命令行参数表用一个指针数组存放起来

code.c:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("main start!\n");
    if(fork() == 0)
    {
        //printf("我是原进程,我的pid : %d\n", getpid());
        //execl("./other", "./other", NULL);
        //execlp("ls", "ls", "-a", "-l", NULL);
        char* const argv[] ={ (char* const)"ls", (char* const)"-l", (char* const)"-a", NULL };
        execv("/usr/bin/ls", argv);
        exit(1);
    }
    waitpid(-1, NULL, 0);
    printf("main end()!\n");
    return 0;
}

运行结果如下:

4.execvp

字面意思就是不需要传入完整路径,只需要传入"ls",就可以自己去环境变量PATH里面寻找

5.execvpe

多了一个e,那么e是什么呢,其实e是环境变量

code.c:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    printf("main start!\n");
    if(fork() == 0)
    {
        //printf("我是原进程,我的pid : %d\n", getpid());
        //execl("./other", "./other", NULL);
        //execlp("ls", "ls", "-a", "-l", NULL);
        char* const argv[] ={ (char* const)"ls", (char* const)"-l", (char* const)"-a", NULL };
        char* const env[] = { (char* const)"MYENV=123456", NULL };
        //execv("/usr/bin/ls", argv);
        execvpe("./other", argv, env);
        exit(1);
    }
    waitpid(-1, NULL, 0);
    printf("main end()!\n");
    return 0;
}

other.cc:

cpp 复制代码
#include <iostream>
#include <unistd.h>
using namespace std;

int main(int argc, char* argv[], char* env[])
{
    //std::cout << "我是execl之后的进程,我的pid : "  << getpid() << std::endl;
    for (int i = 0; i < argc; i++)
    {
        cout << "argv[" << i << "] : " << argv[i] << endl;
    }
    for (int i = 0; env[i]; i++)
    {
        cout << "env[" << i << "] : " << env[i] << endl;;
    }
    return 0;
}

运行结果:

要是我们想在原有的环境变量(系统默认的环境变量)中加入MYENV而不是自己覆盖式的写一个呢

上面的两种办法(一个是循环,一个是直接使用execvp)

4.各个函数的汇总信息与区别

函数名 参数格式 是否带路径 是否使用当前环境变量
execl 列表 不是
execlp 列表
execle 列表 不是 不是,须自己组装环境变量
execv 数组 不是
execvp 数组
execve 数组 不是 不是,须自己组装环境变量

5.execve函数

如果我们使用man,就会发现其他的都是man 3,而只有execve是man 2
事实上,只有 execve 是真正的系统调⽤,其它五个函数最终都调⽤ execve ,所以 execve 在 man
⼿册 第2节,其它函数在 man ⼿册第3节。这些函数之间的关系如下图所⽰。
下图exec函数簇 ⼀个完整的例⼦:

好啦,这就是今天的博博客啦,大家有问题的评论区提问哦~~

相关推荐
std860213 小时前
Linux 6.18发布:年度最后版本或成新长期支持版本
linux·运维·服务器
一叶之秋14123 小时前
从零开始学Linux进程控制:fork、wait、exec 详解
linux·运维·服务器
thinkMoreAndDoMore3 小时前
linux系统服务管理框架
linux·运维·服务器
van久3 小时前
.NET Core 学习第一天:Razor Pages应用介绍及目录结构
学习
waves浪游4 小时前
进程控制(中)
linux·运维·服务器·开发语言·c++
LO嘉嘉VE4 小时前
学习笔记二十六:支持向量机-软间隔与正则化
笔记·学习·支持向量机
满天星83035774 小时前
【Linux】信号(上)
linux·运维·服务器·开发语言·c++
库奇噜啦呼4 小时前
【iOS】GCD学习
学习·ios·cocoa
科普瑞传感仪器4 小时前
航空航天领域青睐:复杂曲面机器人抛光为何必须采用六维力控?
运维·人工智能·机器人·自动化·无人机