首页 > 系统相关 >【Linux】:程序替换

【Linux】:程序替换

时间:2024-07-09 11:30:31浏览次数:22  
标签:程序 pid Linux 进程 rid include 替换

 朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux程序替换的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 程序替换

2. 单进程的程序替换

2.1 单进程程序替换的原理

3. 多进程的程序替换

3.1 多进程程序替换原理

4. 剩余接口的介绍以及使用 

4.1 execl函数介绍

4.2 execlp函数介绍

4.3 execv函数介绍 

4.4 execvp函数介绍

4.5 替换别的程序 

5. 环境变量和程序替换

5.1 从bash开始继承 

5.2 从父进程开始继承 

5.3 环境变量默认继承

5.4 execle函数介绍 

5.5 execve系统调用 


1. 程序替换

我们创建的所有子进程,执行的代码都是父进程代码的一部分!那么如果我们想让子进程执行新的程序呢?执行全新的代码和访问全新的数据,不再和父进程有所瓜葛,那么就需要用到程序替换。

这么多程序替换的函数调用,到底该怎么样用呢?我们直接开始使用:

2. 单进程的程序替换

为了便于理解,我们先从单进程的程序替换开始:

int execl(const char *path, const char *arg, ...);

先来看最简单的程序替换接口,其中里面的三个点表示的意思就是可变参数,使用的方法和我们的printf函数中的可变参数一样;

我们程序要能运行起来,第一步先要找到这个程序,第二部如何运行。

  • const char* path表示:新程序的文件路径 + 文件名;
  • const char *arg表示:这个程序怎么运行(简单的说我们在命令行怎么输就怎么写,最终以NULL结尾)。

返回值:成功没有返回值,失败返回-1。

我们先来演示一遍:让ls命令替换掉我们写的可执行

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("pid: %d, exec command begin\n", getpid());
    sleep(3);
    // 程序替换
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    printf("pid: %d, exec command begin\n", getpid());

    return 0;
}

通过结果可以发现,我们通过execl成功完成了程序替换,但是代码中的最后一行代码为什么没有打印出来呢?这个到后续再解释!

2.1 单进程程序替换的原理

我们已经完成了单进程的程序替换,那么它的原理是什么呢?

当一个进程运行起来时,OS会为它创建PCB、虚拟地址空间、页表,将它的代码、数据加载到物理内存中,并且然后通过页表建立起虚拟到物理的映射关系;当进行程序替换时,execl就将要替换的程序代码和数据直接在物理内存中替换覆盖掉,此时原来的代码和数据就会被新替换的代码和数据覆盖掉,继续向后执行也就执行的新程序的代码和数据,这个过程中并没有创建新的进程。

3. 多进程的程序替换

见识过单进程的程序替换以及程序替换的原理,接下来替换一下多进程:

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(3);
        // 程序替换
        execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    // parent
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
    {
        printf("wait success, rid: %d\n", rid);
    }

    return 0;
}

3.1 多进程程序替换原理

首先,进程具有独立性,其次我们都知道,父子进程代码共享,数据以写时拷贝的方式各自私有一份,那么在程序替换的时候,把子进程的数据替换这没问题,因为父子数据各自私有,但是将代码替换,这就有点不能忍了,父子进程代码共享,既然子进程都执行的是替换后的代码,为什么父进程在程序替换的时候还是继续执行他自己代码呢?

是因为在多进程的程序替换时,当子进程启动程序替换覆盖它们的共享代码时,也会发生写时拷贝的方式,重新开辟一块空间,然后进行替换写入,这样父子进程的代码就不共享,子进程继续执行它替换后的代码,父进程继续执行自己的代码。

在替换完之后,子进程怎么知道从新程序的哪里开始执行呢?

我们的代码在编译形成可执行程序的时候会有一个程序入口地址--entry地址;

CPU内部的eip寄存器可以记录我们程序当前运行到哪里了,所以当程序替换之后,新程序的eip程序计数器就被修改为entry地址,所以就可以重新开始运行了。 

接下来解决上面遗留的问题: 代码中的最后一行代码为什么没有打印出来呢?

是因为在程序替换以后,代码和数据都被覆盖了,所以execl函数替换成功后,后续的代码没有机会执行了,因为已经被替换掉了!

所以我们不用判断它的返回值,如果继续执行了后续代码,说明程序替换出错了。

4. 剩余接口的介绍以及使用 

要完成程序替换需要满足以下两点:

  • 1. 必须先要找到需要替换的程序。
  • 2. 必须告诉替换函数该怎么执行。

这些程序替换的函数前面的单词都相同,主要是后缀不同,代表的意思也不同。

4.1 execl函数介绍

execl中的l表示列表的意思,在我们传递使用方法的时候,就像一个列表一样,最后以NULL结尾。也就类似于一个list链表一样。

4.2 execlp函数介绍

execlp中的p表示要找的目标可执行程序会自动在环境变量PATH中根据file去查找。

execlp("ls", "ls", "-a", "-l", NULL);
// 两个ls表示的含义不一样
// 第一个表示要执行的文件
// 第二个表示如何执行

4.3 execv函数介绍 

execv中v表示的是数组传参的意思,可以将如何执行保存一个数组中,然后传递该数组即可。

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

int main()
{

    pid_t id = fork();
    if(id == 0)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(3);

        // 程序替换
        char *const argv[] = {
           "ls",
           "-a",
           "-l",
           NULL
        };
        execv("/usr/bin/ls", argv);
        printf("pid: %d, exec command begin\n", getpid());
    }
    // parent
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
    {
        printf("wait success, rid: %d\n", rid);
    }

    return 0;
}

4.4 execvp函数介绍

execvp就是execv和execlp和结合,可以直接传递文件名,并且执行方式以数组传参的方式传递。

        char *const argv[] = {
           "ls",
           "-a",
           "-l",
           NULL
        };
        execvp("ls", argv);
        //execvp("argv[0]", argv);

4.5 替换别的程序 

程序替换不仅可以替换系统的命令,还可以替换我们自己写的任何程序:

用C++程序来替换C语言程序

#include <iostream>

int main()
{
    std::cout << "hello C++" << std::endl;
    std::cout << "hello C++" << std::endl;
    std::cout << "hello C++" << std::endl;
    std::cout << "hello C++" << std::endl;
    return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{

    pid_t id = fork();
    if(id == 0)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(3);
        // 程序替换
        execl("./test", "test", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    // parent
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
    {
        printf("wait success, rid: %d\n", rid);
    }

    return 0;
}

我们在命令行执行可执行程序时用的是./exe,那么为什么在传参时只传递exe,因为./表示的意思是先找到该程序,在运行,execl第一个参数已经找到了,所以只需要传递exe即可。

5. 环境变量和程序替换

在环境变量章节说过环境变量具有全局属性,那么当我们进行程序替换的时候,子进程对应的环境变量是可以直接从父进程中来的,父进程的环境变量从bash中来,我们可以通过代码来验证一下:

5.1 从bash开始继承 

同样的我们还是使用程序替换的方式,来看看子进程替换之后从bash继承下来的环境变量。

先在命令行自定义一些环境变量:

export MYENV1=1111111111111
export MYENV2=2222222222222

源test.cc

#include <iostream>

int main(int argc, char *argv[], char *env[])
{
    int i = 0;
    for(; env[i]; i++)
    {
        std::cout << env[i] << std::endl;  // 打印环境变量
    }
    return 0;
}

源myprocess.c

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execl("./test", "test", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
        printf("wait success, rid: %d\n", rid);
    return 0;
}

可以看到父进程继承了bash的环境变量,子进程继承了父进程的环境变量。

5.2 从父进程开始继承 

在程序中导入环境变量的接口为:putenv();

#include <stdlib.h>
int putenv(char *string);

在父进程中导入一个环境变量,看看子进程能否继承:

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

int main()
{
    char *env_val = "MYVAL5=5555555555555555555555555";
    putenv(env_val);
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execl("./test", "test", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
        printf("wait success, rid: %d\n", rid);

    return 0;
}

 

5.3 环境变量默认继承

进程地址空间这一章节我们了解到了进程地址空间中有环境变量和命令行参数,在父进程创建子进程的过程中,子进程以父进程为模版,子进程的地址空间也就以父进程的地址空间为模版创建了,进而父进程的环境变量和命令行参数也就都会被继承下来,所以通过地址空间可以让子进程继承父进程的环境变量和数据,所以说这是一种默认的行为。

那么继承下去的环境变量为什么不会收到程序替换的影响呢?环境变量也是数据呀!

在程序替换的过程中,只会替换程序的代码和数据,并不会对环境变量产生影响。

5.4 execle函数介绍 

将父进程的环境变量传递给子进程有两种方式:

1. 直接使用(默认集成,需要传)。

2. 使用execle函数的第三个参数直接传递。

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

extern char ** environ; 

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execle("./test", "test",NULL, environ);  // 直接传
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
        printf("wait success, rid: %d\n", rid);

    return 0;
}

如果要传递我们自己的环境变量,可以先定义,再传递;同样我们也可以把命令行参数传递给子进程,然后通过程序替换来展现出来:

源test.cc

#include <iostream>
#include <unistd.h>

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

源process.c

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

int main()
{
    char *const myenv[] = {
        "MYVAL1=11111111111111",
        "MYVAL2=22222222222222",
        "MYVAL3=33333333333333",
        "MYVAL4=44444444444444",
        NULL};

    pid_t id = fork();
    if (id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execle("./test", "test", "-a", "-b", NULL, myenv); // 直接传
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if (rid == id)
        printf("wait success, rid: %d\n", rid);

    return 0;
}

可以看到传递的环境变量不是新增,而是覆盖是传递!

程序替换可以将命令行参数、环境变量通过自己的参数,传递给被替换程序的mian函数中!

5.5 execve系统调用 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!      

标签:程序,pid,Linux,进程,rid,include,替换
From: https://blog.csdn.net/Yikefore/article/details/140264002

相关文章

  • Linux 设置环境变量
    1.Linux设置环境变量1.1.export命令1.2.直接使用export命令设置环境变量1.3.修改配置文件设置环境变量1.Linux设置环境变量1.1.export命令export命令用于设置或显示环境变量。用法:export[-fn][name[=value]...]或export-p选项:-f: refertos......
  • 基于SpringBoot的酒店订房系统+82159(免费领源码)可做计算机毕业设计JAVA、PHP、爬虫、A
    springboot酒店订房系统摘 要随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,酒店订房系统当然也不能排除在外。酒店订房系统是以实际运用为开发背景,运用软件工程开发方法,采用springboot技术构建的一个管理系统......
  • Linux环境中应急响应与排查溯源思路总结
    0前言在应急响应和溯源时,经常会遇见Linux系统环境,然后小编经常只记得思路忘记部分命令,下面是小编对Linux环境下应急响应和排查的思路总结。本文来源无问社区(wwlib.cn)更多详细内容可前往观看http://www.wwlib.cn/index.php/artread/artid/2729.html1目录文件分析1.1系统用......
  • (八)ADO.NET用窗体应用程序写增删查改——改(1.1升级版)
    在1.0版本中,紧接前面两节“增”、“删”、“查”代码,这里新增“改”功能一、首先编辑好要修改的控件和相关属性,这里“编号”默认只读属性(ReadOnly)二、其次,修改下窗体显示的代码,让数据直接显示出来,这里我们用一个方法封装好,直接在窗体加载事件(Load)中调用即可。privatevoidFo......
  • Android全局替换字体
    一、概述由于业务需要,各端之间统一字体(Android、IOS、PC、网页)。所以android也需要替换成特定的字体。以后有可能还会增加其他的字体。方案:使用LayoutInflaterCompat.setFactory2来全局替换字体。这样做的好处是可以一次性的替换大部分的字体。剩余的个性......
  • 微信小程序 wx.navigateto无法跳转 原因如下:
     wx.navigateto无法跳转报错问题:"wx.navigateto无法跳转"可能的原因和解决方法如下: 错误的使用方式:确保你使用的是wx.navigateTo而不是wx.navigateto,注意大小写。  目标页面不存在或路径错误:检查你要跳转的页面路径是否正确,确保在app.json中已经声明。......
  • 应用程序会不会导致linux内核崩溃?
    应用程序有可能导致Linux内核崩溃,但这种情况并不是绝对的,它取决于多种因素。应用程序或Linux内核本身都可能存在bug。当应用程序的某部分逻辑与内核的某部分逻辑发生冲突时,有可能导致内核崩溃。例如,应用程序可能尝试访问非法的内存地址,或者触发内核中的某个未修复的错误。这......
  • PC的栈linux里能不能设置反过来,让栈从低地址往高地址长呀?
    在Linux系统中,默认情况下,栈(Stack)的生长方向是从高地址向低地址。这是由多种因素决定的,包括操作系统的内存管理策略、处理器的架构(如x86或ARM等)以及编程语言和编译器的设计。将栈分配在较高的内存地址,并且向低地址生长,可以简化内存管理。因为程序通常是从低地址向高地址分配数......
  • 在Winform程序中增加隐藏的按键处理,用于处理一些特殊的界面显示或者系统初始化操作
    以前,我看到一个朋友在对一个系统做初始化的时候,通过一组魔幻般的按键,调出来一个隐藏的系统设置界面,这个界面在常规的菜单或者工具栏是看不到的,因为它是一个后台设置的关键界面,不公开,同时避免常规用户的误操作,它是作为一个超级管理员的入口功能,这个是很不错的思路。其实Winform做这......
  • Linux 基础知识
    Smiling&Weeping----站在这,难免被遗忘    Linux基础命令这一部分我会带着大家了解Linux的一些基础操作,还有使用一些工具。让大家能够在遇到问题的时候,可以自行解决,如果大家有遇到什么问题的话,也可以在这里评论,我会及时给......