Linux操作系统2-进程控制3(进程替换,exec相关函数和系统调用)

上篇文章:Linux操作系统2-进程控制2(进程等待,waitpid系统调用,阻塞与非阻塞等待)-CSDN博客

本篇代码Gitee仓库:Linux操作系统-进程的程序替换学习 · d0f7bb4 · 橘子真甜/linux学习 - Gitee.com

本篇重点:进程替换

目录

[一. 什么是进程替换?](#一. 什么是进程替换?)

[二. 进程替换函数常用的函数](#二. 进程替换函数常用的函数)

[2.1 execl](#2.1 execl)

[a 进程替换覆盖指定位置后面的代码](#a 进程替换覆盖指定位置后面的代码)

[b 进程替换不会影响父进程](#b 进程替换不会影响父进程)

[2.2 execlp](#2.2 execlp)

[2.3 execv/execvp](#2.3 execv/execvp)

[2.4 execle](#2.4 execle)

[2.5 execve 系统调用](#2.5 execve 系统调用)

[三. 进程替换总结](#三. 进程替换总结)

[四. 进程替换可以执行任何后端语言!](#四. 进程替换可以执行任何后端语言!)


一. 什么是进程替换?

我们知道,使用fork函数可以创建子进程。我们创建子进程的目的是什么?

1 让子进程执行父进程的一部分代码(比如执行父进程处于磁盘中的代码)

2 我们希望让子进程执行一个全新的进程,去完成一个不同的功能

我们让子进程去执行新程序的数据和代码 -> 进程的程序替换

二. 进程替换函数常用的函数

常见的函数如下:

cpp 复制代码
#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[]);
int execve(const char *path, char *const argv[], char *const envp[]);

是不是有点眼花缭乱?具体使用如下

2.1 execl

a 进程替换覆盖指定位置后面的代码

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

//path    用于找到程序(程序的路径,ls的路径是 /usr/bin/ls)
//arg     这个命令是怎么执行的(比如ls命令的执行方式就是单独的 ls)
//...     这个命令需要的参数是什么(比如ls可以带参数 -a -l -i 等)

//返回值,失败返回-1,并且设置错误码

我们使用execl去替换一个 ls命令。代码如下:

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

int main()
{
    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("process is running!\n");

    //第一个参数,要执行哪个路径,就填这个路径
    //第二个参数,程序怎么执行,就怎么填
    //后面的参数,执行这个程序需要带的参数,就一个一个地填这些参数
    //最好使用NULL结尾
    int n = execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
    
    //使用系统调用或者涉及底层的C库函数,需要判断错误返回
    perror("execl");


    //由于执行execl之后,代码被覆盖,以下的代码不会被运行!
    printf("process is running!\n");
    printf("process is running!\n");
    printf("process is running!\n");
    printf("process is running!\n");
    printf("process is running!\n");
    return 0;
}

编译运行结果如下:

execl之后的代码没有被执行的原因是:进程替换的时候没有创建新进程,而是将指定的程序和代码加载到指定的位置

这样会覆盖后面的代码,所以后面printf就不会执行了!

b 进程替换不会影响父进程

进程替换会覆盖指定位置后面的代码,不过我们在子进程中使用进程替换不会影响父进程的代码和数据。

这个原因是父子进程之间有写实拷贝,由于父子进程之间的写实拷贝,一旦子进程尝试写入,OS就会给子进程开辟一份空间用于保存子进程的数据和代码。这样一来,子进程进程替换就不会影响父进程。

测试代码:

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

int main()
{
    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        int n = execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);
        perror("execl");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

测试结果:

2.2 execlp

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

p:path,只要输入替换的程序,会自动去环境变量中进行查找。无需告诉路径

测试代码:

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

int main()
{
    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        //p:path,只要输入替换的程序,会自动去环境变量中进行查找。无需告诉路径
        execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);
        perror("execl");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

测试结果:

2.3 execv/execvp

cpp 复制代码
//v:vector:可以将所有的执行参数放入数组,进行统一传入,不用可变参数传参 
int execv(const char *path, char *const argv[]);    


// v:vector p:文件名
int execvp(const char *file, char *const argv[]);  

execv 举例:

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


int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        char * argv_[] = {"ls", "-a", "-l", "--color=auto", NULL}; 
        execv("/usr/bin/ls", argv_);
        
        perror("execv");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

测试结果:

execvp 加上了p:我们只需给出名字,会去环境变量中自动查找

测试代码:

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


int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        char * argv_[] = {"ls", "-a", "-l", "--color=auto", NULL}; 
        execvp("ls", argv_);
        
        perror("execvp");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

测试结果:

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


int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        char * argv_[] = {"ls", "-a", "-l", "--color=auto", NULL}; 
        execvp("ls", argv_);
        
        perror("execvp");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

测试结果

2.4 execle

cpp 复制代码
int execle(const char *path, const char *arg, ...,char *const envp[]);    //e:传入自定义环境变量,

e:传入自定义环境变量

我们定义另一个C程序mybin,让这个程序打印环境变量。然后我们在子进程中替换为mybin

这样我们的子进程就能够打印我们在execle函数中输入的环境变量了

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

int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        //注意在最后加上NULL
        char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
        // 加入命令行参数env
        execle("./mybin", "./mybin", NULL, env_);

        perror("execle");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

运行结果如下:

我们换成系统环境变量

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

int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        //注意在最后加上NULL
        char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
        // 加入命令行参数env
        extern char** environ;
        execle("./mybin", "./mybin", NULL, environ);

        perror("execle");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

运行结果:

当然我们也能够在 命令行参数获取环境变量

2.5 execve 系统调用

上面的exec函数都是对execve系统调用的封装

cpp 复制代码
//2号手册,这个才是真正的系统调用(上面的都是函数封装)
int execve(const char *path, char *const argv[], char *const envp[]);
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        // 注意在最后加上NULL
        char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
        char *const argv_[] = {"./mybin"};
        execve("./mybin", argv_, env_);

        perror("execve");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}

测试结果:

三. 进程替换总结

我们知道程序必须加载到内存中才能执行。这是由冯诺依曼计算机体系结构决定的。

在Linux中就是通过exec*函数来加载程序的!

那么是exec函数先加载还是main函数先执行?

exec函数先加载,然后main函数再执行

我们可以发现exec函数的参数和main函数的参数很像!

所以是exec函数先加载路径,参数,环境变量在执行main函数

四. 进程替换可以执行任何后端语言!

myCppBin.cc

cpp 复制代码
#include <iostream>
using namespace std;

int main()
{
    cout << "Hello C++!" << endl;
    return 0;
}

mytest.c

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

int main()
{

    // 使用execl去替换ls完成  ls -a -l --color=auto
    printf("1 process is running!\n");
    pid_t id = fork();
    if (id == 0)
    {
        // 注意在最后加上NULL
        char *const env_[] = {"AA=11", "BB=22", "CC=33", "YZC=Hello world!", NULL};
        char *const argv_[] = {"./myCppBin"};

        //执行C程序
        //execve("./mybin", argv_, env_);

        //执行C++
        execve("./myCppBin", argv_, env_);


        perror("execve");
        // 如果替换失败
        exit(-1);
    }

    // 由于写实拷贝,父进程不受子进程进程替换影响
    printf("2 process is running!\n");

    int status = 0;
    int subid = waitpid(id, &status, 0);
    if (subid > 0)
    {
        printf("child exit code:%d child exit signal\n", (status >> 8) & 0xff, status & 0x7f);
    }
    return 0;
}
相关推荐
无敌小茶16 分钟前
Linux学习笔记之环境变量
linux·笔记
Harbor Lau29 分钟前
Linux常用中间件命令大全
linux·运维·中间件
漫谈网络1 小时前
基于 Netmiko 的网络设备自动化操作
运维·自动化·netdevops·netmiko
꧁坚持很酷꧂1 小时前
Linux Ubuntu18.04下安装Qt Craeator 5.12.9(图文详解)
linux·运维·qt
凉、介2 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci
电鱼智能的电小鱼2 小时前
EFISH-SBC-RK3588无人机地面基准站项目
linux·网络·嵌入式硬件·机器人·无人机·边缘计算
电鱼智能的电小鱼2 小时前
基于 EFISH-SBC-RK3588 的无人机环境感知与数据采集方案
linux·网络·嵌入式硬件·数码相机·无人机·边缘计算
小诸葛的博客3 小时前
详解Linux中的定时任务管理工具crond
linux·运维·chrome
一默19913 小时前
CentOS 7.9升级OpenSSH到9.9p2
linux·运维·centos
keep intensify3 小时前
Linux常用指令
linux·服务器·php