资讯动态

Linux操作系统-进程的“夺舍”:程序替换如何清空内存、注入新魂?

发布时间:2026/8/14 4:49:44 来源:尧图企业网站定制
今天我们就到了进程的最后一个话题进程程序替换那这个话题是讲什么的呢我们先来思考一个问题fork()之后父子进程会各自执行父进程代码的一部分那你就没有想过让子进程去执行全新的程序吗这个问题想必大家之前都想过这个问题换个问法就像我们之前深入fork函数时讲的例子你父亲有两个工厂有了你等你长大后去继承你父亲的一个厂子你们父子二人共同去经营这两个厂。但是你就没想过你不去继承你父亲的厂子而是你自己去打拼有自己的厂子自己去经营回到父子进程的话题而要实现上面的操作我们就需要今天要讲的知识进程程序替换下面且听我来说道说道。一.进程程序替换的原理首先我们要先大概了解一下进程程序替换的原理是什么这样下面进行实操时大家就能知道是怎么个事了废话不多说我们先来看一张图页表左边的部分就是一个进程中含有的结构包括PCB虚拟地址空间(mm_struct)页表中间就是我们的物理内存虚拟内存中的各部分如代码段数据段都经过页表映射到物理内存中的相应位置。而进程程序替换是怎么做的呢简单来讲就是将图中右边磁盘中新的程序(代码和数据)覆盖掉或者说替换掉原本物理内存中代码段和数据段的内容那么当前进程在执行时就会按照新的代码和数据来运行。我们要覆盖的是物理内存中的代码和数据而物理内存又是被操作系统所管理的那么换句话说也就是我们要完成上面的操作就要访问操作系统中的资源那么我们该如果访问呢没错正是通过系统调用也就是进程程序替换是通过特定的接口加载磁盘上的一个全新的程序(代码和数据)加载到调用进程的地址空间中有了上面对进程程序替换的初步认识我们先面就来看看具体是如何操作的。二.程序替换函数在这里我将介绍6中常见的程序替换函数每一种替换函数都有些许的不同下面我一个一个来介绍。2.1execl函数我们先看这个函数参数部分第一个参数从名字我们就能猜到这个参数应该传什么就是路径也就是指定你要让当前的进程执行哪个新的程序而第二个参数的含义就是你想要执行程序的程序名称如“ls””pwd“这里这么说大家可能不太理解下面我们实操时大家就能明白。而后面的参数是...这个相比都不陌生应该都见过正是可变参数这点在printf函数的参数中也是有的这里就不详细介绍什么叫做可变参数了不是我们今天讲的重点那么相比大家心中都有一个疑问那就可变参数传什么呢我们上面说第二个参数要传执行程序的程序名称并举了例子而那些例子我们都知道后面是可以跟选项的如ls -a -n -l等可变参数传的就是后面的这些选项并且最后一位一定是NULL因为NULL代表程序替换结束的标志。2.1.1单个进程废话不多说我们直接来看实例这里我们选执行的是系统内部的程序也是我们比较熟悉的命令ls我们通过结果可以看到进程通过execl函数确实执行了ls命令可能有人会疑惑为什么没有执行后面的printf函数呢我们在上面介绍进程程序替换原理时就说了程序替换是会替换掉你原先的代码和数据的既然已经替换了那么后面代码中的printf函数自然就没有了。那为什么会执行前面的printf函数呢因为前面的printf函数在execl函数之前而程序是顺序结构执行的所以会先执行printf函数在执行execl函数后程序的代码和数据会被替换为ls的可执行文件中的代码和数据。这里可能有人会有疑问那第一个参数含有ls第二个参数还是ls不会显得重复吗答案并不会这两个语义是不同的第一个参数我们要指定具体你要执行哪个可执行文件而第二个参数是我们想怎么执行这个可执行文件。我们通过上面的输出对比图就可以发现第二个参数及后面的可变参数对应的不正是我们在命令行中输入的ls -l -a吗所以说二者并不重复。也就是说我们要执行哪个文件/usr/bin/ls这个路径下的可执行文件怎么执行以ls -l -a的方式执行我们在命令行怎么写第二个参数及后面的可变参数就怎么传这样大家就能更好地去理解。而在上面的execl图中我们也注意到了execl这个函数是有返回值的既然是替换那么肯定会有失败的情况啊我们来看看它返回的是什么这句话的意思是exec系列的函数只会在替换失败时返回-1而替换成功则什么也不返回。这种情况我们其实也能理解替换成功了就去执行新的代码即使成功了给你返回值而你下面的代码也用了这个返回值但是有什么意义呢我替换成功就直接将原来的代码和数据替换了当然也包括你在execl函数后面写的用到返回值的代码所以说成功了给返回值没有意义。那什么情况下会出现替换失败呢那就是你想执行一个不存在的可执行文件既然不存在那可不就会替换失败吗而替换失败就会接着执行后面的代码我们在替换失败后打印出返回失败的返回值结果也正如上图所示。2.1.2父子进程上面我们只介绍了单独一个进程的程序替换但是重点我们还是要放在fork之后产生的父子进程上面下面我们就来看一个父子进程的实例根据我们上面所说子进程在执行execl函数时就会去执行全新的代码结果也符合我们的预期而父进程同样也执行了自己的代码但是我想在这正确的输出结果下有人可能会有疑问不是说父子进程共享代码和数据(写时拷贝)吗那子进程通过execl函数替换了代码父进程为什么还会执行后面的代码原因是要保持父子进程的独立性子进程替换了代码但是因为进程之间的独立性它并不能影响父进程的执行那要如何做到保持父子进程之间的独立性呢答案就是写时拷贝既然数据都能通过写时拷贝的方式来保持独立性那么代码也同样能通过写时拷贝来保持独立性方式和数据的写时拷贝是一样的经过写时拷贝子进程的代码和数据就会执行新的地址空间并修改页表中相应的映射关系用这种方式也就保持了进程之间的独立性。有了前面知识的铺垫我们来思考一个问题我们总说要执行一个二进制文件要先将程序加载到内存中这是由冯诺依曼体系结构决定的那到底是怎么加载的呢这个问题想必我们之前并没有人深入思考过在这里我们就能理解个大概了我们上面自己写的程序在某种意义上不正是一个”解释器“吗我们用自己写的代码借助系统调用接口将磁盘中的一个新的程序加载到了内存当中这样说可能不太明显下面通过其他的接口我们来证明一下。2.2execv函数第二个函数我们来讲execv这个函数相比于上面的execl看起来也就是参数有些不同罢了其实也就是参数不同。那么execv的第二个参数这个数组是怎么个事呢这个其实就是将execl后面的参数都装进了一个数组中也就是说数组中的内容和之前是一样的我们来看其基本应用这次我们换个程序执行也是我们熟悉的pwd命令这个函数要传一个数组数组中的内容最后一个也要是NULL和execl是一样的。其实execv这个函数与execl就只是传参方式不同罢了execl需要直接传参而execv则是将要传的参数写入一个数组中在将数组传进去就这么点区别所以execv的v也就是我们常见的vector。说到这里我们利用这个函数来给大家演示上面我们所说的”解释器“的证明这里我们要用到main函数的参数argv也就是我们之前讲过的命令行参数我们让arg指针指向argv的第二个数据因为第一个数据是我们要执行的文件后面才是要怎么执行并将argv的第一个数据传给execv的第一个参数之后将arg指向的从下标为1开始的数组传给第二个参数。我们看命令行的输入将./myexec当成解释器这样写不就是我用解释器来解释后面要执行的代码吗结构就如上图所示上面我们在写代码并没有写main函数的参数所以只用./myexec即可但其实底层在传参时是按照上面的参数进行传参的无非就是execv中的参数有两个/usr/bin/ls而已不影响我们之前第二个参数不写路径是因为第一个参数已经写过路径了系统已经能找到要执行的文件了所以就省略没写。2.3execlp函数和execvp函数因为上面的execl和execv非常相似这里的execlp和execvp同样也是如此所以将这两个函数放在一块讲我们先来看看这两个函数依旧是传参形式不同和上面的两个是一样的不过我们之一到这两个函数的第一个参数不再是pathname而是file是有什么区别呢我们看例子就知道区别在哪儿了很显然这两个函数相较于前面的两个函数区别在于第一个参数不再需要传路径了它会自动去环境变量PATH含有的路径中去找你要执行的文件。写起来就更加简单和方便了剩下的和上面两个函数没区别这里就不过多介绍了。2.4execle函数和execvpe函数这两个函数相比较上面四个函数多了一个参数就是用红色方框圈起来的envp这个参数也是一个数组那么是什么呢答案是环境变量这个参数用于指定新程序执行时的环境变量既可以传我们自己定义的环境便变量数组也可以传系统默认的环境变量数组其实也就是我们之前提到过的环境变量表下面我们一起来看看它的基本应用这里我以execvpe为例我们这次让子进程来执行我们自己写的程序程序的内容就是打印出命令行参数和环境变量程序的结果我们也看到了命令行参数和环境变量的内容其中环境变量就是我们所传进去的并且命令行参数正是arg数组中的内容。那我们要想打印出系统的默认环境变量呢我们来看利用我们之前讲环境变量时的environ即可它指向系统默认的环境变量表这样我们就能获得所有体统的环境变量。那有人又会问了那我要想既想传自定义环境变量又想传系统环境变量该如何做呢很简单我们将自定义环境变量变成系统环境变量不就行了那该如何做呢我们可以通过putenv这个函数来讲我们自定义的环境变量给加到系统的环境变量中那效果怎么样呢我们来看看从结果可以看到我们确实将自定义的环境变量加入到了系统的环境变量中至此也就解决了上面的问题。而execle函数的应用和execvpe差不错这里就不再演示了。最后其实还有最后一个exec系列的函数我们简单介绍一下。2.5execve函数我们对比这两张图可以发现上面的execve函数是在linux手册的第2章节而我们上面介绍的exec的一系列函数都在手册的第3章节有什么区别呢在第2章节的都是系统调用函数而在第3章节的都是库函数讲到这里想必有的朋友就明白我想说什么了。没错上面我们介绍的一系列exec函数都是底层都是由execve系统调用函数实现的它们都是对execve函数进行了封装传给它们的参数都会在函数内部进行转变转变为execve函数的参数并将其传进去。有了上面内容的铺垫我们就可以解决一个之前没有讲清楚的问题我们之前总说父进程获得了命令行参数和环境变量就比如bash它把命令行参数表和环境变量表传给了子进程那到底是如何传的呢答案就是我们上面讲的exec系列函数就和我们上面讲的” 解释器 “例子是一样的bash通过这些函数讲命令行参数表和环境变量表传给了子进程上面execve的argv和envp两个数组就是啊。并且如果你不以参数的形式传给子进程子进程也是能拿到的为什么呢因为虚拟地址空间中就有命令行参数和环境变量啊即使你不传子进程也能通过虚拟地址空间和页表来找到物理地址中的命令行参数和环境变量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价