• 【Linux】进程替换


    img

    Halo,这里是Ppeua。平时主要更新C语言,C++,数据结构算法…感兴趣就关注我吧!你定不会失望。


    在这里插入图片描述# 0. 进程替换概念

    我们想要在一个进程中的子进程运行外部程序,就可以用到进程替换的相关接口.

    系统对于此提供了exec类的相关接口.

    相关接口有6个,不过都具有一定的使用规则.

    image-20231116081306274

    先来看看单进程版的进程替换

    #include
    #include
    #include
    #include
    int main()
    {
        printf("before i am a process,pid:%d,ppid:%d\n",getpid(),getppid());   
        execl("/usr/bin/ls","ls","-a","-l",NULL);
        printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    编译运行会发生什么呢?

    image-20231116081848443

    执行到execl前的程序是正常运行的,也就是正常打印出了进程PID相关信息.

    后来执行到了execl,如我们猜测的一样执行了 ls -al 的指令

    但是!!后来的 **printf(“after i am a process,pid:%d,ppid:%d\n”,getpid(),getppid()); **这条指令并未执行.

    这是我们没有想到的?这是为什么呢?

    回顾我们的标题名,叫做进程替换.显然这里发生了进程的替换.

    8df75dddf104585179d4c2eed3338dd

    一个文件未被运行时其在磁盘当中,execl把内存当中的ls可执行程序,对内存中的./proce可执行程序进行了一个替换.不创建新的进程,只进行代码和数据的替换

    注意!这里并未重新创建一个进程,而是在原有的进程上进行了夺舍,也就是运行了exec*指令后,进程还是那个进程,内容却不再是原来的内容了(肉体仍旧,灵魂改变).

    所以替换成功后,整个程序改变了,那么这个函数有返回值嘛

    运行成功后数据替换,就算返回了也被换掉,所以执行成功是没有返回值的,执行失败则返回-1

    现在我们大概能知道什么是进程替换了.我们在看看在多进程状态下,进程替换会发生什么呢?

    #include
    #include
    #include
    #include
    #include
    int main(int argc,char *argv[])
    {
        printf("before i am a process,pid:%d,ppid:%d\n",getpid(),getppid());   
        pid_t ret=fork();
        
        if(ret == 0)
        {
            printf("before i am a child process,pid:%d,ppid:%d\n",getpid(),getppid());   
            execl("/usr/bin/ls","ls","-a","-l",NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        
        else
        {
            int ret=wait(NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20231116084200236

    子进程执行了execl指令,完成了进程替换,而父进程照常运行自己剩下的指令.

    那么,这几个接口有什么含义呢.以及那个命令为什么要这样写呢?

    1. 进程替换库函数接口

    exec*相关接口使用需包含两个库

    #include
    #include
    
    • 1
    • 2

    其中函数的命名是有一定的规律的

    1. l:list
    2. p:path
    3. v:vector
    4. e:environ

    execl与execv

    观察六个函数我们发现,前缀要么是 execl,要么是 execv,这具体含义是

    l: list 列表 v: vector 数组

    也就是

    execl("可执行文件所在路径","文件运行时指令","NULL")
    
    • 1

    文件运行指令在屏幕上输入什么,就在这里输入了什么

    • 例如: ls -al 我们在屏幕上需要输入这两个指令+参数.所以而ls所在路径为 /usr/bin/ls,最后以NULL结尾

      execl("/usr/bin/ls","ls","-al",NULL);
      
      • 1

    同样也可以运行我们自己的可执行程序 并不仅限于C,可以是任何可执行程序!

    test.c:

    #include
    int main()
    {
        printf("hello\n");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    proce.c:

    #include
    #include
    #include
    #include
    #include
    int main(int argc,char *argv[])
    {
        printf("before i am a process,pid:%d,ppid:%d\n",getpid(),getppid());   
        pid_t ret=fork();
        
        if(ret == 0)
        {
            printf("before i am a child process,pid:%d,ppid:%d\n",getpid(),getppid());   
            execl("./test","test",NULL); //可以为相对路径也可以为绝对路径
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        
        else
        {
            int ret=wait(NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20231116085803827

    如何用makefile同时编译多文件

    test:test.c
    	gcc -o $@ $^ -std=c99
    .PHONY:clean
    clean:
    	rm -rf proce test
    
    • 1
    • 2
    • 3
    • 4
    • 5

    通常我们编译单文件是这样.

    Makefile会自顶向下执行检测到的第一个依赖项,然后完成编译,不会编译其他依赖项

    也就是如果想要这样编译多文件是没有用的

    proce:proce.c
    	gcc -o $@ $^ -std=c99
    test:test.c
    	gcc -o $@ $^ -std=c99
    .PHONY:clean
    clean:
    	rm -rf proce test
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们需要添加一个伪目标:

    .PHONY:all
    all:proce test
    
    proce:proce.c
    	gcc -o $@ $^ -std=c99
    test:test.c
    	gcc -o $@ $^ -std=c99
    .PHONY:clean
    clean:
    	rm -rf proce test
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    回到上面,我们看过了L如何使用,那么V呢?

    其实这两个差不多,我们需要手动创建一个字符数组,将上面的指令放到数组中即可.最后同样需要以NULL结尾

    #include
    #include
    #include
    #include
    #include
    int main(int argc,char *argv[])
    {
        printf("before i am a process,pid:%d,ppid:%d\n",getpid(),getppid());   
        pid_t ret=fork();
        char * myargv[]=
        {
            "ls",
            "-a",
            "-l",
            NULL
        };
        if(ret == 0)
        {
            printf("before i am a child process,pid:%d,ppid:%d\n",getpid(),getppid());   
            // execl("./test","test",NULL);
            execv("/usr/bin/ls",myargv);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        
        else
        {
            int ret=wait(NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    image-20231116090741443

    execlp与execvp

    这个P的概念是,系统会从默认的**$PATH**中去寻找可执行程序,不需要再写路径.

    这里以execlp举例.

    #include
    #include
    #include
    #include
    #include
    int main(int argc,char *argv[])
    {
        printf("before i am a process,pid:%d,ppid:%d\n",getpid(),getppid());   
        pid_t ret=fork();
        if(ret == 0)
        {
            printf("before i am a child process,pid:%d,ppid:%d\n",getpid(),getppid());   
            execlp("ls","ls","-a","-l",NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        else
        {
            int ret=wait(NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    **execlp(“ls”,“ls”,“-a”,“-l”,NULL)**这里的第一个ls表示要在PATH中寻找哪个执行程序,后面依然是参数列表.

    execle与execvpe

    多了一个e的选项:表示可以传递的环境变量列表.

    我们用自己创建的程序来验证一下这个信息.

    #include
    #include
    int main(int argc,char * argv[],char * environ[])
    {
        for(int i=0;environ[i];i++)
        {
            printf("i -> %d :,environ: %s\n",i,environ[i]);
        }
        printf("end\n");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    #include
    #include
    #include
    #include
    #include
    int main(int argc,char *argv[])
    {
        printf("before i am a process,pid:%d,ppid:%d\n",getpid(),getppid());   
        pid_t ret=fork();
        char *environ[]={
            "hello=123",
            "Linux",
            "execle",
            NULL
        };
        if(ret == 0)
        {
            printf("before i am a child process,pid:%d,ppid:%d\n",getpid(),getppid());   
            execle("./test","test",NULL,environ);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        else
        {
            int ret=wait(NULL);
            printf("after i am a process,pid:%d,ppid:%d\n",getpid(),getppid());
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    image-20231116092051565

    现在我们有三种形式向子进程中传入环境变量

    1. 不传入,直接使用全局的环境变量
    2. 在父进程中使用putenv(),追加形式向子进程传入环境变量
    3. 使用自定义的环境变量传参

    2. 进程替换系统调用接口

    image-20231116125255627

    上方提到的六个库函数都是调用此函数

    image-20230905164632777

  • 相关阅读:
    第三十一章 使用带附件的 SOAP
    【C++】stack和queue
    HCIE-Security Day47:AC准入控制MAC
    TikTok的AI技术:智能推荐的幕后机制
    比 BeanUtils 更快的实体映射工具 MapStruct 原理
    【C#】async和await
    SQL面试题之区间合并问题
    【电商数仓】数仓搭建之DIM维度层(商品、优惠券、活动、地区、时间维度表)
    数据分析与数据挖掘研究之一
    代码随想录算法训练营第六十五天 | 岛屿数量 深搜、岛屿数量 广搜、岛屿的最大面积
  • 原文地址:https://blog.csdn.net/qq_62839589/article/details/134439318