自学内容网 自学内容网

进程呼叫转移

进程不会消失,只会转移

把一个进程的温暖转移到另一个的胸膛~

让上个进程犯的错反省出梦想~

1e7a77934bfe451ba004f9b08ebeca40.jpg

 不扯皮了,来说进程替换

进程替换

现象

先来看看怎么个事吧:

 有个参数:...,是表示参数可变的意思

写段代码来解释下:

#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("testexec ... begin!\n");

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

    printf("testexec ... end!\n");
    return 0;
}

 我们会发现明明是自己的程序在跑,为什么执行起ls命令嘞

因为之前提过Linux设计的理念:一切皆文件

而ls命令本身也是拿C写的,在那个目录下的一个可执行程序

我们(进程)调用了execl函数,而这个函数可以帮助我们执行新的程序

原理

那到底怎么回事呢?

我们自己的可执行程序,在./运行之后就会变成进程

进程 = 内核数据结构 + 代码和数据

而进程程序替换就是把一个进程的代码和数据换掉

那么在替换的过程中有没有创建新进程呢?

并没有哦,只是拿老进程的壳子执行新进程的代码罢了

而站在被替换进程的角度,本质是这个程序被加载到内存了

可是一个程序怎么加载到内存啊?

别忘了exec*!它类似一种Linux上的加载函数

从外设拷贝到内存是操作系统的活

我们还会发现一个现象,就是后面并没再打印结束的函数了

这正常吗?

正常耶

exec*系列函数执行完毕之后代码就被替换啦

而execl的返回值我们并不关心,因为一旦替换成功,就不会向后继续运行了

而只要继续运行了,那就一定是替换失败了

多进程

将代码改成多进程版就是fork创建紫禁城,让紫禁城自己替换,父进程只需要等待就好了

看看失败的:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    printf("testexec ... begin!\n");

    pid_t id = fork();

    if (id == 0)
    {
        sleep(2);
        execl("/usr/bin/lsss", "lsss", "-l", "-a", NULL);
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));
    }
    printf("testexec ... end!\n");
    return 0;
}

紫禁城退出成功,退出码为1,替换失败

成功的是这样的:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
printf("testexec ...begin!\n");

pid_t id = fork();
if (id == 0)
{
sleep(2);
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
exit(1);
}

//father
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));
}
printf("testexec ... end!\n");
return 0;
}

(写路径的时候不要忘记根目录,我就是落了个/查半天...) 

 

以前创建紫禁城让它完成任务,是让紫禁城执行父进程代码的一部分,现如今可以让紫禁城执行一个全新的程序

程序有对应的数据和代码(从可执行程序来),创建紫禁城会有新的地址空间和页表

父子进程数据共享但是有独立性

替换本身是覆盖,紫禁城在数据部分进行写时拷贝,当执行新程序的时候新程序也要进行写时拷贝,所以此后父子间几乎完全独立(在数据结构和代码层面完全分离)

 

替换方法及函数参数含义
execl

上面说过一个替换方法:execl

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

康康 l 嘛意思:

l类似list,列表,如果参数为path,就需要带路径(别忘根目录,使用函数的人需要填)

剩下的...带选项,在命令行中怎样传参(依次传参很像list捏)就怎么写,结束时传NULL

路径表明我们想要执行谁,后面的选项表示我们想要怎样执行

execv

v是什么意思。。。很明显是vector总共就那点容器

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

前面的是路径,后面的是指针数组,execl的是可变参数,execv的传参需要传指针数组(自己构建)

char *const argv[] = {"ps", "-ef", NULL};
execv("/bin/ps", argv);
 execvp

带p的意思就是用户可以不用传要执行文件的路径(但要传文件名)

char *const argv[] = {"ps", "-ef", NULL};
execvp("ps", argv);

那它是怎么做到的呢?

p:查找这个程序,系统会自动在环境变量PATH中进行查找 

execlp

和上面规律差不多,自己悟去

execlp("ps", "ps", "-ef", NULL);
execvpe

带e的需要自己组装环境变量

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

上面的程序替换,替换的就是系统命令,所以肯定也可以替换我们自己写的程序

 下面就来替换段C嘎嘎代码(文件后缀: .cpp , .cc ,.cxx)

tips:别忘了makefile默认是从上到下形成可执行程序,所以第一个要是主程序,其他的依赖程序依次往下排哦

举个栗子,可以这样干:

.PHONY:all
all:testexec mypragma

mypragma:mypragma.cc
        g++ -o $@ $^ -std=c++11

testexec:testexec.c
        gcc -o $@ $^
.PHONY:clean
clean:
        rm -f testexec mypragma

 这是testexec.c:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
printf("testexec ...begin!\n");

pid_t id = fork();
if (id == 0)
{
sleep(2);
execl("./mypragma", "mypragma", NULL);
exit(1);
}

int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));
}
printf("testexec ... end!\n");
return 0;
}

 这是pragma:

#include<iostream>

using namespace std;

int main()
{
cout << "hello C++!" << endl;
cout << "hello C++!" << endl;
cout << "hello C++!" << endl;
cout << "hello C++!" << endl;
return 0;
}

 

 程序替换并未形成新进程,可以通过这个验证下,但是懒得验证啊都会吧应该唉算了还是替换下

这是mypragma.cc:

#include<iostream>
#include<unistd.h>

using namespace std;

int main()
{
        printf("I am a process,pid:%d\n", getpid());
        cout << "hello C++!" << endl;
        cout << "hello C++!" << endl;
        cout << "hello C++!" << endl;
        cout << "hello C++!" << endl;
        return 0;
}

 这是testexec.c:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
printf("testexec ...begin!\n");

pid_t id = fork();
if (id == 0)
{
sleep(2);
printf("I am a process,pid:%d\n", getpid());
execl("./mypragma", "mypragma", NULL);
exit(1);
}

int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));
}
printf("testexec ... end!\n");
return 0;
}

 makefile和之前一样不多说咯

哦对替换不止可以c和cpp,Python啥的也行呢,会就写呢

我写了一篇大概的Python基础语法:

我与Python的一夜情-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/chestnut_orenge/article/details/139599983这样运行(Python不是编译型语言):

python3 test.py

说过了Python3和Python2是运行环境,然后pycharm是集成环境 

就写了段简单的代码然后可以运行这样子:

s1 = "hello"
s2 = "world"
print(s1 + s2)

 

Python虽然不用编译(其他脚本语言也差不多都这样,Java也是半解释语言),但是它有解释器,解释器本身使用C和C++写的:

 

 然后刚不是说替换也可以替换成其他语言,看个Python的(因为我不会shell脚本编写):

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
printf("testexec ...begin!\n");

pid_t id = fork();
if (id == 0)
{
sleep(2);
printf("I am a process,pid:%d\n", getpid());
execl("/usr/bin/python3", "python3","test.py", NULL);
exit(1);
}

int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));
}
printf("testexec ... end!\n");
return 0;
}

 

有一种,殊途同归,的感觉 

不是等会不是说要说环境变量我怎么跑这么远

言归正传:

环境变量怎么传!这样传(先试试):

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
printf("testexec ...begin!\n");

pid_t id = fork();
if (id == 0)
{
sleep(2);
char* const argv[] = { (char*)"mypragma",NULL };
execvpe("./mypragma", argv, NULL);
exit(1);
}

int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));
}
printf("testexec ... end!\n");
return 0;
}

咳咳传空不是传啊

看看真的:

testexec.c:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
printf("testexec ...begin!\n");

pid_t id = fork();
if (id == 0)
{
sleep(2);
char *const argv[] = { (char*)"mypragma",NULL };
char* const envp[] = { (char*)"LIKE=521",(char*)"LOVE=1314",NULL };
execvpe("./mypragma", argv, envp);
exit(1);
}

int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));
}
printf("testexec ... end!\n");
return 0;
}

 mypragma.cc:

#include<iostream>

using namespace std;

int main(int argc,char *argv[],char *env[])
{
int i = 0;
for (; argv[i]; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}

printf("-------------------------------\n");

for (i = 0; env[i]; i++)
{
printf("env[%d]: %s\n", i, env[i]);
}

printf("-------------------------------\n");

cout << "hello C++!" << endl;
cout << "hello C++!" << endl;
cout << "hello C++!" << endl;
cout << "hello C++!" << endl;
return 0;
}

 哦?

 

我思故我在

 

父进程本身具有一批环境变量,这些环境变量来自爷进程bash~

可以这样获取爷进程Bash的环境变量,直接传:

extern char**environ;
execvpe("./mypragma" , argv, environ);

传参如果传自定义的,那就整体替换环境变量嘞

传环境变量可以用全新的,还可以用老的,如果老的想要修改之后再给紫禁城就可以这样:

这是putenv,它能帮你改改再传 

putenv("LOVE=1314");    //直接用就行,它自己会添加的

然后总结归类下 :

这是替换函数

#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[]);

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回

如果调用出错则返回-1 所以exec函数只有出错的返回值而没有成功的返回值

这是助记:

l(list) : 表示参数采用列表

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH

e(env) : 表示自己维护环境变量  

这是使用举例 :

#include <unistd.h>
 
int main()
{
 char *const argv[] = {"ps", "-ef", NULL};
 char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 
 execl("/bin/ps", "ps", "-ef", NULL);
 
 // 带p的,可以使用环境变量PATH,无需写全路径
 execlp("ps", "ps", "-ef", NULL);
 
 // 带e的,需要自己组装环境变量
 execle("ps", "ps", "-ef", NULL, envp);
 
 execv("/bin/ps", argv);
 
 // 带p的,可以使用环境变量PATH,无需写全路径
 execvp("ps", argv);
 
 // 带e的,需要自己组装环境变量
 execve("/bin/ps", argv, envp);
 
 exit(0);
}

tips:只有execve是真正的系统调用,其它五个函数最终都调用 execve(execve在man手册第2节,其它函数在man手册第3节)

拜拜~

UNO很好玩,串串很好吃,蛇蛇很好摸 

 


原文地址:https://blog.csdn.net/chestnut_orenge/article/details/140567959

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!