Linux进程替身术:深入解析进程程序替换机制

Linux系列


文章目录


前言

进程程需替换是进程管理中一个重要的机制,它是通过exec系列的函数实现的,它允许一个进程进行程序替换后,替换为另一个新进程,下面我们会通过示例详细分析它的使用及替换机制。


一、进程替换的概念

进程替换允许当前进程,在不创建新进程的情况下通过调用exec系列函数,将当前进程替换为一个新的进程。替换后,新的程序从main函数开始执行,进程的PID保持不变,待代码段、数据段、堆栈等都会被替换。进程替换以便会配合frok创建子进程,然后使用子进程替换,一般替换为shell命令,进程替换发生时,当前进程的会被新的程序映像所替换,也就是说一旦发生进程替换该进程的旧代码将不会再被执行。

对于进程替换的机制,我们可以结合进程管理时所讲的内容来理解,发生进程替换时仅改变子进程所拥有的页表中虚拟地址和物理地址间的映射关系,将虚拟地址与新加入内存的数据代码建立联系,发生进程程序替换时,不仅数据会发生写实拷贝,代码同样会发生写实拷贝,所以进程管理机制对这里的程序替换可谓是非常重要。

进程替换的核心特点

  • 内存空间替换:进程的内存(代码段、数据段、堆栈等)会被新的程序映像替换。
  • 进程描述符:对于部分进程描述符是不改变的,如PIDPPID、进程优先级等。

接下来我们会结合示例,对上面的概念详细分析和验证

二、exec系列函数

2.1 execve()函数

这个函数属于系统调用,是最基础的一个函数,exec系列函数都是通过封装它来实现的,下面我们来介绍一下它的使用:
头文件
#include<unistd.h>
参数
1、filename:要替换执行程序的路径(绝对路径/相对路径)。
2、argv[]:字符串指针数组,包含要传递给程序的命令行参数,最后以NULL结尾。
3、字符串指针数组,包含要传递给程序的环境变量,最后以NULL结尾。
返回值
调用成功不返回,调用失败-1.
为方便大家理解我先使用单继承版的进程程序替换
示例1:

c 复制代码
    1 #include<stdio.h>
    2 #include<unistd.h>
    3 
    4 int main ()                                                                                                                     
    5 {
    6   char *argv[]={"ls","-a","-l",NULL};
    7   char *env[]={"AAA=1234",NULL};
    8   printf("进程开始执行\n");
    9   execve("/bin/ls",argv,env);
   10   printf("程序未完成替换\n");
   11   return 0;
   12 }

从程序执行结果可以看到当,程序执行过execve函数后,程序被替换为ls -a -l命令,后面代码直接完成替换,所以少打印了一句。对于单进程的替换,不会存在写时拷贝,程序直接完成替换,进行上面替换时,可以更具需要创建不同的环境变量、命令行参数。

2.2 execl()函数

对于exec系列的函数,都是仅在调用错误时返回-1并且设置error标识错误。

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

参数
1.要替换执行程序的路径(绝对路径/相对路径)
2.这是一个可变参数,可以同时接受多个传参

下面全部以多进程为例进行介绍
示例:

c 复制代码
  1 #include<stdio.h>  
  2 #include<unistd.h>                                                        
  3 #include<sys/wait.h>                                                                                                              
  4 int main ()                            
  5 {                                      
  6   pid_t id=fork();                     
  7   if(id==0)                                                
  8   {                                    
  9     printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());  
 10     execl("/bin/ls","ls","-l","-a",NULL);  
 11   }                                    
 12   else{                                
 13     printf("I am parent,pid:%d\n",getpid());  
 14     int status=0;                      
 15     wait(&status);                                    
 16   }                                    
 17   return 0;                                                            
 18 }     

这里的可变参数允许我们传递多个命令参数,exec系列函数是使用execve封装而产生的,所以在功能上都差不多,只是使用上略微有差异。

2.3 execv()函数

c 复制代码
int execv(const char *path,char const*argv[])

execl使用方法相似
参数
1.要替换执行程序的路径(绝对路径/相对路径)
2.字符串指针数组,存储的是我们需要执行的命令参数,以NULL结尾

示例:

c 复制代码
    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<sys/wait.h>
    4 int main ()
    5 {
    6   pid_t id=fork();
    7   if(id==0)
    8   {
    9     char *const argv[]={"ls","-a","-l",NULL};
   10     printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
   11     //execl("/bin/ls","ls","-l","-a",NULL);
   12     execv("/bin/ls",argv);                                                                                                      
   13   }                                                                                                             
   14   else{                                                                                                         
   15     printf("I am parent,pid:%d\n",getpid());                                                                    
   16     int status=0;                                                                                               
   17     wait(&status);                                                                                              
   18   }                                                                                                             
   19   return 0;                                                                                                     
   20 }           

执行结果相同,这一块真的没什么讲的。。。

2.4 execlp()函数

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

这个函数相较于execl,不需要传递路径信息,只需要传递要执行的程序名,该函数会去PATH环境中查找。
1、file:程序名。
2、可变参数列表,可同时接受多个参数,以NULL收尾。

c 复制代码
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/wait.h>
  4 int main ()
  5 {
  6   pid_t id=fork();
  7   if(id==0)
  8   {
  9   //char *const argv[]={"ls","-a","-l",NULL};
 10     printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
 11     execlp("ls","ls","-l","-a",NULL);                                                                                             
 12   //execv("/bin/ls",argv);
 13   }
 14   else{
 15     printf("I am parent,pid:%d\n",getpid());
 16     int status=0;
 17     wait(&status);
 18   }
 19   return 0;
 20 }

函数功能都类似,接下来我们在介绍一个,大家对比上面的理解即可

2.5 execle

c 复制代码
 int execle(const char *path, const char *arg,..., char * const envp[]);

execle函数是exec系列中比较复杂的一个,分析:
1、path:要替换执行程序的路径(绝对路径/相对路径)。
2、可变参数,包含要传递给程序的命令行参数,最后以NULL结尾。
3、字符串指针数组,包含要传递给程序的环境变量,最后以NULL结尾。
这个环境变量数组,根据自己的需要进行设置

c 复制代码
    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<sys/wait.h>
    4 int main ()
    5 {
    6   pid_t id=fork();
    7   if(id==0)
    8   {
    9     char* env[]={"AAA=123",NULL};     ;                                                                                         
   10     printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
   11     execle("bin/ls","ls","-l","-a",NULL,env);               
   12   }                                                         
   13   else{                                                                                              
   14     printf("I am parent,pid:%d\n",getpid());                                                         
   15     int status=0;                                                                                    
   16     wait(&status);                                                                                   
   17   }                                                                                                  
   18   return 0;                                                 
   19 }         

需要注意的是,在传递环境变量时,是覆盖式传递。

三、程序替换的使用

早在学习指令是我们就说过,指令就是操作系统写好的可执行程序,所以我们不仅可以使用指令程序进程替换,我们也可以使用我们自己的程序进行替换,在我们介绍的main函数的三个参数,刚好对应着exec系列参数的类型。

当前目录下存在,可执行文件process,我使用test通过进程程序替换对它进行调用,并修改环境变量验证,子进程创建继承下来的环境变量不会应为,进程替换而改变。

test.c文件

c 复制代码
  1 #include<stdio.h>  
  2 #include<unistd.h>  
  3 #include<sys/wait.h>  
  4 #include<stdlib.h>  
  5 int main ()  
  6 {  
  7   pid_t id=fork();  
  8   setenv("AAA","123",0);                                                                                                          
  9   if(id==0)                                                                                              
 10   {                                                                                                      
 11     execl("./process","process",NULL);                                                                   
 12   }                                                                                                      
 13   else{                                                                                                  
 14     printf("I am parent,pid:%d\n",getpid());                                                             
 15     int status=0;                                                                                        
 16     wait(&status);                                                                                       
 17   }                                                                                                      
 18   return 0;                                                                                              
 19 }             

process.c文件

c 复制代码
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main()
  4 {
  5   printf("%s\n",getenv("AAA"));                                                                                                   
  6   return 0;                  
  7 }  

上面代码我们通过执行test文件达到对其他文件的调用,并且证明了程序替换后子进程继承的环境变量不会改变。这里我们可以使用进程程序替换的方式对任意(不受语言限制),可执行文件进行调用,而exec系列函数,只是充当了一个加载器的作用。
bash命令行对于外部命令的调用也是使用fork()exec(),通过进程程序替换来完成工作的。

总结

进程程序替换通过exec系列函数实现,高效切换程序逻辑,广泛应用于命令行工具和动态服务。使用时需精准处理参数、环境变量及资源,确保稳定性和安全性。理解其机制对开发高效、可靠的多进程应用至关重要。

相关推荐
Dontla18 分钟前
Modbus RTU ---> Modbus TCP透传技术实现(Modbus透传、RS485透传、RTU透传)分站代码实现、协议转换器
服务器·网络·tcp/ip
Wireless_wifi627 分钟前
QCN9274/QCN6274 WiFi 7 Modules: Transforming Mining & Oil Industries
linux·5g·service_mesh
梅见十柒1 小时前
UNIX网络编程笔记:TCP、UDP、SCTP编程的区别
服务器·网络·c++·笔记·tcp/ip·udp·unix
JZC_xiaozhong1 小时前
单一主数据系统 vs. 统一主数据中心,哪种更优?
大数据·运维·企业数据管理·主数据管理·mdm管理·数据孤岛解决方案·数据集成与应用集成
一直走下去-明1 小时前
docker简单使用
运维·docker·容器
三块钱07942 小时前
ubuntu22.04 安装Jitsi meet 开源会议系统,代替腾讯会议
linux·运维·服务器·腾讯会议·会议系统·jitis meet
m0_740154672 小时前
SpringMVC 请求和响应
java·服务器·前端
多多*2 小时前
JavaEE企业级开发 延迟双删+版本号机制(乐观锁) 事务保证redis和mysql的数据一致性 示例
java·运维·数据库·redis·mysql·java-ee·wpf
叱咤少帅(少帅)2 小时前
Go环境相关理解
linux·开发语言·golang