Linux笔记---进程:进程替换

1. 进程替换的概念

进程替换是指在一个正在运行的进程中,用一个新的程序替换当前进程的代码和数据,使得进程开始执行新的程序,而不是原来的程序。

这种技术通常用于在不创建新进程的情况下,改变进程的行为。

我们之前谈到过fork函数,这个函数可以启动一个子进程,子进程继承了父进程的代码和数据。

在谈到进程替换之前,我们只能通过判断fork函数的返回值id来区分父子进程,并让二者运行不同的分支。而利用进程替换技术,我们可以将子进程的代码数据完全替换为另一个程序,实现我们所期望的,父子进程完全独立为两个不同的进程。

进程替换的原理

进程替换的原理涉及到操作系统的内存管理和进程控制。当一个进程调用exec系列函数时,操作系统会将新程序的代码和数据加载到内存中,并将其与当前进程的地址空间相关联。这个过程通常涉及到以下几个步骤:

  1. 加载新程序:操作系统将新程序的可执行文件从磁盘加载到内存中。
  2. 替换代码和数据:新程序的代码和数据会替换当前进程的代码和数据段。
  3. 更新进程状态:进程的状态会被更新,以反映新程序的执行状态。
  4. 执行新程序:进程开始执行新程序的入口点,通常是main函数。

在这个过程中,进程的标识符(PID)和其他一些属性(如打开的文件描述符、环境变量等)通常会保持不变。

2. exec进程替换函数

在Linux系统中,进程替换通常通过exec系列函数来实现,该系列函数包含在头文件<unistd.h>。

这些函数包括:

  • execl :执行一个新程序,参数以列表形式给出。

    cpp 复制代码
    int execl(const char *pathname, const char *arg, ...);
  • execlp :执行一个新程序,参数以列表形式给出,并在环境变量PATH中搜索程序。

    cpp 复制代码
    int execlp(const char *file, const char *arg, ...);
  • execle :执行一个新程序,参数以列表形式给出,并提供自定义的环境变量。

    cpp 复制代码
    int execle(const char *pathname, const char *arg, ...);
  • execv :执行一个新程序,参数以数组形式给出。

    cpp 复制代码
    int execv(const char *pathname, char *const argv[]);
  • execvp :执行一个新程序,参数以数组形式给出,并在环境变量PATH中搜索程序。

    cpp 复制代码
    int execvp(const char *file, char *const argv[]);
  • execve :执行一个新程序,参数以数组形式给出,并提供自定义的环境变量。

    cpp 复制代码
    int execve(const char *pathname, char *const argv[], char *const envp[]);
  • execvpe :执行一个新程序,参数以数组形式给出,并提供自定义的环境变量。

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

这些函数的使用方式和参数传递方式略有不同,但它们的基本功能都是相同的:用新程序替换当前进程的代码和数据。

记忆技巧:

  • l(list):表示参数采用列表。
  • v(vector):参数用数组。
  • p(path):到环境变量PATH中搜索指定程序,无需完整路径(带p的函数第一个参数为file,代表可执行程序;不带p的函数第一个参数为pathname,代表完整路径)。
  • e(env) : 表示自定义环境变量,不带e的表示继承当前的环境变量。

使用示例:

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

int main()
{
    char* vector[] = {"ls", "-l", "-a", NULL};
    int id = fork();
    if(id == 0)
    {
        execvp("ls", vector);
        return 0;
    }
    int pid = wait(NULL);
    return 0;
}

注意:传入的参数为命令行参数,也就是说在命令行要执行该程序需要输入什么,参数就传递什么,主要是不要忘记选项是从第二个参数开始的。

第一个参数传什么都不要紧,随你喜欢,但要记得传:

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

int main()
{
    char* vector[] = {"cxk", "-l", "-a", NULL};
    int id = fork();
    if(id == 0)
    {
        execvp("ls", vector);
        return 0;
    }
    int pid = wait(NULL);
    return 0;
}

execve函数

该函数相比于其他函数具有一定的特殊性,他是上述函数中唯一一个系统调用。

在命令行输入[man exec]能查到如下信息,可以看到并没有execve的存在,且这些函数都在3号手册当中:

只有单独查询execve函数时才能查到,可以看到该函数在2号手册(系统调用接口)中:

execve函数的特殊性:

  1. 系统调用层级的基础地位
    • execveexec函数族中具有特殊的基础性地位。它是直接与系统调用接口紧密相连的函数。其他的exec系列函数(如execlexecv等)在很多情况下最终可能会调用execve来实现实际的进程替换操作。
    • 例如,在一些库函数的实现中,为了提供更方便的参数传递方式(如execl的可变参数列表形式),可能会在内部对参数进行处理后调用execve来完成进程替换的核心功能。
  2. 参数处理方式的不同
    • execve的参数包含要执行的程序文件路径、传递给新程序的命令行参数数组以及环境变量数组。这种参数形式与其他exec函数有所不同。
    • execl函数,它的参数是以可变参数列表的形式,最后以NULL结尾,这种形式在使用上有一定的便利性,但在底层实现中可能需要更多的转换工作才能与系统调用接口对接,而execve的参数形式更直接地反映了系统调用的需求。
  3. 安全和权限方面的考虑
    • 由于execve是直接进行进程替换的底层函数,在安全和权限管理方面有着重要的作用。它对可执行文件的路径、执行权限等有着严格的要求。
    • 当调用execve时,系统会根据文件的权限设置(如是否可执行、所属用户和组等)以及当前进程的权限来判断是否允许进程替换操作。这种严格的权限检查有助于保障系统的安全性。
  4. 与内核交互的特点
    • execve在与内核交互时,需要将新程序的代码和数据加载到当前进程的地址空间,同时更新进程的各种状态信息,如程序计数器、堆栈指针等。这个过程涉及到内核中的进程管理、内存管理等多个模块的协同工作。
    • 相比其他exec函数,execve在与内核的这种深度交互方面更为直接,因为其他函数可能会在调用execve之前进行一些额外的参数处理或环境设置。

exec函数族调用关系如下:

相关推荐
liebe1*110 分钟前
第七章 防火墙地址转换
运维·服务器·网络
好好学操作系统15 分钟前
autodl 保存 数据 跨区
linux·运维·服务器
dbitc18 分钟前
WIN11把WSL2移动安装目录
linux·运维·ubuntu·wsl
KingRumn18 分钟前
Linux同步机制之信号量
linux·服务器·网络
嵌入式学习菌18 分钟前
SPIFFS文件系统
服务器·物联网
旺仔Sec19 分钟前
2026年度河北省职业院校技能竞赛“Web技术”(高职组)赛项竞赛任务
运维·服务器·前端
BullSmall39 分钟前
linux 根据端口查看进程
linux·运维·服务器
herinspace43 分钟前
管家婆软件年结存后快马商城操作注意事项
服务器·数据库·windows
程序员陆业聪1 小时前
将相和:一场战国时期的职场生存智慧
笔记
_F_y1 小时前
Linux:进程间通信
linux