Linux学习之路 -- 进程篇 -- 进程程序替换

本文主要介绍程序替换原理和相关接口

目录

一、程序替换原理

二、execl相关接口

<1>execl接口

细节一、

细节二、

细节三、

细节四、

<2>execlp

<3>execv

<4>execvp

<5>execle/execve/execvpe

<6>区别


我们的程序一般只能执行我们自己的代码,如果我们创建的子进程想要执行其他程序的代码,就必须通过程序替换的手段来实现。

一、程序替换原理

首先我们先看一张图

进程替换就是把磁盘上另一个程序的代码和数据,加载到原来存放子进程的代码和数据的物理内存上,将原有的子进程在物理内存上的数据和代码覆盖,并且重新建立页表的映射关系。 cpu在运行程序时,就会直接执行另一个程序的代码和数据了。这个过程并没有创建新的进程。这个过程在内核中发生,所以必需调用系统接口,接下来介绍一下程序替换的系统接口。

二、execl相关接口

我们先看一下系统中的execl接口

<1>execl接口

先看一下该接口在手册中的参数

pathname: 需要执行程序的路径,我们想要执行一个程序的前提,就是找到一个程序,而这里我们就需要为籍人口提供特定路径,让系统进行查找。

arg :你想怎么执行的字符串描述,简单点描述就是具体的命令,比如ls。

......:可变参数,类似于printf中的可变参数,这里的参数其实就是arg命令的选项,这是一张表,表的结尾是NULL。

下面演示一下该接口的具体用法,方便理解

#include<stdio.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
    printf("I am process , pid: %d\n",getpid());
    printf("替换开始\n");
    execl("/usr/bin/ls","ls","-l",NULL);
    printf("替换结束\n");
    return 0;
}

这里arg就是“ls”命令,“-l”表示选项,注意最后一定要以NULL结尾,不是“NULL”。

运行结果:

我们可以看见,运行Test.c这个可执行程序时,运行结果出现了ls命令的结果,这证明替换成功了。

细节一、

我们可以看见在exec*替换接口后我们还有一条printf语句并没有执行,说明替换开始后,原来在exec*接口后面的程序就不会被执行了,它已经在物理内存中被覆盖。

细节二、

exec* j接口只有失败返回值,没有成功返回值。如果成功了,后面的程序就被替换了,返回了没有意义。

细节三、

替换完成后,不建立新的程序(这里可以替换top命令,查看该程序的pid是否改变)。

细节四、

创建一个进程,先创建pcb,地址空间,页表,然后把进程加载到内存当中。

在上述的知识的支撑下,我们可以写一个多进程版本的替换。

#include<stdio.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
       printf("execl begin,pid:%d\n",getpid());
       execl("/usr/bin/ls","ls","-l",NULL);
       printf("execl end\n");
       exit(1);
    }
    //父进程
    pid_t rid = waitpid(id,NULL,0);
    if(rid > 0)
    {
        printf("wait success, pid: %d\n",rid);
    }

    return 0;
}

执行结果

上述的代码通过子进程进行替换,父进程仍然可以得到子进程的退出信息。这里父进程没有被替换的原因其实就是因为进程具有独立性。原来的父子进程是共享物理内存中的代码和数据的,一旦发生程序替换,这里就会产生写时拷贝,保证进程的独立性。其实这里我们也能发现,我们的bash进程也是通过上述的操作来执行我们的命令。

简单介绍其他exec类接口

<2>execlp

前面已经介绍过execl接口,下面我们将介绍一下execlp。execlp基本很execl一样,就第一个参数传替换程序的路径即可。这里的最后一个字母p,可以看成PATH,你不用告诉系统,程序在哪里,只要告诉我名字是什么,系统在程序替换时,系统会自动帮我们在环境变量中寻找该程序,所以第一个参数传需要替换执行程序的文件名即可。当然,如果在写程序时,已经将要替换程序的路径写进去,系统也就不会自己寻找了。而p字母前面的l字母可以看成是list,表示参数列表的意思。

下面复用上面的代码,演示一下该函数的具体用法。

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
       printf("execl begin,pid:%d\n",getpid());
       execlp("ls","ls","-l",NULL);
       printf("execl end\n");
       exit(1);
    }
    //父进程
    pid_t rid = waitpid(id,NULL,0);
    if(rid > 0)
    {
        printf("wait success, pid: %d\n",rid);
    }
    return 0;
}

运行结果

execlp接口的运行结果于execl接口的运行结果无异。

<3>execv

第一个参数是替换程序的路径,第二个参数是一个指针,这个指针其实就是指向我们命令行参数列表,就是我们前面介绍的main函数的其中一个参数。这张表的内容其实和execl接口传的参数是一样的,只不过在execl中,pathname后面参数的没被打包成一张表,而在这里我们的execv中需要传入一张表。这里最后一个字母v可以看成v,表示用数组传递参数的意思。

下面复用上面的代码,演示一下这个接口

int main()
{
    char* const arg[] = {(char*)"ls",(char*)"-l",(char*)"-a",NULL};
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
       printf("execl begin,pid:%d\n",getpid());
       //execl("/usr/bin/ls","ls","-l",NULL);
       //execlp("ls","ls","-l",NULL);
       execv("/usr/bin/ls",arg);
       printf("execl end\n");
       exit(1);
    }
    //父进程
    pid_t rid = waitpid(id,NULL,0);
    if(rid > 0)
    {
        printf("wait success, pid: %d\n",rid);
    }
    return 0;
}

运行结果

<4>execvp

这个接口的参数非常简单,只需要传替换程序的文件名和命令行参数表即可,下面就不演示了,结果和上面的几个接口一样。

<5>execle/execve/execvpe

这三个接口的最后都带了一个e,这里我们可以把这个e看成是环境变量。所以这三个接口都需要传递环境变量参数。这几个接口的使用规则就是在结尾不加e的三个接口(execl,execv,execvp)基础上,在传参数时,最后增加一个环境变量表的指针。这里的环境变量表指针,既可以通过main函数的传参获得,也可以通过environ获得。

这里我们会发现其实execvp并没有传递环境变量参数,但是该接口会依据环境变量自动寻找程序,那这里的execvp这个接口是如何获得环境变量的呢?其实这就和之前的进程地址空间有关系,在进程地址空间中存在一块空间专门用于存放命令行参数和环境变量。默认可以通过进程地址空间继承的方式,让所有的子进程拿到环境变量。并且我们进行进程程序替换时,不会替换环境变量数据。所以每一个子进程都能获得环境变量。

如果我们想要传入一个全新的环境变量呢?那我们就可以使用以上的三个接口,这三个接口都含有envp参数,只要我们传入新的环境变量表,原来的环境变量就会被覆盖。如果想保持原样,直接传原有的环境变量表即可。

<6>区别

上面所有接口的功能其实是一样的,只不过传递参数的形式是不一样的。其实只要我们打开手册就可以发现除了execve这个接口是二号手册外,其他的接口都是在三号手册里面,说明这里真正的系统调用只有一个接口,那就是execve。其他的接口都是execve的封装。

以上就是所有的内容,文中如有不对之处,还望各位大佬指正,谢谢!!!

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值