【Linux】进程理解与学习-程序替换

简介: 【Linux】进程理解与学习-程序替换

前言



在前文,我们学习了fork函数创建子进程,而创建子进程主要就是为了让它帮我们执行特定的任务。而我们之前所学的都只是为了让子进程帮我们执行父进程的部分代码(通过执行流分流的方式),并没有执行一个全新的程序。但实际上子进程也是可以执行一个新的程序。子进程可以通过程序替换的方式将父进程的代码与数据替换成新的程序对应的代码与数据。本文将对此进行探讨。


程序替换



进程可以通过程序替换的方式来执行一个全新的程序,具体的做法则是通过对应的程序替换的几个系统调用函数来实现,下面先来看一下程序替换的现象,根据这个现象来分析程序替换实现的原理。


程序替换的现象


接下来我们通过该现象对此进行分析,现象如下:


1.png


我们可以看到,我们最终的执行结果并不是像我们想象的那样,而是将本该打印的end给替换成了执行ls这个指令。那么具体的原理是怎样的呢?如下图所示:


程序替换的原理


原理图解


2.png


通过上图,也解释了为什么在执行我们的进程时,execl函数后面的end的打印并没有执行,因为在我们执行完打印begin后,开始调用系统调用函数execl,将新的程序(ls)的代码与数据加载到内存对应的位置,将老进程的代码与数据给替换掉,所以就执行不了后面的打印end指令了。(这里注意的是,程序替换是实现的代码与数据的整体替换)


那么这里不仅有一个问题:在进程替换时,有没有产生新的进程呢?


答案是没有的,因为我们仅仅只是将老进程里面的代码与数据,替换成新程序的代码与数据,实现程序替换。并没有产生新的进程。正如上图所示,原进程对应的pcb并没有发生改变,也没有产生新的pcb。


当然我们也可以通过代码来验证一下:


3.png


多进程对应的程序替换


当然,假如我们使用fork创建子进程,让子进程完成程序替换,子进程的程序替换并不会影响父进程,这是因为父子进程都有各自独立的PCB,并且由于写时拷贝机制的存在,使得父子进程互相独立,互不影响。


4.png


程序替换函数


接下来我们讲一下几个程序替换函数。总体一共有7个,其中这七个里的六个实际上底层都是调用第七个。总体如下:


5.png


exec函数家族关系


对于这些exec函数,它们都只具有失败时候的返回值,当程序替换失败时,会返回-1,同时继续往后执行exec后面的指令,当替换成功时会直接执行替换后的新程序。接下来逐一介绍。


execl函数


首先介绍的是execl函数,我们在上面的演示中用到的就是该函数。


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

对于该函数来说:


返回值:失败返回-1,替换成功执行新程序。

path参数:新程序的路径位置(找到它)

arg参数:新程序名称(执行它)

...:可变参数,这里表示命令行参数选项(就比如上文所示-a -l),以NULL结尾。

举例:


6.png


execv

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


该函数我们发现,之前的l变成了v,实际上其实就是用了一个函数指针数组,将之前的"ls","-a","-l"的地址放进数组里,数组最后一位元素为NULL,然后将该指针数组的起始地址(数组名),当作execv的第二个参数。


对于该函数来说:


返回值:替换失败返回-1

path参数:程序所在路径

argv[]:指针数组的地址(数组名)

举例:


7.png


execlp

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


对于该函数,我们发现之前的path参数更换成了file,然后函数名中加了个p,其实代表的意思就是,会在PATH环境变量中根据file名查找file的路径,后面的参数代表的与execl一样。


对于该函数:


返回值:替换失败返回-1

file传程序名,会根据程序名自动在PATH中搜索该程序对应的路径。(找到它,不需要我们传具体的路径,会自动匹配)

后面的arg表示程序名(执行它)

...:可变参数,表示新程序的命令行参数选项(怎么执行,比如:-a -l),以NULL结尾

举例:


8.png


不过这里需要注意的是,自动搜索匹配路径是指在PATH环境变量中搜寻,假如一个新程

序的路径并不在PATH中,则会匹配不上,就导致替换失败。如下:


9.png


程序替换失败


execvp


我们发现,该函数变成了vp结尾,v表示数组(指针数组)的形式,p表示自动搜索匹配环境变量PATH中的路径。


int execvp(const char *file, char *const argv[]);

对于该函数:


返回值:替换失败返回-1

file:程序名,会根据程序名自动搜索在PATH中对应的路径

argv[]:指针数组,用法同execv

举例:


10.png


同样,这里自动匹配路径指的是在PATH中搜索,假如我们想要执行替换自己写的程序,就要将我们写的程序的路径用export导入环境变量即可。


execle


这里的e,表示environ,即表示环境变量表。也就是说,我们可以将当前程序的替换成新程序,同时将老的环境变量表也传给新程序。


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

对于该函数:


返回值:替换失败返回-1

用法同execl,只是最后加了一个参数,用来传给新程序环境变量表(这个环境变量表可以是自己定义,也可以是系统的)。

当然如果将自定义的环境变量表传给新程序的话,新程序的原有的系统环境变量表就会被覆盖掉。

举例:


11.png


execvpe


我们发现,这里v(数组)、p(PATH)、e(环境变量表),三者都集齐了。  


int execvpe(const char *file, char *const argv[],
                   char *const envp[]);

对于该函数:


返回值:替换失败返回-1

file:会自动根据file匹配PATH中的路径,不需要我们手动写全

argv[]:指针数组

envp[]:环境变量表(可以是系统的,也可以是自定义的)

举例:


newtest程序:


#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
 int main()
 {
   extern char** environ;
   printf("我是新程序,我的环境变量表中的前三个环境变量是:\n");                                                                               
   for(int i=0;i<3; ++i)
   {
     printf("%d:%s\n",i,environ[i]);
   }
   return 0;
 }

运行newtest:


12.png


mytest程序:实现程序替换(在此之前已经将newtest的路径导入了PATH)


#include<stdio.h>
 #include<unistd.h>
 #include<stdlib.h>
 #include<sys/types.h>
 #include<sys/wait.h>
 #include<fcntl.h>
 int main()
 {
   pid_t id=fork();//创建子进程
   if(id == 0)
   {
     //child
    printf("我是子进程,pid:%d\n",getpid());
    char*const envp[]={"myval=520",NULL};//自定义环境变量表
    const char* argv[]={"newtest",NULL};//指针数组
    execvpe("newtest",argv,envp);//注意:我已经将newtest的路径导进了PATH
    printf("程序替换失败\n");                                                                                                               
    exit(1);
  }
   //father
   int status=0;
   waitpid(id,&status,0);//进程等待
   if(WEXITSTATUS(status)!=1)
   {
     printf("进程替换成功\n");
   }
   return 0;
 }

13.png


已经将新程序路径导入环境变量


14.png


execve

execve为最正宗的系统调用函数,我们这里讲解的其它的系统调用其实底层都是调用了该函数。


int execve(const char *filename, char *const argv[],
                  char *const envp[]);

对于该函数:


替换失败返回-1

参数filename:表示新程序的路径(找到它)

argv[]:指针数组,存放新程序名(执行它),以及程序对应的命令行参数列表(比如ls的-a -l),最后以NULL结束。

envp[]:环境变量表(可自定义),传入环境变量表后,新程序的老的环境变量表就被这里的envp环境变量表给覆盖了。

举例:


15.png


额外补充


对于以上的这么多系统调用函数,可能看了都头大,但是仔细看,其实会有很多共性,可以利用这 个特点来更加巧妙地记住这些函数的用法。


联想记忆exec函数家族:


带有l的:l联想list,所以参数中要带有NULL,就好像一个链表一样。("ls", "-l"," -a"," NULL)

带有v的:v联想vector,说明第二个参数传的是个数组(指针数组)

带有p的:p联想PATH,说明第一个参数不需要我们传具体的路径,只需要传个程序名即可(会自动搜索PATH路径进行匹配)。

带有e的:说明最后一个参数是用来传环境变量表(也可以是自定义的环境变量表)的


相关文章
|
12天前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
75 4
|
6月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
265 67
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
153 16
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
120 20
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
110 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
133 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
99 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
96 0
|
7月前
|
存储 IDE Linux
零基础保姆级教程!手把手教你免费玩转Linux CentOS安装+学习环境搭建(附避坑指南)
本文详细介绍了在VMware虚拟机中安装CentOS 6.8的全过程。首先,需确保已安装VMware并开启V-CPU虚拟化功能,可通过BIOS设置或使用LeoMoon CPU-V工具检测。接着,下载CentOS镜像文件,并在VMware中新建虚拟机,配置CPU、内存、硬盘等参数。最后,加载ISO镜像启动虚拟机,按照提示完成CentOS的安装,包括语言、键盘、存储方式、地区、密码设置及硬盘分区等步骤。安装完成后,以root用户登录即可进入系统桌面,开始学习Linux命令和操作。
726 12
零基础保姆级教程!手把手教你免费玩转Linux CentOS安装+学习环境搭建(附避坑指南)
|
7月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。