【Linux】程序替换

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️小林爱敲代码
      🛰️博客专栏:✈️Linux之路
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

💖程序替换的概念

目前,我们执行的子进程,都只能让子进程执行父进程的代码。那么我们想要让 子进程去执行一个全新的进程,那么这就是程序替换。 程序替换不会再创建新的进程,仅仅只是修改子进程的代码和数据。

💖程序替换的原理

进程替换的本质就是,替换代码和数据! 我们都知道子进程会继承父进程的代码和数据,而如果子进程的数据发生修改。那么父进程会写实拷贝一份数据,然后把拷贝的数据交给子进程。

在这里插入图片描述

而程序替换的本质,就是替换子进程的代码和数据!随后再更新一下页表的一些数据即可,也就是说,不用去修改PCB!只需要替换页表映射数据和代码的物理内存即可! 把子进程的代码和数据替换成全新进程的代码和数据,那么全新进程从哪里获取呢? 全新进程是从磁盘上获取的!然后把子进程的代码和数据替换成全新进程的即可。

在这里插入图片描述

但是,数据发生修改时进行了写实拷贝。但是子进程和父进程的代码是共享的!如果这样直接替换,那么父进程的代码不也被替换掉 ?所以:在正常情况下,子进程和父进程共用同一段代码!但是如果子进程发生了程序替换。那么父进程的代码同样会写实拷贝一份!因为进程之间是相互独立的。

所以正确的情况应该是如下图这样:

在这里插入图片描述

当然。页表也会进行调整。

💖进程替换函数

我们知道了进程替换是什么,进程替换的原理。那么怎么实际运用到我们的代码上呢?那么我们就需要介绍一些exce替换函数。

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

而我们exce替换函数有七种,其中有一种是系统调用。所以我们这里只介绍六种。

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

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

函数命名规律

这些函数看起来好复杂好难记,其实是有规律的。

  • (list) : 表示参数采用列表
  • v(vector) 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表不是
execlp列表
execle列表不是不是,需要自己组装环境变量
execv数组不是
execvp数组
execve数组不是不是,需要自己组装环境变量

接下来我们依次演示这6个函数。

execl

int execl(const char *path, const char *arg, …)。第一个参数传的是文件所在路径,第二个是一个可变参数列表。根据的在命令行学的命令挨个填。

比如你想程序执行 ls -a -1,你只需要:

execl "文件路径","命令1","命令2" ..... NULL

我们写一个myload程序演示一瞎。

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


int main()
{
  unsigned int pid = fork();
  if(pid == 0)
  {
    //child
    printf("i am child\n");
    execl("/usr/bin/ls","ls","-a","-1",NULL);//execl "文件路径","命令1","命令2" .....NULL
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);//如果execl调用失败,那么子进程直接终止。
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}

然后我们运行一下这个程序。

在这里插入图片描述

我们可以发现子进程并没有打印下列一大串的hhhhhhhhhhhhhhh。因为子进程的代码已经被替换成ls的代码了。

execlp

execlp 和 execl 可以说百分之95的相似度了。知识execlp可以不用带全路径,只要是存在在环境变量中的,就可以直接输入命令找到环境变量。

代码演示:

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


int main()
{
  unsigned int pid = fork();
  if(pid == 0)
  {
    //child
    printf("i am child\n");
    execl("ls","ls","-a","-1",NULL);//execl "环境变量","命令1","命令2" .....NULL
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);//如果execl调用失败,那么子进程直接终止。
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}

结果还是一样

在这里插入图片描述

注意,一定是要是环境变量中的。否则无法找到对应的路径。

execle

这个需要自己组装环境变量,那么我们再写一个myexe.c的程序。

#include<stdio.h>


int main()
{
  //就遍历一下环境变量
  extern char ** environ;
  int i = 0;
  while(environ[i])
  {
    printf("%s\n",environ[i]);
    ++i;
  }

  return 0;
}

这个程序很简单,就是打印一下环境变量。

然后我们运行这个程序

在这里插入图片描述

我们可以发现,它打印了所有的环境变量,那么我们再来修改一下我们的 myload文件。

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


int main()
{
  unsigned int pid = fork();
  if(pid == 0)
  {
    //child
    printf("i am child\n");
    char *env[] = 
    {
      "A = AAAAAAAAAAAAAAAAA",
      "B = BBBBBBBBBBBBBBBBB",
      "C = CCCCCCCCCCCCCCCCC",
      NULL 
    };  
    execle("./myexe","myexe",NULL,env);//传自己组装的环境变量
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}

这时候我们运行myload试试。

在这里插入图片描述

这时候我们的子进程就打印了父进程组装的环境变量。

execv

而v则代表数组,也就是说传一个数组进去。

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


int main()
{
  unsigned int pid = fork();
  if(pid == 0)
  {
    //child
    printf("i am child\n");
    char *avg[] = 
    {
      "ls","-a","-1",NULL 
    };//命令数组

    execv("/usr/bin/ls",avg);//传数组进去

 
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}

execvp

和execlp一样,只不过把列表换成数组。

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


int main()
{
  unsigned int pid = fork();
  if(pid == 0)
  {
    //child
    printf("i am child\n");
    char *avg[] = 
    {
      "ls","-a","-1",NULL 
    };
    execvp("ls",avg);//传一个数组
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}

execve

同理,和execle一样,只不过列表变成了数组传递。

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


int main()
{
  unsigned int pid = fork();
  if(pid == 0)
  {
    //child
    printf("i am child\n");
    char *env[] = 
    {
      "A = AAAAAAAAAAAAAAAAA",
      "B = BBBBBBBBBBBBBBBBB",
      "C = CCCCCCCCCCCCCCCCC",
        NULL 
    };

    char *avg[] = 
    {
      "myexe",NULL 
    };
    execve("/myexe",avg,env);
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}

以内建方式进行运行!

不创建子进程,让父进程自己执行

chdir
{
      "myexe",NULL 
    };
    execve("/myexe",avg,env);
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    printf("hhhhhhhhhhhhhh\n");
    exit(0);
  }
  sleep(1);
  printf("i am father\n");

  return 0;
}
### Linux替换程序启动和停止命令的方法 在 Linux 系统中,替换正在运行的程序并实现其平滑启停是一项常见的需求。以下是几种常见的方式及其具体操作: #### 使用 `kill` 和重新启动 如果目标是完全终止旧版本的程序并启动新版本,则可以通过以下方式完成: 1. **查找进程 ID (PID)** 可以通过组合 `ps` 和 `grep` 来定位特定的 Java 进程[^2]。例如: ```bash ps -ef | grep 'java -jar' ``` 上述命令会显示所有与指定条件匹配的进程。 2. **发送信号终止进程** 找到对应的 PID 后,可以使用 `kill` 命令向该进程发送终止信号。例如: ```bash kill -9 <PID> ``` 3. **启动新的程序实例** 终止旧版程序后,可以直接启动新版程序。假设这是一个基于 JAR 文件的应用程序,则可执行如下命令: ```bash nohup java -jar new-version.jar & ``` --- #### 动态链接库与符号链接 对于某些场景,可能希望避免中断服务而直接替换运行中的二进制文件或依赖项。此时可以采用动态链接库(Dynamic Linking)以及符号链接的技术[^3]。这种方法的核心在于不改变原有路径的前提下更新实际加载的内容。 1. 创建一个新的符号链接指向最新版本的目标文件: ```bash ln -sf /path/to/new-program /original/path/program ``` 2. 如果涉及共享库 (.so),则需确保 LD_LIBRARY_PATH 或者其他环境变量已正确定义以便于加载最新的库版本。 3. 对于部分支持热部署的服务端应用框架而言,它们内部已经实现了类似的机制从而无需额外干预即可自动检测变更后的资源。 --- #### 利用 systemd 实现优雅重启 现代 Linux 发行版通常配备有 systemctl 工具来管理服务生命周期。假如你的应用程序被配置成一个 service 单元的话,那么就可以利用它来进行更安全可靠的切换过程。 编辑对应的服务定义文件 `/etc/systemd/system/myapp.service` ,其中至少包含 ExecStart 参数指明初始执行脚本的位置;之后每当修改完毕只需简单调用两步指令即能达成目的: ```bash systemctl daemon-reload # 更新系统守护进程中关于此单元的信息 systemctl restart myapp # 请求立即关闭现有实例然后再开启另一个全新副本 ``` 以上步骤不仅简化了手动处理流程而且还能保证日志记录完整性和错误恢复能力等方面的优势。 --- ### 总结 综上所述,在不同条件下可以选择适合自己的策略去解决如何更换处于活动状态下的软件包问题——无论是简单的先杀掉再唤起还是复杂的无缝迁移方案都各有优劣之处取决于具体的业务背景和技术栈特性等因素影响最终决策方向。
评论 48
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林 子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值