Linux系统:进程程序替换以及相关exec接口

本节重点

  • 理解进程替换的相关概念与原理
  • 掌握相关程序替换接口
  • 程序替换与进程创建的区别
  • 程序替换的注意事项

一、概念与原理

进程程序替换是操作系统中实现多任务和资源复用的关键机制,允许进程在运行时动态加载并执行新程序。

1.1 定义

进程程序替换是指用新程序的代码、数据和堆栈完全替换当前进程的地址空间(加载新程序到内存、更新页表映射、初始化虚拟地址空间,并将进程控制块(PCB)指向新程序),使进程执行新程序的逻辑,而进程ID(PID)保持不变

1.2 与进程创建的区别

  • 进程创建(fork):创建新进程,并分配PID
  • 进程程序替换:不创建新进程,更改原程序的代码与数据

二、实现方法 exec函数(6+1)

2.1 语言封装(6)

2.1.1 execl

函数原型:

cpp 复制代码
int execl(const char *pathname, const char *arg, ...);

参数:

pathname:新程序的路径+文件名

arg:传递给新程序的参数

代码示例:

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<unistd.h>
int main()
{
    printf("进程替换之前!\n");
    int ret=execl("/bin/ls","ls","-a","-l",NULL);
    //进程替换成功后后续代码不会执行
    //只有进程替换出错后才会执行后续代码,并设置错误码
    (void)ret;
    printf("进程替换之后!\n");
    return 0;
}

运行结果:

2.1.2 execlp

cpp 复制代码
int execlp(const char *file, const char *arg, ...);

参数解析:

file:新程序的程序名

arg:传递给新程序的参数/执行的方法

代码示例:

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

int main()
{
    printf("进程替换之前!:\n");
    int ret=execlp("ls","ls","-a","-l",NULL);
    (void)ret;
    if(ret==-1)
    printf("execlp fail!\n");
    printf("进程替换之后!:\n");
    return 0;
}

运行结果:

2.1.3 execle

cpp 复制代码
int execle(const char *pathname, const char *arg, char *const envp[]);

参数解析:

pathname:新程序的路径+文件名

arg:传递给新程序的参数/执行新程序的方法

envp:传递给新程序的环境变量表(以NULL结尾)

代码示例:

code.cc

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

int main()
{
    printf("进程替换之前!:\n");
    char* const env[]={
        (char* const)"other=12345",
        (char* const)"n1=45612",
        (char* const)"n2=56784",
        (char* const)"n3=12034",
        (char* const)"n4=yuejianhua",
        (char* const)"n5=jinzhiqi",
        NULL,
    };
    int ret=execle("./text","text",NULL,env);
    if(ret==-1)
    printf("execlp fail!\n");
    printf("进程替换之后!:\n");
    return 0;
}

text.cc

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

int main(int argc,char* argv[],const char* env[])
{
    //打印环境变量表:
    int i=0;
    while(env[i])
    {
        std::cout<<env[i]<<std::endl;
        i++;
    }
    i=0;
    //打印命令行参数列表:
     while(argv[i])
    {
        std::cout<<argv[i]<<std::endl;
        i++;
    } 
    return 0;
}

运行结果:

2.1.4 execv

cpp 复制代码
int execv(const char *pathname, char *const argv[]);

参数解析:

pathname:新程序的路径+文件名

argv:传递给新程序的命令行参数列表(以NULL结尾)

代码示例:

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

int main()
{
    printf("进程替换之前!:\n");
    char* const argv[]={
        (char* const)"ls",
        (char* const)"-a",
        (char* const)"-l",
        NULL,
    };
    int ret=execv("/bin/ls",argv);
    if(ret==-1)
    printf("execlp fail!\n");
    printf("进程替换之后!:\n");
    return 0;
}

运行结果:

2.1.5 execvp

cpp 复制代码
int execvp(const char *file, char *const argv[]);

参数解析:

file:新程序的文件名

argv:传递给新程序的命令行参数表(以NULL结尾)

代码示例:

code.cc

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

int main()
{
    printf("进程替换之前!:\n");
    char* const argv[]={
        (char* const)"yuejianhua",
        (char* const)"jinzhiqi",
        NULL,
    };
    int ret=execvp("./text",argv);
    if(ret==-1)
    printf("execlp fail!\n");
    printf("进程替换之后!:\n");
    return 0;
}

text.cc

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

int main(int argc,char* argv[],char* env[])
{
    //打印环境变量表:
    int i=0;
    //打印命令行参数列表:
     while(argv[i])
    {
        std::cout<<argv[i]<<std::endl;
        i++;
    }  
    return 0;
}

运行结果:

2.1.6 execvpe

cpp 复制代码
int execvpe(const char *file, char *const argv[],char *const envp[]);

参数解析:

file:新程序的函数名

argv:传递给新程序的命令行参数列表(以NULL结尾)

envp:传递给新程序的环境变量表(以NULL结尾)

code.cc

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

int main()
{
    printf("进程替换之前!:\n");
    char* const argv[]={
        (char* const)"yuejianhua",
        (char* const)"jinzhiqi",
        NULL,
    };
    char* const env[]={
        (char* const)"other=12345",
        (char* const)"n1=45612",
        (char* const)"n2=56784",
        (char* const)"n3=12034",
        NULL,
    };
    int ret=execvpe("./text",argv,env);
    if(ret==-1)
    printf("execlp fail!\n");
    printf("进程替换之后!:\n");
    return 0;
}

text.cc

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

int main(int argc,char* argv[],char* env[])
{
    //打印环境变量表:
    int i=0;
    while(env[i])
    {
        std::cout<<env[i]<<std::endl;
        i++;
    } 
    i=0;
    //打印命令行参数列表:
     while(argv[i])
    {
        std::cout<<argv[i]<<std::endl;
        i++;
    }  
    return 0;
}

运行结果:

2.2 系统调用(1)

2.2.1 execve

cpp 复制代码
int execve(const char *pathname, char *const argv[],char *const envp[]);

参数解析:

pathname:新程序的路径+文件名

argv:传递给新程序的命令行参数表(以NULL结尾)

envp:传递给新程序的环境变量表(以NULL结尾)

代码示例:

code.cc

cpp 复制代码
#include<cstdio>
#include<iostream>
#include<unistd.h>
int main()
{
    std::cout<<"这是进程替换之前"<<std::endl;
    //传递自己的命令行参数与环境变量表
    char* const argv[]={
        (char* const)"yuejianhua",
        (char* const)"jinzhiqi",
        NULL,
    };
    char* const env[]={
        (char* const)"n1=12345",
        (char* const)"n2=45678",
        (char* const)"n3=lut",
        NULL,
    };
    int ret=execve("./text",argv,env);
    //替换成功后续代码不会执行
    if(ret<0)
    {
        std::cout<<"进程替换失败!"<<std::endl;
    }
    std::cout<<"进程替换之后"<<std::endl;
    return 0;
}

text.cc

cpp 复制代码
#include<cstdio>
#include<iostream>

int main(int argc,char* argv[],char* env[])
{
    int i=0;
    while(argv[i])
    {
        std::cout<<argv[i]<<std::endl;
        i++;
    }
    i=0;
    while(env[i])
    {
        std::cout<<env[i]<<std::endl;
        i++;
    }
    return 0;
}

运行结果:

2.3 总结

函数 参数传递方式 环境变量 路径搜索 示例调用
execl 可变参数列 表 继承 需完整路 径 execl("/bin/ls", "ls", "-l", NULL)
execlp 可变参数列表 继承 支持 PATH execlp("ls", "ls", "-l", NULL)
execle 可变参数列表 显式传递 需完整路径 execle("/bin/ls", "ls", "-l", NULL, envp)
execv 参数数组 继承 需完整路径 char *argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv);
execvp 参数数组 继承 支持 PATH execvp("ls", argv);
execve 参数数组 显式传递 需完整路径 execve("/bin/ls", argv, envp);

知识点:

进程程序替换所关联的exec族函数都有一个显著特征就是exec+参数传递的方式,每个字母代表特定的传参方法,以下是关于这一类型的总结:

  • l (list) :表示给新进程传参需要一个个传
  • p(PATH):表示索引新进程可以只传递文件名,但是要是自己的代码文件必须指明路径
  • v(vector):表示给新进程传参可以直接使用自定义命令行参数列表
  • e(env) :表示可以给新进程传递自定义环境变量表

这里需要注意的是命令行参数列表与环境变量表必须都以NULL结尾。

exec族函数在底层都封装了系统调用execve。

当在进程替换的时候显式地给新进程传递环境变量表时传递的环境变量表会覆盖默认的环境变量表,可以参考execle的代码演示。

三、进程替换的用途

在后期我们可以通过fork+exec机制创建子进程利用进程替换机制使子进程执行定义好的代码文件。

如:Shell执行命令(简易版):

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

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程:替换为 ls 命令
        char *argv[] = {"ls", "-l", NULL};
        execvp("ls", argv); // 自动搜索 PATH
        perror("execvp failed");
        exit(1);
    } else if (pid > 0) {
        // 父进程:等待子进程结束
        wait(NULL);
        printf("Command executed.\n");
    } else {
        perror("fork failed");
    }
    return 0;
}

fork+exec机制使用后要注意资源回收的问题,使用进程等待的方式回收或使用信signal(SIGCHLD, SIG_IGN) 自动回收(需谨慎)。

相关推荐
luckywuxn1 小时前
ant design pro 项目发布遇到登录页访问404
运维·服务器·网络
小小不董1 小时前
Oracle RAC ‘Metrics Global Cache Blocks Lost‘告警解决处理
linux·服务器·数据库·oracle·dba
洒脱的六边形战士加辣2 小时前
玩转Docker(一):基本概念
运维·docker·容器
Tesseract_95272 小时前
【Linux】Linux基础概念
linux
李匠20242 小时前
C++负载均衡远程调用学习之 Dns-Route关系构建
运维·c++·学习·负载均衡
whoarethenext2 小时前
linux的信号量初识
linux·运维·前端·c/c++·信号量
sz66cm2 小时前
Linux基础 -- Generic Netlink 框架详解与开发实践
linux
贰元13 小时前
微信聊天机器人搭建 教程/开发
运维·服务器·前端
自由鬼3 小时前
基于Windows系统分析IT生态建设
linux·windows·生态建设