[Linux 进程控制(二)] 进程程序替换

这篇具有很好参考价值的文章主要介绍了[Linux 进程控制(二)] 进程程序替换。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

1、进程程序替换

首先,我们要认识到,我们之前fork()所创建的子进程,执行的代码,都是父进程的一部分(用if-else分流或者执行同样的代码)!
如果我们想让子进程执行新的程序呢?执行全新的代码和访问全新的数据,不再和父进程有瓜葛,这种技术就叫做程序替换,下面我们就来学习一下:
首先我们先写一份单进程版的程序替换的代码(没有子进程),先来见见!
linux下有execl这样的一批接口,大家先来看看:
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

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

int main()
{
    printf("pid: %d, exec command begin\n", getpid());
    // 因为可变参数列表,所以以NULL结尾,表示参数传完了
    execl("/usr/bin/ls", "ls", "-l", "-a", NULL); 
    printf("pid: %d, exec command end\n", getpid());
    
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

我们直接用“语言”将其他进程执行了! 但是我们预期还有end呢,没有打印出来,怎么回事?这个我们下面回答!
熟悉了使用后,我们再来看看另外的代码:

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execl("/usr/bin/ls", "ls", "-l", "-a", NULL); // 因为可变参数列表,所以以NULL结尾,表示参数传完了
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

我们在等成等待的时候,没有指定pid,给了-1,让随机等待,返回的进程pid与子进程的pid一样,由此我们可以得出一个结论:进程替换没有创建新的进程!
在上面的代码中子进程进行了程序替换,是需要在物理内存中重新开辟空间,并将子进程的页表进行修改,代码也是数据,替换时子进程对代码做出了修改,又由于进程具有独立性,父进程与子进程互不影响,他们各自执行自己的代码,就实现了解耦,互不影响!
问题:
子进程怎么知道,要从新的程序的最开始执行?它怎么知道最开始的地方在哪?
在Linux中,可执行程序有一定的格式叫做ELF,二进制文件的头部有一张表,表中有一个字段,里面记录了程序的入口地址(entry),CPU内有一批寄存器,有的时程序计数器(eip,pc)它们记录了代码执行的上下文,eip内存的是当前所执行代码的下一条,所以我们想要执行新的程序,只需要将entry中的地址填到eip,它就会进入新的程序开始执行!
至此,我们就知道了,为什么上面第一段代码中替换后,输出没有打印出来了。单进程中,替换后原本的代码和数据都被替换了,原本的后续代码就没有了!多进程中,因为我们将eip的地址填为了替换后进程的入口地址,这样就不会再给eip中更新原本语句的地址了,因此也就不可能再执行后面的代码了!
exec这样的函数,如果当前进程执行成功,则后续代码没有机会在执行了!因为被替换掉了!如果失败,就会继续执行本来代码的后续!exec只有失败的返回值,没有成功的返回值,所以使用时不用判断!

1.1 替换原理

程序替换 原理是,将物理内存中的数据和代码替换为我们想要替换的进程的代码和数据(虚拟内存会根据新加载的代码和数据做相应的调整,但是各个区的范围是不变的),这就做到了谁调用exec系列函数就可以实现程序替换!!!
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

因此这也说明 进程替换并没有创建新的程序,只是替换了数据和代码!

1.2 exec系列函数使用

总结一个思想:
a. 必须先找到这个可执行程序
b. 必须告诉exec,怎么执行*
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

1.2.1 execl函数

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器
此函数最开始就使用了,使用样例直接跳转到通篇开始去看即可!

1.2.2 execlp函数

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execlp("ls", "ls", "-a", "-l", NULL);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

1.2.3 execv函数

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        char* const argv[] = {"ls", "-a", "-l", NULL};
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execv("/usr/bin/ls", argv);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

1.2.4 execvp函数

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        char* const argv[] = {"ls", "-a", "-l", NULL};
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execvp("ls", argv);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

替换自己的程序

我们一直都替换的是库中的程序,我们自己写的程序可以替换吗?答案是可以,我们来试一下:
先写一份C语言测试代码:

#include <iostream>
using namespace std;

int main()
{
    cout << "hello linux!" << endl;
    cout << "hello linux!" << endl;
    cout << "hello linux!" << endl;

    return 0;
}

再写程序替换代码:

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

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        execl("./mytest.cc", "mytest", NULL);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

问题:
之前不是说execl函数第一个参数是路径,第二个以及后面的命令行怎么写就怎么传么,这里execl函数第二个参数怎么没有带./呢?
我们在命令行中执行自己的可执行程序时,先要找到可执行程序,再调用execl函数时,我们第一个参数传了路径了,后面的参数就不用了带./了。
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

我们还可以写一份脚本语言,并进行程序替换:

#!/usr/bin/bash 

echo "hello world!"
touch file1 file2 file3
echo "hell done"
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        execl("/usr/bin/bash", "bash", "test.sh", NULL);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器
所以exec系列函数进行程序替换,不就是加载的过程么,这不就是加载器的重要功能么!

1.2.5 execle函数

我们先来铺垫两点,铺垫完之后就更容易理解execle函数了!
1、当进行程序替换的时候,子进程对应的环境变量,是可以直接从父进程来的!下面我们进行验证:
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

这里介绍一个函数,putenv()函数,添加环境变量的。
我们写两套代码,mytest.cc和procReplace.c,mytest.cc为替换的程序!

#include <iostream>
using namespace std;

int main(int argc, char* argv[], char* env[])
{
    for(int i = 0; env[i]; i++)
        {
            cout << i << " : " << env[i];
        }

    return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
    char* my_val = "MYENV=11111111111";
    putenv(my_val);
    
    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        execl("./mytest", "mytest", NULL);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

我们之前在环境变量中就讲过,子进程会继承父进程的环境变量表,这里的父进程是procReplace,mytest继承了它,而procReplace是bash的子进程,所以除了MYENV这个环境变量是procReplace的,其他的环境变量都是procReplace继承bash来的。所以我们得出的结论就是:
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

2、环境变量被子进程继承下去是一种默认行为,不受程序替换的影响!
为什么?
[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

子进程创建时,拷贝了父进程的PCB,进程地址空间,页表。命令行参数和环境变量是数据,在物理内存中开辟了空间,因此通过地址空间可以让子进程继承父进程的环境变量数据!
程序替换,只替换新程序的代码和数据,环境变量不会被替换!
3、让子进程执行的时候,获得的环境变量,以下面两种方式传递
a、将父进程的环境变量原封不动传递给子进程
经过上面的演示,我们发现:

  • 直接用就可以,因为子进程继承了父进程的环境变量表(不演示,上面的代码就是直接用);
  • 直接传环境变量表。
    这里我们使用一下execle函数,演示一下直接传:
    [Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

还是两个代码,一个父一个子:

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

int main()
{
    extern char** environ;
    
    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        execle("./mytest", "mytest", "-a", "-l", NULL, environ);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}
#include <iostream>
using namespace std;

int main(int argc, char* argv[], char* env[])
{
    for(int i = 0; i < argc; i++)
    {
        cout << i << " -> " << argv[i] << endl;
    }
    cout << "##################################" << endl;
    for(int i = 0; env[i]; i++)
    {
        cout << i << " : " << env[i] << endl;
    }
    
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

b、我们想传我们自己的环境变量!
我们自己写一批环境变量,再使用execle函数进行传参即可:

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

int main()
{
    char* const myenv[] = // 自定义环境变量表
    {
        "MYENV1 = 11111111111111111",
        "MYENV2 = 22222222222222222",
        "MYENV3 = 33333333333333333",
        "MYENV4 = 44444444444444444",
        NULL 
    };

    pid_t id = fork();
    if(0 == id)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        execle("./mytest", "mytest", "-a", "-l", NULL, myenv);
        printf("pid: %d, exec command end\n", getpid());
    }
    else
    {
        // father
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success, rid: %d\n", rid);
        }
    }
    return 0;
}
#include <iostream>
using namespace std;

int main(int argc, char* argv[], char* env[])
{
    for(int i = 0; i < argc; i++)
    {
        cout << i << " -> " << argv[i] << endl;
    }
    cout << "##################################" << endl;
    for(int i = 0; env[i]; i++)
    {
        cout << i << " : " << env[i] << endl;
    }
    
    return 0;
}

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器

我们发现,我们使用execle函数将定义的环境变量传过去之后,并不是在原来的基础上新增,而是而是覆盖式传递!
c、如果我想新增呢?
其实我们已经做过了,我们在这个函数讲解开始的时候说过一个函数,putenv函数,如果我们想要新增,直接在父进程中使用putenv添加环境变量,然后再fork创建子进程,子进程继承父进程环境变量表后,就实现了在原来的基础上新增!
总结程序替换(exec系列函数)可以将命令行参数和环境变量(覆盖式传递),通过自己的参数传递给被替换的程序的main函数中!

1.2.6 execvpe函数

这里就不再多讲了,结合execvp函数与execle函数很好理解!!!

1.3 execve函数

[Linux 进程控制(二)] 进程程序替换,Linux,linux,运维,服务器
左边的6个函数底层其实都是封装了右边execve函数,只有execve是系统调用,封装6个是为了满足不同的需求。文章来源地址https://www.toymoban.com/news/detail-839754.html

到了这里,关于[Linux 进程控制(二)] 进程程序替换的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux之进程控制&&进程终止&&进程等待&&进程的程序替换&&替换函数&&实现简易shell

    1.1 fork的使用 我们可以使用man指令来查看一下 子进程会复制父进程的PCB,之间代码共享,数据独有,拥有各自的进程虚拟地址空间。 这里就有一个代码共享,并且子进程是拷贝了父进程的PCB,虽然他们各自拥有自己的进程虚拟地址空间,数据是拷贝过来的,通过页表映射到

    2024年04月17日
    浏览(54)
  • 【探索Linux】—— 强大的命令行工具 P.10(进程的控制——创建、终止、等待、程序替换)

    前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的一些知识,也学习了一些Linux的基本操作,也了解并学习了有关Linux开发工具vim 、gcc/g++ 使用、yum工具以及git 命令行提交代码也相信大家都掌握的不错,上一篇文章我们了解了关于进程的地址空间,今

    2024年02月08日
    浏览(51)
  • 【Linux进程控制】进程创建 | 进程终止 | 进程等待 | 进程替换

    【写在前面】 本文主要学习理解 fork 的返回值、写时拷贝的工作细节、为什么要存在写时拷贝;进程退出码、进程退出的场景及常见的退出方法、对比 man 2 _exit 和 man 3 exit;进程终止、操作系统怎么进行释放资源、池的概念;进程等待的价值、进程等待的方法 wait 和 waitpid

    2023年04月08日
    浏览(36)
  • 【Linux从入门到精通】进程的控制(进程替换)

      本篇文章会对进程替换进行讲解。希望本篇文章会对你有所帮助  文章目录 一、进程替换概念 二、进程替换函数 2、1 execl 2、2 execlp 2、3 execv 2、3 execle 2、4 execve 三、总结 🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️ 👀 专栏:Linux从入门到精通  👀 💥 标题:进程控制💥  ❣

    2024年02月16日
    浏览(42)
  • 【Linux】Linux进程控制 --- 进程创建、终止、等待、替换、shell派生子进程的理解…

    柴犬: 你好啊,屏幕前的大帅哥or大美女,和我一起享受美好的今天叭😃😃😃 1. 在调用fork函数之后, 当执行的程序代码转移到内核中的fork代码后 ,内核需要分配 新的内存块 和 内核数据结构 给子进程, 内核数据结构包括PCB、mm_struct和页表,然后构建起映射关系 ,同时

    2024年01月16日
    浏览(55)
  • [Linux]进程控制详解!!(创建、终止、等待、替换)

            hello,大家好,这里是bang___bang_,在上两篇中我们讲解了进程的概念、状态和进程地址空间,本篇讲解进程的控制!!包含内容有进程创建、进程等待、进程替换、进程终止!! 附上前2篇文章链接: Linux——操作系统进程详解!!(建议收藏细品!!)_bang___ba

    2024年02月15日
    浏览(40)
  • Linux :进程的程序替换

    目录 一、什么是程序替换 1.1程序替换的原理 1.2更改为多进程版本 二、各种exe接口 2.2execlp  ​编辑 2.2execv 2.3execle、execve、execvpe 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种

    2024年04月10日
    浏览(33)
  • 【Linux】进程的程序替换

    目标:为了让子进程帮父进程执行特定的任务 具体做法:1. 让子进程执行父进程的一部分代码 红框中的代码实际上是父进程的代码,在没有执行fork之前代码就有了,在没有创建子进程之前,父进程的代码加载到内存了,子进程被创建出来是没有独立的代码,这个代码是父进

    2024年01月17日
    浏览(43)
  • 【Linux】—— 进程程序替换

    目录 序言 (一)替换原理 1、进程角度——见见猪跑  1️⃣ 认识 execl 函数 2、程序角度——看图理解 (二)替换函数 1、命名理解  2、函数理解 1️⃣execlp 2️⃣execv 3️⃣execvp 4️⃣execle 5️⃣execve 6️⃣execve (三)自制shell 总结 在前面的文章中,我已经详细的讲解了进程

    2024年02月12日
    浏览(62)
  • 【Linux】详解进程程序替换

             用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。 调用exec并不创建新

    2024年04月18日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包