进程的程序替换(exec函数)【Linux】

简介: 进程的程序替换(exec函数)【Linux】


程序替换的原理

  进程的程序替换就是让子进程执行新程序, 执行全新的代码和数据,不再和父进程有瓜葛。

替换原理

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

  • 也就是说程序替换的作用是为了完成一些特定的任务,需要去执行其他的程序。
  • 使用exec系列函数可以达到程序替换的目的。
  • 程序替换会重新创建虚拟地址,页表,然后将磁盘中的文件加载进物理内存。
  • 程序替换会自动释放原来的进程虚拟内存和页表。
  • 程序替换时, 虚拟地址空间和页表的映射会变化,PCB里的优先级等信息不变。

exec系列函数

  以下函数的参数末尾都一定需要带上一个==“NULL”==。

#include <unistd.h>
// path是要替换的程序文件路径+文件名,main函数的argv参数中是什么,这里就怎么写。
int execl(const char *path, const char *arg, ...);
// 会自动到环境变量PATH中根据file寻找新程序的文件,所以file可以直接写成文件名即可。
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[]);
int execve(const char *path, char *const argv[], char *const envp[]);

函数理解

  • 这些函数执行成功的话,则加载进新的程序,启动新程序并且从新程序的的开头开始执行,不再返回。
  • 如果执行成功,原程序中的后续代码将不再执行,因为eip(程序计数器),已经改变到新程序处。
  • 如果调用出错则返回-1.
  • 所以exec只有出错的返回值没有成功的返回。毕竟成功的返回值是无意义的。
  • 除了系统文件,也可以替换成自定义文件,如: .sh,python,java都可以调用。

命令理解(助记)

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

关于程序替换中环境变量的解释

  1. 当进行程序替换时,子进程对应的原始环境变量是从父进程中来的,bash拥有最原始的环境变量,后代进程可在其基础上添加并继承自己的父进程的环境变量。
  2. 环境变量被继承是一种默认行为,不受程序替换的影响。(程序替换只替换代码和数据,不替换环境变量)因为创建子进程都以父进程为模板,父子进程的环境变量指向同一块物理地址,所以一样。
  3. 如何让子进程获得环境变量。
  • 将父进程的环境变量原封不动的传递给子进程(1. 直接用。 2. 直接传)。
  • 想传递自己的环境变量,可以直接构建环境变量表,给子进程传递(是覆盖原来环境变量式的传递)
  • 新增传递(可以利用putenv()函数)。

exec函数之间的关系

  事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve, man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

下图exec函数族 一个完整的例子:

exec函数的使用

execl

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

// test.cpp:
#include <iostream>
#include <unistd.h>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        // child
        execl("/root/Y23_12_28/t", "t", "-start", NULL);
    }
    return 0;
}
#include <stdio.h>
int main(int argc, char *argv[])
{
    printf("%s %s\n", argv[0], argv[1]);
    printf("我是t.cpp进程\n");
    return 0;
}

execlp

int execlp(const char *file, const char *arg, ...);

此时我们打印环境变量PATH:

#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main(int argc, char *argv[])
{
    printf("%s", getenv("PATH"));
    return 0;
}

打印当前的文件路径cwd:

  对比会发现PATH中没有cwd,若是直接使用execlp的话一定会报错。我们来试一下:

#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        // child
        int ret = execlp("t", "t", "-start", NULL);
        if (ret < 0)
        {
            printf("execpl错误\n");
        }
    }
    return 0;
}

  果然报错了,原因是PATH中没有cwd,无法自动找到文件位置。所以我们需要添加环境变量,使用putenv()函数,使用这个函数需要注意的是,会将原来的PATH内容全部清除,然后再添加新的PATH。

//test.cpp:
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        char *old_PATH = getenv("PATH");
        char new_PATH[] = ":/root/Y23_12_28";
        char new1[1000];
        sprintf(new1, "PATH=%s%s", old_PATH, new_PATH);
        putenv(new1);
        // child
        int ret = execlp("t", "t", "-start", NULL);
        if (ret < 0)
        {
            printf("execpl错误\n");
        }
    }
    return 0;
}
//t.cpp:
#include <stdio.h>
#include <cstdlib>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
    printf("%s %s\n", argv[0], argv[1]);
    printf("我是t.cpp进程\n");
    return 0;
}

  这里有一个需要注意的地方,就是合并新老PATH时,一定不要忘记PATH=%s%sPATH=

execle

int execle(const char *path, const char *arg, ...,char *const envp[]);

  多出来的这个e表明了可以自己设定环境变量:

// test.cpp
#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        char *const envp[] = {"PATH=/root/Y23_12_28", "NULL"};
        // child
        int ret = execle("t", "t", "-start", NULL);
        if (ret < 0)
        {
            printf("execpl错误\n");
        }
    }
    return 0;
}

execv

int execv(const char *path, char *const argv[]);

  v表示使用数组来装载命令, 而不是vector;

#include <stdio.h>
#include <unistd.h>
#include <cstdlib>
int main()
{
    pid_t fd = fork();
    if (fd == 0)
    {
        // child
        char *arg[3] = {"t", "-start", "NULL"};
        int ret = execv("/root/Y23_12_28/t", arg);
        if (ret < 0)
        {
            printf("execpl错误\n");
        }
    }
    return 0;
}

  至于execvp和execve在这里就不过多赘述了。


    😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

相关文章
|
3天前
|
NoSQL Linux 程序员
【linux进程信号(一)】信号的概念以及产生信号的方式
【linux进程信号(一)】信号的概念以及产生信号的方式
|
3天前
|
Linux
【linux进程间通信(一)】匿名管道和命名管道
【linux进程间通信(一)】匿名管道和命名管道
|
3天前
|
Java Shell Linux
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
【linux进程控制(三)】进程程序替换--如何自己实现一个bash解释器?
|
3天前
|
算法 Linux Shell
【linux进程(二)】如何创建子进程?--fork函数深度剖析
【linux进程(二)】如何创建子进程?--fork函数深度剖析
|
3天前
|
存储 Linux Shell
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
【linux进程(一)】深入理解进程概念--什么是进程?PCB的底层是什么?
|
4天前
|
消息中间件 Unix Linux
Linux的学习之路:17、进程间通信(1)
Linux的学习之路:17、进程间通信(1)
20 1
|
5天前
|
存储 安全 Linux
Linux的学习之路:9、冯诺依曼与进程(1)
Linux的学习之路:9、冯诺依曼与进程(1)
18 0
|
Linux
Linux系统调用二、open()函数与close()函数介绍
Linux系统调用二、open()函数与close()函数介绍
290 0
Linux系统调用二、open()函数与close()函数介绍
|
Linux C++ Unix
|
6天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。