关于Linux下的进程替换(进程篇)

目录

进程替换是什么?

进程替换需要怎样操作?

 替换函数

 命名理解

不创建子进程进行进程替换

关于替换程序时的写时拷贝

fork创建子进程进行替换

函数1:execl

函数2:execv

函数3:execlp

函数4:execvp

函数5:execle


进程替换是什么?

父进程fork()创建子进程之后,父子各自执行父进程代码的一部分

父子进程代码共享,数据写时拷贝各自一份

那么此时,子进程想执行一个全新的程序呢? 子进程想有自己的代码呢??应该怎么办?

此时就需要进程替换,来完成这个功能。

官方的说法是:程序替换,是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间中!

可参考:进程的创建  程序地址空间

fork常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。---就叫做程序替换

图例:

  • 进程替换,有没有创建新的子进程? 没有!
  • 那么如何理解所谓的将程序放入内存中!   是通过加载!
  • 所谓的exec*函数本质,就是如何加载程序的函数!
  • 编译有编译器,加载也有加载器注意磁盘和内存都是硬件,那么加载的时候,系统会提供相关函数来进行把磁盘的数据搬到内存中就是加载
     

进程替换需要怎样操作?

a.不创建子进程进行进程替换(无意义,主要用于演示)

b.fork创建子进程进行替换

替换函数

EXEC(3)                                                         Linux Programmer's Manual                                                         EXEC(3)

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

SYNOPSIS
       #include <unistd.h>

       extern char **environ;

       int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[],
                   char *const envp[]);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       execvpe(): _GNU_SOURCE

 命名理解

这些函数原型看起来很容易混 , 但只要掌握了规律就很好记。
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

示例:

函数1:execl

l:参数是列表

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

path :路径+目标文件名

arg :参数包,包含了若干个参数

... :可变参数列表(最后一个参数为NULL,标识参数传递完毕)

不创建子进程进行进程替换

示例:

当前未进行程序替换:

[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("\n");
    printf("****************process begin*************\n");

    printf("hello world\n");

    printf("*****************process end**************\n");
    printf("\n");

    return 0;
}
[wxq@VM-4-9-centos code_4_10_2]$ ./test 

****************process begin*************
hello world
*****************process end**************

[wxq@VM-4-9-centos code_4_10_2]$ 

进行程序替换①:

[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("\n");
    printf("****************process begin*************\n");

    execl("/usr/bin/ls", "ls", "-l",NULL);

    printf("hello world\n");

    printf("*****************process end**************\n");
    printf("\n");

    return 0;
}
[wxq@VM-4-9-centos code_4_10_2]$ ./test 

****************process begin*************
total 20
-rw-rw-r-- 1 wxq wxq   69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq  321 Apr 10 20:54 process_replace.c
-rwxrwxr-x 1 wxq wxq 8472 Apr 10 20:54 test
[wxq@VM-4-9-centos code_4_10_2]$ 

进行程序替换②:

[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    printf("\n");
    printf("****************process begin*************\n");

  //  execl("/usr/bin/ls", "ls", "-l",NULL);

    execl("/usr/bin/top", "top",NULL);
    
    printf("hello world\n");

    printf("*****************process end**************\n");
    printf("\n");

    return 0;
}
[wxq@VM-4-9-centos code_4_10_2]$ ./test 

****************process begin*************
top - 20:57:31 up 220 days, 23:12,  3 users,  load average: 0.01, 0.03, 0.05
Tasks: 126 total,   1 running, 120 sleeping,   0 stopped,   5 zombie
%Cpu(s):  0.5 us,  0.3 sy,  0.0 ni, 99.0 id,  0.2 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  2046504 total,   152152 free,   279976 used,  1614376 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1568788 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                 
  473 root      20   0  973744  36212  16904 S   1.3  1.8  11:39.93 YDService                                                                               
20006 root      20   0  757184  17520   2624 S   0.3  0.9 671:00.83 barad_agent                                                                             
    1 root      20   0   43728   3916   2436 S   0.0  0.2  21:57.28 systemd                                                                                 
    2 root      20   0       0      0      0 S   0.0  0.0   0:08.15 kthreadd                                                                                
    4 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/0:0H                                                                            
    6 root      20   0       0      0      0 S   0.0  0.0   3:17.70 ksoftirqd/0                                                                             
    7 root      rt   0       0      0      0 S   0.0  0.0   1:11.94 migration/0                                                                             
    8 root      20   0       0      0      0 S   0.0  0.0   0:00.00 rcu_bh                                                                                  
    9 root      20   0       0      0      0 S   0.0  0.0  51:58.18 rcu_sched                                                                               
   10 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 lru-add-drain                                                                           
   11 root      rt   0       0      0      0 S   0.0  0.0   0:52.50 watchdog/0                                                                              
   12 root      rt   0       0      0      0 S   0.0  0.0   0:45.59 watchdog/1                                                                              
   13 root      rt   0       0      0      0 S   0.0  0.0   1:11.67 migration/1                                                                             
   14 root      20   0       0      0      0 S   0.0  0.0   2:59.24 ksoftirqd/1                                                                             
   16 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kworker/1:0H                                                                            
   18 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kdevtmpfs                                                                               
   19 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 netns                                                                                   
   20 root      20   0       0      0      0 S   0.0  0.0   0:04.50 khungtaskd                                                                              
   21 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 writeback                                                                               
   22 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 kintegrityd                                                                             
   23 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 bioset                                                                                  
   24 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 bioset                                                                                  
   25 root       0 -20       0      0      0 S   0.0  0.0   0:00.00 bioset                                                                                  
[wxq@VM-4-9-centos code_4_10_2]$ 

注意:可变模板参数最后一定要传NULL,不然会导致程序替换失败

这里有一个问题,为什么???这里的内容没有被显示在屏幕上???

  • 其实,这里的命令根本就没有被执行。
  • 原因是execl是程序替换,调用该函数成功之后,会将当前进程的所有代码以及数据都进行替换。包括已经执行的和未执行的!
  • 所以,—旦调用成功,后续的所有代码,都不会执行。
  • execl为什么调用成功,没有返回值呢?(注:失败,返回-1,继续执行原进程的后续代码)
  • execl根本不需要返回值进行判断,可以理解为它在替换的过程中,把自己都干掉了,所以产生的新代码和它没有关系
     

关于替换程序时的写时拷贝

        其实这里还涉及到一个写时拷贝的问题:在之前的博客中提及过,父进程创建子进程,代码是共享的,地址空间也是共享的,因为代码是不能更改的,而数据写时拷贝。那么发生了进程替换,父子进程还是共用一块代码,一个地址空间吗???那不就乱套了吗?所以我们需要捋顺这一观点:

首先:

父进程创建子进程,如果父进程或者子进程对数据进行写入,那么操作系统就会发生写时拷贝。

此时代码不会进行写时拷贝,代码是不可更改的,因为代码存储在常量区,父进程或者子进程只有读取的权限

(此时:代码共享,数据写时拷贝。父子进程共用虚拟地址空间)

其次:

如果子进程发生替换程序,意味着新程序会从磁盘往内存中加载,这不就是写入吗?

此时,代码会不会发生写实拷贝,答案是肯定,因为要保证进程的独立性,此时,父子的代码必须要分离

(此时:代码,数据写时拷贝,重新给子进程建立虚拟地址空间)

fork创建子进程进行替换

  • 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)
  • 子进程往往要调用一种exec函数以执行另一个程序
  • 当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动进程开始执行。
  • 调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

示例:

函数1:execl

-----------------------------------------------测试代码process_replace.c (下面的测试代码脚本都是这个)-------------------------------------

[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
    pid_t id = fork();

    if( id == 0 )
    {
        //child process
        printf("子进程开始运行, pid:%d\n", getpid());
        execl("/usr/bin/ls", "ls", "-l", NULL);
        exit(1);
    }
    else
    {
        printf("父进程开始运行, pid:%d\n", getppid());
        int status = 0;
        pid_t ret = waitpid(-1, &status, 0); //-1 :等待任意子进程
        if(ret > 0)
        {
            printf("waitpid success! exit code: %d\n", WEXITSTATUS(status));
        }

    }

    return 0;
}
[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test 
父进程开始运行, pid:16226
子进程开始运行, pid:29294
total 20
-rw-rw-r-- 1 wxq wxq   69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq  985 Apr 10 21:26 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 21:26 test
waitpid success! exit code: 0

观察输出可以发现,其实父进程或者子进程的运行顺序是由操作系统决定的,但是,父进程一定会等待子进程运行结束。

函数2:execv

v:表示参数是数组   

execv和execl只有传参上的区别

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

图例:

示例:

输入:

     //参数是列表形式      
     // execl("/usr/bin/ls", "ls", "-l", NULL);  
                           
      char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};  
     // 参数的数组形式     
      execv("/usr/bin/ls", _argv);  
                           

输出:

[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test 
父进程开始运行, pid:16226
子进程开始运行, pid:1093
total 20
-rw-rw-r-- 1 wxq wxq   69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq 1127 Apr 10 21:41 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 21:41 test
waitpid success! exit code: 0

函数3:execlp

p:我会去自己的环境变量PATH中进行查找,你不用告诉我你要执行的程序在哪

补:执行程序需要带路径吗?答案是需要的,但是系统的程序是不需要的的,就是因为环境变量。详细可以参考:Linux的环境变量

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

示例:

输入:

     //参数是列表形式      
     // execl("/usr/bin/ls", "ls", "-l", NULL);  
                           
     // char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};  
     // 参数的数组形式     
     // execv("/usr/bin/ls", _argv);  
                           
      //不需要传入路径                                                                                                                                 
     execlp("ls", "ls", "-a", "-l", NULL); 

输出:

[wxq@VM-4-9-centos code_4_10_2]$ vim process_replace.c 
[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test 
父进程开始运行, pid:16226
子进程开始运行, pid:4772
total 28
drwxrwxr-x 2 wxq wxq 4096 Apr 10 21:55 .
drwxrwxr-x 8 wxq wxq 4096 Apr 10 19:42 ..
-rw-rw-r-- 1 wxq wxq   69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq 1258 Apr 10 21:53 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 21:55 test
waitpid success! exit code: 0
[wxq@VM-4-9-centos code_4_10_2]$ 

其实这里有个小概念需要明白,p不是代表不需要传入路径吗?那为什么还要:输入 "ls", "ls" ,"-a", "-l" NULL

 你不是告诉我,execlp,p不是表示path,不是说不需要带路径吗?为什么这里还需要写第一个"ls"???

图例:

函数4:execvp

那这个不就是很好理解了嘛,v表示参数是数组,p表示不需要传入路径

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

示例:

输入:

         char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};                                            
                                                                                                        
        //参数是列表形式                                                                                
        // execl("/usr/bin/ls", "ls", "-l", NULL);                                                      
                                                                                                       
        // 参数的数组形式                                                                               
        // execv("/usr/bin/ls", _argv);                                                                 
                                                                                                        
        //不需要传入路径                                                                               
        //execlp("ls", "ls", "-a", "-l", NULL);                                                        
                                                                                                       
         execvp("ls", _argv);

输出:

[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test 
父进程开始运行, pid:16226
子进程开始运行, pid:7646
total 20
-rw-rw-r-- 1 wxq wxq   69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq 1287 Apr 10 22:05 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 22:06 test
waitpid success! exit code: 0
[wxq@VM-4-9-centos code_4_10_2]$ 

函数5:execle
int execle(const char *path, const char *arg, ..., char * const envp[]);

这个其实是有点上难度的,因为这里的表示的是环境变量

怎么去描述这个函数呢???

咱先描述另外一个东西再来带入到这个函数。

比如说,我们上面执行的都是系统的命令,假如程序替换我想替换一个我自己写的程序行不行呢?

示例:

我们自己写一个程序 mycmd

通过程序  test (process_replace.c) 创建子进程去调用我们自己写的程序mycmd

[wxq@VM-4-9-centos code_4_10_2]$ ll
total 36
-rw-rw-r-- 1 wxq wxq  125 Apr 10 22:25 Makefile
-rwxrwxr-x 1 wxq wxq 8456 Apr 10 22:32 mycmd
-rw-rw-r-- 1 wxq wxq  496 Apr 10 22:32 mycmd.c
-rw-rw-r-- 1 wxq wxq 1287 Apr 10 22:05 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 22:06 test
[wxq@VM-4-9-centos code_4_10_2]$ cat mycmd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("can not execute!\n");
        exit(-1);
    }
    else if(strcmp(argv[1], "-a") == 0)
    {
        printf("hello a!\n");
    }
    else if(strcmp(argv[1], "-b") == 0)
    {
        printf("hello b!\n");
    }
    else if(strcmp(argv[1], "-c") == 0)
    {
        printf("hello c!\n");
    }
    else
    {
        printf("default!\n");
    }

    return 0;
}
[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main()
{
    pid_t id = fork();

    if( id == 0 )
    {
        //child process
        printf("子进程开始运行, pid:%d\n", getpid());

        char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};

       //参数是列表形式
       // execl("/usr/bin/ls", "ls", "-l", NULL);
      
       // 参数的数组形式
       // execv("/usr/bin/ls", _argv);
        
        //不需要传入路径
        //execlp("ls", "ls", "-a", "-l", NULL);

       // execvp("ls", _argv);

        execl("/home/wxq/study_4/code_4_10_2/mycmd", "mycmd", "-a", NULL);
        
        exit(1);
    }
    else
    {
        printf("父进程开始运行, pid:%d\n", getppid());
        int status = 0;
        pid_t ret = waitpid(-1, &status, 0); //-1 :等待任意子进程
        if(ret > 0)
        {
            printf("waitpid success! exit code: %d\n", WEXITSTATUS(status));
        }

    }

    return 0;
}
[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test 
父进程开始运行, pid:16226
子进程开始运行, pid:15842
hello a!
waitpid success! exit code: 0

通过上面的测试,我们可以看到,我们自己写的程序是可以被程序替换,并且成功执行的。

上述逻辑:

ok,现在有了这个基础,我们再来聊一聊execle这个函数

接下来写的东西围绕着(execle函数在进行程序替换的时候,可以把环境变量传递到我们想要执行的程序里面)

意思就是我们在 process_replace.c中设置一个环境变量,然后传递给mycmd,看看mycmd是不是能使用这个环境变量。

第一步:

因为这里我们并没有设置环境变量,也没有传入,所以这里为空

第二步:在 process_replace.c 中添加环境变量,再使用execle进行程序替换

所以函数execle不仅调用了程序mycmd还给mycmd传入了环境变量
 


但是从严格意义上来说,上述关于程序替换的接口并不是操作系统提供给我们的,操作系统提供的接口只有一个:execve

NAME
       execve - execute program

SYNOPSIS
       #include <unistd.h>

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

这6个接口是经过封装提供给我们的,为的是满足不同的调用场景。底层都是execve这个接口

NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值