【Linux】进程程序替换

简介: 【Linux】进程程序替换

一、 初识进程替换

1、为什么要学习进程替换

在前面我们讲过如何创建一个子进程,创建一个子进程能够帮我们父进程完成一些任务,但是前面我们创建的子进程都有一定的缺陷,那就是我们创建的子进程只能执行父进程的部分代码,而不能独立于父进程去执行一个父进程没有的代码,如果我们想要子进程去执行不同于父进程的代码,这时就需要学习进程程序替换了

2、进程程序替换的原理

在学习进程程序替换之前我们先来感受一下进程替换,看下面一段代码:

#include < stdio.h > 
#include < unistd.h > 
#include < stdlib.h > 
#include < sys / wait.h > 
#include < sys / types.h >
  int main() 
   {
       pid_t id = fork();
       if (id < 0) 
       {
           perror("fork() fail:");
           exit(-1);
       } 
       else if (id == 0) 
       {
           printf("我是子进程,我的pid是: %d\n", getpid());
           //进行程序替换
           int n = execl("/bin/ls", "ls", "-a", "-l", NULL);
           printf("我是子进程,我的pid是: %d\n", getpid());
           printf("我是子进程,我的pid是: %d\n", getpid());
           printf("我是子进程,我的pid是: %d\n", getpid());
           printf("我是子进程,我的pid是: %d\n", getpid());
           if (n == -1) 
           {
               printf("进程程序替换失败!\n");
               exit(-1);
           }
       }
       int status = 0;
       pid_t Pid = wait( & status);
       printf("我是父进程,等待子进程成功!子进程的pid是: %d\n", Pid);
       if (WIFEXITED(status)) 
       {
           printf("子进程正常退出,退出码为: %d\n", WEXITSTATUS(status));
       }
       else 
       {
       printf("子进程退出异常,退出信号为: %d\n", status & 0x7F);
     }
  return 0;
}

执行结果:

我们发现,子进程在调用完execl后下面的代码就全变了,变成了去执行ls命令的代码了,也就是说子进程中的代码与数据被磁盘中的文件给替换了,从而让我们子进程执行一个不同于父进程的代码。

好了,看完了现象,我们来看一看进程替换的原理:

替换原理如图所示:

当我们子进程调用了execl后便将磁盘中的另一个程序的代码与数据拷贝给子进程,此时子进程发生写时拷贝,代码与数据都被拷贝至一个新的位置,与父进程彻底分裂。

这里对于进程程序替换有几个要点要好好理解:

  • 程序替换是整体替换不能局部替换,执行程序替换,新的代码和数据就被加载了,execl后续的代码属于老代码,直接被替换了。设机会执行了!
  • 进程的程序替换,并没有创建新的进程!进程程序替换只是将原来进程的代码与数据进行了替换。
  • 进程具有独立性,子进程的程序替换,并不会影响父进程

二、进程程序替换的接口

明白了进程程序替换的原理后我们就要开始学习进程替换的使用了,在Linux中我们使用man execl命令可以看到许多有关进程程序替换的接口。

这里我们可以看到C库中为我们提供了 6 个关于进程程序替换的接口,下面我们就来一 一学习一下!

函数解释

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

命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p,程序就自动搜索环境变量PATH
  • e(env) : 表示使用自己维护环境变量

1、execl函数

函数原型:

int execl(const char *path, const char *arg, ...); //此函数的参数属于可变参数
  • 第一个参数表示要替换的程序的绝对路径
  • 第二个参数表示要执行的程序是谁
  • 第三个参数表示要怎样执行这个程序,(可以不写)
  • 由于是可变参数,所以参数列表最后一个参数一定要写上NULL告诉函数,参数传递完毕

代码示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  printf("我是一个进程,我的pid是: %d\n", getpid());
  int n = execl("/bin/ls", "ls", "-a", "-l", NULL);
  if(n == -1)
  {
    perror("execl() fail:");
    exit(-1);                                                                                                                                        
  }
  return 0;
}

运行结果:

2、execv函数

函数原型:

int execv(const char *path, char *const argv[]);
  • 第一个参数表示要替换的程序的绝对路径
  • 第二个参数是一个指向不能改变的指针数组,包含了要执行的程序是谁以及怎么执行的。
  • 这个指针数组最后一个元素必须指向NULL,方便告诉函数,参数传递完毕

代码演示:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
 {
     char* const argv[]={
       "ls",
       "-a",
      "-l",                                                                                                                                          
        NULL
    };
    printf("我是一个进程,我的pid是: %d\n", getpid());
    int n = execv("/bin/ls", argv);
    if(n == -1)
    {
      perror("execl() fail:");
      exit(-1);
    }
  return 0;
}

运行结果:

3、execlp函数

函数原型:

int execlp(const char *file, const char *arg, ...);
  • 第一个参数表示要执行的程序是谁。
  • 第二个参数及以后表示要怎样执行这个程序,(第三个参数可以不写)
  • 由于是可变参数,所以参数列表最后一个参数一定要写上NULL告诉函数,参数传递完毕

代码示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
  int main()
  {
    printf("我是一个进程,我的pid是: %d\n", getpid());
    int n = execlp("ls", "ls", "-a", "-l", NULL);                                                                                                    
    if(n == -1)
    {
     perror("execl() fail:");
     exit(-1);
    }
    return 0;
  }

运行结果:

4、execvp函数

函数原型:

int execvp(const char *file, char *const argv[]);
  • 第一个参数表示要执行的程序是谁。
  • 第二个参数是一个指向不能改变的指针数组,包含了要执行的程序怎么执行。
  • 这个指针数组最后一个元素必须指向NULL,方便告诉函数,参数传递完毕

代码示例:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
 {
     char* const argv[]={
       "ls",
       "-a",
      "-l",                                                                                                                                          
        NULL
    };
    printf("我是一个进程,我的pid是: %d\n", getpid());
    int n = execvp("ls", argv);
    if(n == -1)
    {
      perror("execl() fail:");
      exit(-1);
    }
  return 0;
}

5、execle函数

函数原型:

int execle(const char *path, const char *arg, ...,char *const envp[]);
  • 第一个参数表示要替换的程序的绝对路径
  • 第二个及以后参数表示要怎样执行这个程序
  • 由于是可变参数,所以参数列表倒数第二个参数一定要写上NULL告诉函数,参数传递完毕
  • 最后一个参数是一个指向不能改变的指针数组,里面记录了自定义的环境变量。

我们来看一看下面的代码来理解这个execle函数:

#include<iostream>
#include<stdlib.h>
using namespace std;
 int main()
 {
    cout << "---------------------------" << endl;
    cout << "这时一个C++的进程,自定义的环境变量是MYNAME:" << endl;
    cout << (getenv("MYNAME") == NULL ? "NULL" : getenv("MYNAME")) << endl;
    cout << getenv("PATH") << endl;
    cout << "---------------------------" << endl;
   return 0;                                                                                                                                          
 }

当我们单独运行此C++编写的程序时,由于没有传递"MYNAME"环境变量,所以,我们只能看到NULL,与PATH对应的环境变量。

当我们运行下面的代码时,为 myproc 程序传递了环境变量 argv,我们就能看到"MYNAME"对应的环境变量,但是环境变量表只能有一个,于是我们就看不到了默认的环境变量了。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
   int main()
   {
      printf("我是一个C进程\n");
      char* const argv[]={
        "MYNAME=you can see me?",                                                                                                                      
          NULL
      };
      int n = execle("./practice/myproc", "myproc", NULL, argv);
      if(n == -1)
      {
        perror("execl() fail:");
        exit(-1);
      }
      return 0;
   }

运行结果:

如果我们即想要默认的环境变量,又想要自定义的环境变量怎么办呢?我们有两种方法,一种是在Linux命令行中使用export命令添加想要添加的环境变量,另一种是调用putenv

  • 利用命令行中的export命令
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
   int main()
   {
      extern char** environ;
      printf("我是一个C进程\n");
      int n = execle("./practice/myproc", "myproc", NULL, environ);
      if(n == -1)
      {
        perror("execl() fail:");
        exit(-1);
      }
      return 0;
   }

  • 调用putenv函数

那个进程调用了该函数就会在那个进程在环境变量表里面添加一个环境变量。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
   int main()
   {
      extern char** environ;
      putenv("MYNAME=you can see me?");
      printf("我是一个C进程\n");
      int n = execle("./practice/myproc", "myproc", NULL, environ);
      if(n == -1)
      {
        perror("execl() fail:");
        exit(-1);
      }
      return 0;
   }

6、总结

讲到这里,对于execvpe函数相信不用我讲,你也能参照前面的函数给出答案了!

但是我们发现上面的exec类中少了execve函数这时怎么回事呢?我们使用man手册查看一下。

execve是函数调用,在2号手册,刚才我们讲的函数是C库函数,3号手册是C语言的库函数,C库函数exec类底层调用的都是exceve系统调用。

三、进程程序替换的补充强调

进程程序替换,我们可以替换任何编程语言写的可执行程序,因为进程程序替换是操作系统提供的系统调用,是系统级别的操作。

下一章我们可以利用进程程序替换制作一个简单的shell程序,加深对于进程程序替换以及shell的运行的理解。

相关文章
|
17天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
51 4
linux进程管理万字详解!!!
|
8天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
48 8
|
5天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
17天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
52 4
|
18天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
19天前
|
消息中间件 存储 Linux
|
26天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
31 1
|
1月前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
【10月更文挑战第9天】本文将深入浅出地介绍Linux系统中的进程管理机制,包括进程的概念、状态、调度以及如何在Linux环境下进行进程控制。我们将通过直观的语言和生动的比喻,让读者轻松掌握这一核心概念。文章不仅适合初学者构建基础,也能帮助有经验的用户加深对进程管理的理解。
25 1
|
1月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
38 1
|
1月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
31 0
Linux c/c++之IPC进程间通信
下一篇
无影云桌面