Linux-程序替换

本文详细介绍了Linux系统中的进程控制,特别是execl接口的使用,包括如何实现程序替换、接口的工作原理、多进程版本的替换以及不同exec接口的区别。同时探讨了环境变量在进程替换中的继承问题和编程实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

🌎进程控制【下】


文章目录:

进程控制

    execl接口介绍

    多进程版本程序替换

    其他exec接口

      接口介绍
      替换本地程序

    总结


前言:

  在Linux系统中,进程程序替换是一种重要的操作,通过进程程序替换,程序可以更新自己的代码和数据,让进程富有动态性和灵活性,话不多说,开始今天的话题。

在这里插入图片描述


🚀execl接口介绍

  我们的程序只能执行该程序自己的代码,这是众所周知的,但是今天,我想要创建一个子进程来执行别的文件的代码是否可行呢?

在这里插入图片描述
  在Linux下是可实现的,因为Linux给我们提供了对应的接口:

在这里插入图片描述
  这些接口支持我们程序在运行的过程中进行程序替换,从而执行到自己想执行的程序。

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

path :表示带路径文件名的字符串,从而搜索到对应的文件
arg, ...:表示可变参数列表,参数不确定,可传入一个或多个
最后必须以NULL结尾

  首先第一个接口,以下面代码来理解:

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());
    printf("exec begin...\n");

    execl("/usr/bin/ls", "ls", "-a", "-l", NULL); //程序替换,可变参数

    printf("exec end  ...\n");
    return 0;
}

在这里插入图片描述
  能够清晰观察到,在begin之后,程序被替换为了ls 指令,并且选项为 -al,执行程序,发现运行成功了,但是仔细观察之后,在execl之后的printf并没有起作用。

  结论1

程序在执行完exec* 的接口之后,是不会再执行后续的代码了,因为后续代码已经被替换

  从man手册里有exec* 接口返回值的描述:

在这里插入图片描述
  结论2

exec* 只有失败有返回值,为-1。成功就是成功替换了,所以没返回值

  替换完成后是属于创建了新的进程还是旧的进程不变呢?我们不妨做个测试:

#include<stdio.h>
#include<stdlib.h> 
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());
    printf("exec begin...\n");
    sleep(5);

    execl("/usr/bin/top", "top", NULL); 

    printf("exec end  ...\n");
    return 0;
}

在这里插入图片描述
  虽然在替换之后进程的名字变了,但是前后两次的pid并没有变化。

  结论3

进程替换并不会创建新的程序,依旧是原来进程的pid


🚀多进程版本程序替换

  通过之前的学习,我们知道进程之间相互独立,那么我们就可以创建一个子进程,让其来执行程序替换,而父进程回收结果:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());

    pid_t id = fork();
    if(id == 0)
    {
        sleep(3);
        printf("exec begin...\n");
        execl("usr/bin/ls", "ls", "-a", "-l", NULL);
        printf("exec end  ...\n");
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
    if(rid > 0)
    {
        printf("wait success\n");
    }
    
    exit(1);
}

  运行之后,就可以让子进程执行程序替换,并且父进程回收子进程的资源。

在这里插入图片描述
  我们来思考一个问题:程序替换为什么对父进程没有影响?这是因为,进程具有独立性,在程序替换的时候发生写时拷贝


🚀其他exec接口

✈️接口介绍

  我们通过man手册查询exec*接口,发现不止一个接口,还有六个接口:

在这里插入图片描述
在这里插入图片描述
  我们需要了解这七个接口的含义以及用法,但是在这里我不会全部一一列举,因为有些接口是类似的,这些类似的接口我只需要说一个就够了。

  首先,这些接口中带有 ‘p’ 字符的接口都有 path 这个参数,实际上这个参数的意义是:

PATH: 并不需要告诉系统程序的具体位置,只需要告诉系统程序的名称,系统在进行替换的时候,会自动在PATH环境变量中去查找。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());

    pid_t id = fork();
    if(id == 0)
    {
        sleep(3);
        printf("exec begin...\n");
        execlp("ls", "ls", "-a", "-l", NULL);//使用带有'p'的接口
        printf("exec end  ...\n");
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
    if(rid > 0)
    {
        printf("wait success\n");
    }
    
    exit(1);
}

在这里插入图片描述

  使用带 ‘p’ 字符的接口,就不需要带替换程序的路径了,只需要替换程序的名字,在OS中会 依照PATH环境变量来寻找该程序

 下面就是带有 ‘v’ 字符的接口,实际上这个v 在参数里表示的是 const char* argv[],我们在main函数里面是见过的,也就是 命令行参数表

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());

    pid_t id = fork();
    if(id == 0)
    {
        char *const argv[] = {
            (char*)"ls",
            (char*)"-a",
            (char*)"-l"
        };
        sleep(3);
        printf("exec begin...\n");
        execv("/usr/bin/ls", argv);//带有 'v' 字符的接口
        printf("exec end  ...\n");
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
    if(rid > 0)
    {
        printf("wait success\n");
    }
    
    exit(1);
}

在这里插入图片描述

 最开始我们也见过带有 ‘l’ 字符的接口,它表示的是 list,也就是列表,把需要执行的命令和参数全部放在接口内。

  那么带 ‘vp’ 的其实就是传 程序名,以及参数列表即可:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());

    pid_t id = fork();
    if(id == 0)
    {
        char *const argv[] = {
            (char*)"ls",
            (char*)"-a",
            (char*)"-l"
        };
        sleep(3);
        printf("exec begin...\n");
        execvp("ls", argv);//带 'vp' 的接口
        printf("exec end  ...\n");
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
    if(rid > 0)
    {
        printf("wait success\n");
    }
    
    exit(1);
}

在这里插入图片描述


✈️替换本地程序

  我们前面的程序替换全部都是使用系统提供好的程序,我们使用自己写的程序该当何如?

#include<stdio.h>
#include<stdlib.h>

int main(int argc, const char* argv[])
{
    for(int i = 0; argv[i]; ++i)
    {
        printf("argv[%d]:%s\n", i, argv[i]);
    }
    printf("I'm test process!\n");
    printf("I'm test process!\n");
    printf("I'm test process!\n");
    printf("I'm test process!\n");
    printf("I'm test process!\n");
    return 0;
}

  此时我们使用之前学习的make语法已经行不通了,因为无论怎样,只能编译过一个,今天我们来看点别的:

.PHONY:all#由依赖关系无依赖方法
all:myprocess mytest 

mytest:mytest.c
	gcc -o $@ $^ -g -std=c99 
mybin:mybin.c
	gcc -o $@ $^ -g -std=c99

.PHONY:clean
clean:
	rm -f mybin mytest 

  在需要生成多个文件之前使用 .PHONY加上依赖关系,但是不需要依赖方法,这样就能 根据依赖关系 从前到后依次 生成可执行文件

  那么现在我mybin.c文件的子进程要替换 mytest 程序,我们可以这么写:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    printf("I am a process, pid: %d\n", getpid());

    pid_t id = fork();
    if(id == 0){ //child process
        sleep(1);
        printf("exec begin...\n");
        execl("./mytest", "./mytest", "-a", "-b", "-c", NULL);//切换自己写的程序
        printf("exec end  ...\n");
        exit(1);
    }

    pid_t rid = waitpid(id, NULL, 0);
    if(rid > 0)
    {
        printf("wait success\n");
    }
    
    exit(1);
}

在这里插入图片描述

  当然,这里我是用C语言调用C语言程序,但是我们可以调用其他语言吗?答案是 可以调用 其他语言写的程序。

  这是因为:不论什么语言,运行之后都是进程,只要是进程就都能在Linux下运行

  我们修改test文件,让其打印系统环境变量表:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, const char* argv[], const char
{
    for(int i = 0; env[i]; ++i)
    {
        printf("env[%d]:%s\n", i, env[i]);
    }
    
    return 0;
}

  此时再使用程序替换,让子进程执行这段代码,父进程等待子进程资源回收:

在这里插入图片描述

  我们也可以使用系统变量 environ,来获取环境变量:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
    extern char **environ;
    for(int i = 0; environ[i]; ++i)
    {
        printf("env[%d]:%s\n", i, environ[i]);
    }
    
    return 0;
}

在这里插入图片描述

  我们使用 mybin 文件来执行程序的:

在这里插入图片描述
  在mybin.c 中,我们并没有传递环境变量表给子进程,但是子进程却能默认拿到环境变量表?

  实际上,子进程会默认拿到父进程环境变量表,那么mybin 也是子进程,是bash的子进程,所以mybin能拿到bash的环境变量,而mybin的子进程可以拿到父进程环境变量:

  我们导入一个新环境变量在系统里以供猜想:

export VAL=youcanseeme

在这里插入图片描述

  我们在进程地址空间那一节说过,进程地址空间内在 栈的上方存储命令行参数以及环境变量的地方

在这里插入图片描述

  而在本文的最开始,我们也说了,进程替换替换的仅仅是进程的代码和数据,环境变量是不变的

  如果我们想单纯新增环境变量呢?我们可以使用 putenv:

在这里插入图片描述

在这里插入图片描述

  此时我在程序内写入了mytest环境变量,但是当我们在系统中查询时:

在这里插入图片描述

  此时并没有在系统中出现,但是当我们运行程序之后:

在这里插入图片描述

  此时进程内就多了一项mytest的环境变量,而这个环境变量的导入位置是mytest 父进程传给子进程的环境变量,而mytest的父进程是bash,也就是说,在这里bash将从 0-24号环境变量传给了进程mytest,而mytest 使用了putenv新增了环境变量给子进程。

  而现在我想 设置全新的环境变量给子进程,这个时候我们就需要用到带有 ‘e’ 字符的接口了

 接口中还存在带 ‘e’ 字符的接口,e表示的就是 env:const char* env[], 也就需要 环境变量表

在这里插入图片描述

  其实这是以 覆盖 的方式来传递环境变量,也就相当于子进程设置了全新的环境变量了。

在这里插入图片描述

  我在最前面总共列举了七个接口,一个程序替换为什么会有这么多的接口呢?但他们的功能都是进行程序替换,所以他们在功能上没有区别。

  他们仅仅是在传参上有区别,其实我们 程序替换的系统调用只有一个,就是 execve 接口,剩下的六个全部都是由这个接口进行封装的。

在这里插入图片描述


📒✏️总结

  •  进程不仅仅只能运行自己的程序,和可以运行其他程序,使用 exec* 的接口 就可以做到,被称为 程序替换
  •  exec* 接口有七个,他们的 功能全部相同,仅仅是 使用参数不同
  •  子进程会 默认 继承父进程的环境变量表,并 不需要父进程显示传给子进程
  •  程序替换 不看 是什么 语言 的程序,因为 在Linux下运行起来都是进程
  •  七个接口只有 execve 是系统调用,其他6个全是由此接口进行封装。

在这里插入图片描述

  创作不易,还望三联支持博主呀~~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Swashbuckler-Xiong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值