Linux进程替换


进程替换

进程替换的基本概念

父进程调用fork函数创建子进程,一般而言,父子进程代码共享,数据写时拷贝。即子进程只能执行父进程代码的一部分,这种情况下,一般使用if/else来控制父子进程各自能够执行的代码。现在想让子进程与父进程不是代码共享的,想让子进程执行一个全新的程序,有自己的代码和数据,就要使用进程替换来完成这个任务。

进程替换的概念:进程替换是指通过使用特定的系统调用接口,加载磁盘上一个程序的代码和数据到内存中,让进程的页表重新映射物理内存。

一般情况下父子进程的代码共享,数据写时拷贝:在这里插入图片描述

现在想要达到的效果是加载磁盘中一个程序的代码和数据到内存中,然后子进程的页表重新建立映射,不在使用父进程的代码和数据,达到进程替换的目的。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJKrQbs9-1664010129560)(C:\Users\19199\AppData\Roaming\Typora\typora-user-images\image-20220923144252180.png)]

进程替换不是创建一个新的进程,是把已有进程的页表重新建立映射关系。让子进程使用A的代码和数据,操作系统并没有给A建立它的内核数据结构。程序A要加载到内存中,需要借助加载器,对于一个c/c++文件,要想编译需要使用编译器。编辑文件需要使用编辑器,链接文件要链接器,把一个程序加载到内存中需要使用加载器。

进程替换需要使用到exec系列的函数,exec系列的函数可以把程序从磁盘加载到内存,并且完成进程替换的工作,exec系列函数的功能就是加载器,可以加载任何程序,包括系统的可执行二进制文件,例如可执行命令,也能加载我们自己写的执行程序。

exec系列函数

exec系列的函数有execl, execlp, execle, execv, execvp, execvpe 它们的头文件都是unistd.h

EXEC(3)
NAME
       execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS
#include <unistd.h>
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[]);

exec系列的函数会把命令行参数...(可变参数)指针数组的方式传递给main函数,这里的main函数指的是进程在调用exec系列函数后页表重新建立映射以后对应的代码和数据里的main函数。

execl

int execl(const char* path,const char* arg,...)参数...表示的是可变参数。execl的使用举例:

execl("usr/bin/ls","ls","-l","-a",NULL);//表示把ls这个可执行程序的代码和数据加载到内存,当前进程的页表重新映射,"-l","-a"是可变参数,可变参数以NULL结束。表示当前进程经过页表重新映射以后进程执行的任务是ls -l -a
execl("usr/bin/top","top",NULL);
int main()
{
    printf("开始\n");
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    printf("结束\n");
    return 0;
}

运行结果

[slowstep@localhost day06]$ ./mybin 
开始
total 20
drwxrwxr-x.  2 slowstep slowstep    49 Sep 23 15:27 .
drwxrwxr-x. 10 slowstep slowstep   118 Sep 23 14:52 ..
-rw-rw-r--.  1 slowstep slowstep   226 Sep 23 15:27 exec.c
-rw-rw-r--.  1 slowstep slowstep    63 Sep 23 15:07 makefile
-rwxrwxr-x.  1 slowstep slowstep 11024 Sep 23 15:27 mybin

在调用execl以后,进程的代码和数据全部被替换,包括进程已经被执行的代码和数据都会被替换,"开始"能被打印出来的原因是execl是在第一个printf之后才调用的。

execl函数一旦执行成功,该进程后续的所有代码都不会在被执行。execl函数调用失败返回-1,调用成功没有返回值,也不需要返回值,因为execl函数一旦调用成功,该进程的所有代码和数据都会被替换,包括调用的execl本身也会被替换。所以execl函数调用成功没有返回值。

使用execl也可以替换自己写的程序,也可以使用c语言调用fork创建子进程,让子进程使用execl函数把自己的页表映射到其它程序的代码和数据上,并执行这些程序。

test.c

int main(int argc, char *argv[], char *env[])
{
    printf("这是新功能\n");
    if (strcmp(argv[1], "-a") == 0)
        printf("提供-a参数的功能\n");
    if (strcmp(argv[2], "-b") == 0)
        printf("提供-b参数的功能\n");
    return 0;
}
execl("/home/slowstep/mydir/day06/test.out", "./test.out", "-a", "-b", NULL);

一般使用execl的方法都是父进程创建子进程,让子进程去调用execl函数。父进程通过创建子进程,并让子进程调用execl函数,可以让子进程的代码和数据进行替换,执行其它任务,父进程则可以专注于读数据,取数据和解析数据。

子进程调用execl函数加载新程序,会实现父子进程代码和数据的分离。一般情况下,父子进程代码共享,数据写时拷贝。但是调用execl可以把父子进程的代码和数据都分离。

execv

int execv(const char* path,char* const argv[]),execv函数的使用与execl类似,execl的’l’可以理解为list,表示参数在execl函数的参数列表一串传进去。execv的’v’可以理解为vector,表示把参数以数组的方式传进去。execl与execv的区别在于传参的方式不同

char* _argv[]={"ls","--color=auto","-l","-a",NULL};
execv("usr/bin/ls",_argv);

execlp

int execlp(const char* file,const char*arg,...)p表示只需要说明可执行程序的名称,不用指定路径,会自动到环境变量中去找,p表示PATH,一般使用exec系列的函数替换系统的可执行程序使用execlp.

execlp("ls","ls","--color=auto","-l","-a");

execvp

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

char* _argv[]={"ls","--color=auto","-i","-a","-l",NULL};
execvp("ls",_argv);

execle

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

参数envp表示环境变量。execle会把命令行参数和环境变量进行传递。execl,execv,execlp,execvp只会把命令行参数进行传递。execle中最后一个’e’指的是env环境变量。

int main(int argc,char* argv[],char* env[])
{
    char* myenv[]={"slowstep=100","SLOWSTEP=20",NULL};
    execle("./a.out","a.out","-a",NULL,myenv);//环境变量具有具有全局属性的原因是execle把main函数中的env环境变量传递了下去
    exit(0);
}

execvpe

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

char* _argv[]={"ls","--color=auto","-l","-a",NULL};
char* _envp[]={"SLOWSTEP=15","slowstep=20"};
execvpe("ls",_argv,_envp);

execle,execvpe,最后一个字母是e表示需要自己维护环境变量。环境变量具有全局属性,本质上是因为在调用该函数的时候把环境变量作为参数传递了下去。

execve才是系统调用

execl,execlp,execle,execv,execvp,execvpe都不是系统调用,而是对系统调用的封装。execve才是系统调用。int execve(const char* filename,char* const argv[],char* const envp[]),对execve系统调用接口的封装是为了满足上层不同的调用场景。

实现一个简单地shell

原理:父进程创建子进程,让子进程执行各种命令。父进程等待子进程并且进行解析。

#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define NUM 1024
#define SEP " " //定义空格为分割标志
int main()
{
    while (1) //命令行解释器一定是一个死循环
    {
        printf("[root@localhost root]# ");
        fflush(stdout);             //刷新到屏幕
        static char str[NUM] = {0}; //存放读取的字符串
        memset(str, 0, sizeof str);
        while (fgets(str, sizeof str, stdin) == NULL) //不使用scanf,scanf不能读取空格
            continue;                                 // fgets读取失败会返回NULL
        str[strlen(str) - 1] = 0;                     // fgets会读取\n,要把最后的\n变成\0    ls\n
        static char *_argc[] = {0};                   //用来保存解析的字符串
        memset(_argc, 0, sizeof _argc);
        int index = 0;
        _argc[index++] = strtok(str, SEP); //使用strtok拆分串  "ls -a -l" -> "ls" "-a" "-l"
        if (strcmp(_argc[0], "ls") == 0)
            _argc[index++] = "--color=auto";
        if (strcmp(_argc[0], "ll") == 0)
        {
            _argc[--index] = "ls";
            index++;
            _argc[index++] = "-l";
            _argc[index++] = "--color=auto";
        }
        while (_argc[index++] = strtok(NULL, SEP))
            ;
        if (strcmp(_argc[0], "cd") == 0) //子进程进行cd父进程目录没有发生变化
        {
            chdir(_argc[1]); //使父进程目录变化
            continue;
        }
        pid_t id = fork();
        if (id == -1)
            exit(0);
        else if (id == 0)            //让子进程执行各种命令
            execvp(_argc[0], _argc); //选择execvp函数,直接到环境变量中去找
        else                         //每一次父进程负责回收子进程
        {
            int status = 0;
            pid_t ret = waitpid(id, &status, 0); //父进程要调用waitpid等待回收子进程,阻塞式等待
        }
    }
    exit(0);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值