当前位置: 首页 > backend >正文

7. 进程控制-进程替换

目录

1. 进程替换

1.1 单进程版:

1.2 进程替换的原理

1.3 多进程版-验证各种程序替换接口

2. 进程替换的各种接口

2.1 execl

2.2 execlp

2.3 execv

2.4 execvp

2.5 execle


 

1. 进程替换

上图为程序替换的接口,之后会详细介绍。

1.1 单进程版:

        最简单的看看程序替换的效果:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>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;
}

        我们可以看到,before...执行了,然后执行了execl里的内容,最重要的是after...的内容没有被执行。这就叫程序替换。

1.2 进程替换的原理

       让我们分析一下上面代码的执行过程,刚开始肯定执行本代码,也就是before,然后execl,关键就在于这个接口,这个接口是执行了 ls -l -a,但并不是创建新进程去执行这个指令,而是在这个进程上,直接替换代码和数据,从新程序的main处开始运行,新程序的code和data替换掉老的。

        也就是说我们运行起来的 myproc 这个进程,在运行到execl时,这个进程的代码和数据被换成了 execl 中的命令内容的代码和数据。

1.3 多进程版-验证各种程序替换接口

int main()
{//多进程版的程序替换pid_t id = fork();if(id == 0){printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());sleep(5);execl("/usr/bin/top", "top", NULL);printf("after: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());exit(0);}//fatherpid_t ret = waitpid(id, NULL, 0);if(ret > 0)printf("wait success, father pid: %d, ret id: %d\n", getpid(), ret);sleep(5);return 0;

程序一运行起来就有两个进程,其中一个是子进程,替换后就结束了,然后父进程5秒后也退出。

有几个现象结论:

  1. 子进程执行程序替换,不会影响父进程父进程在等待子进程结束,没有直接top完就退出
  2. execl时,替换数据时发生写时拷贝,此时替换的代码也发生了写时拷贝!子进程的代码被替换,但是父进程的还在执行。
  3. 程序替换有无创建新进程呢?进程最多都是两个,所以没有!

程序替换成功之后,exec*之后的代码不会被执行;如果替换失败呢?才可能执行后续的代码,所以exec*系列的函数,只有失败返回值,没有成功返回值,因为成功了就替换成功了,显示被替换后的代码的结果。

我们的CPU如何知道程序的入口地址在哪?代码区这么大,程序替换后的main在哪里呢?

2. 进程替换的各种接口

通过对接口参数的学习,我们可以回答上面的问题。

文章一开头的图片就是这一系列的各种接口,有6个,但是其实有7个,剩下那个不在这个手册里。

我们这里介绍5个,剩下的就都知道怎么用了。

2.1 execl

exec是一样的,l 代指 list。

所以这一系列的函数的第一个参数:你要执行一个程序的第一件事情是什么?就是先找到这个程序!这个参数就是决定如何找到程序的,什么路径下的什么程序!

找到这个程序之后,接下来要怎么办?就是如何执行这个程序,主要就是要不要涵盖选项,涵盖哪些?所以从第二个参数开始,命令行中怎么输入,就怎么输入!只不过都要加上""

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

最后的NULL是必须要加的。

2.2 execlp

        我们发现它的第一个参数是file,不是path,说明第一个参数只用写明文件就行,不需要指明路径(当然指明也是可以的),它会自动去PATH环境变量中去找这个指令。

execlp("ls", "ls", "-l", NULL);

2.3 execv

        这个接口的最后一个是 v,表示vector,这种不外乎是将传参的形式改变了,之前是传list,现在是传个vector。

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

exec 会在内部把命令行参数传给ls,exec就是代码层面的加载器,把可执行程序导到内存里。

2.4 execvp

一带p就不用写path,只写file就可以。带v就是把传的参数列表换成vector

2.5 execle

这个e是我们没见过的,它的意思是环境变量,意味着我们可以自己往里传环境变量,前面的命令行参数我们知道可以在内部传给指令,那这个环境变量会传给替换后的代码吗?

在验证这个问题之前,先来讲一些前置知识:

1. 怎么用一个makefile生成两个可执行文件?

我们都知道makefile会把它碰到的第一个文件定位目标文件,那怎么能有两个目标文件呢?总得有个先后吧,所以我们可以这样处理:

.PHONY:all
all:otherExe mycommandotherExe:otherExe.cppg++ -o $@ $^ -std=c++11
mycommand:mycommand.cgcc -o $@ $^ std=c99
.PHONY:clean
clean:rm -f mycommand otherExe

接下来我们实现一下:一个可执行程序调用另一个可执行程序

mycommand.c 中可以通过exec系列接口调用其他可执行程序,当然这个可执行程序也得是编译好的,所以需要一个makefile编译两个文件。

int main()
{pid_t id = fork();if(id == 0){printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());execl("./otherExe", "otherExe", NULL);}return 0;
}

这里有一个小问题:我们在调用 otherExe时,是 ./otherExe 这样执行,为什么我们传的第二个参数是otherExe,而不是 ./otherExe 呢?

  • 因为前面已经说明它在哪里了,这里就不用再写一遍了

既然我一个C程序可以调用C++程序,那能不能调用其他语言写的程序呢?

让我们来验证一下:

脚本语言(所谓的脚本语言就是bash从文件中一行一行的执行):test.sh:

#!/usr/bin/bashecho "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
echo "hello 1"
ls -a -l

要注意直接./test.sh是不能执行的。

int main()
{pid_t id = fork();if(id == 0){printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());//execl("./otherExe", "otherExe", NULL);execl("/usr/bin/bash", "bash", "test.sh", NULL);}return 0;
}

这样就可以在C程序中执行脚本代码,还可以发现其他什么 .py 这种python文件也一样可以运行。

无论是我们的可执行程序,还是脚本,为什么能跨语言调用呢???

这是因为:所有的语言写成的运行起来,本质都是进程!!!只要是进程,就能用exec系列接口调用

回到正题:将这个的目的就是为了验证execle时,要传环境变量,因为我们自己写的程序是可以让他输出环境变量、命令行参数的,所以要用我们自己写的程序验证是否传入成功。

这时就需要观察:由一个程序形成的环境变量如何导给另一个程序(需要我们自己写程序并且一个调用另一个)

先来验证一下命令行参数:我们前面的结论说,命令行参数会在接口内部传给那个命令。

mycommand.c:

int main()
{pid_t id = fork();if(id == 0){printf("before: i am a process, pid: %d, ppid: %d\n", getpid(), getppid());char *const myargv[] = {"otherExe","-a","-b","-c",NULL};execv("./otherExe", myargv);}return 0;
}

它会把这些个命令行参数传给 otherExe,然后otherExe.cpp:

int main(int argc, char *argv[])
{cout << argv[0] << " begin running" << endl;for(int i=0; argv[i]; i++){cout << i << ":" << argv[i] << endl;}cout << argv[0] << " stop running" << endl;return 0;
}

他会接收到命令行参数,然后打印出来,事实上的效果:

可以看到命令行参数确实传递成功了。

接下来验证一下环境变量:

将otherExe修改成:

int main(int argc, char *argv[], char* env[])
{cout << argv[0] << " begin running" << endl;cout << "这是命令行参数" << endl;for(int i=0; argv[i]; i++){cout << i << ":" << argv[i] << endl;}cout << "这是环境变量" << endl;for(int i=0; env[i]; i++){cout << i << ":" << env[i] << endl;}cout << argv[0] << " stop running" << endl;return 0;
}

我们可以看到:我们调用execv,只传了命令行参数,都没传环境变量,就能打印出来。

环境变量是什么时候给进程的呢?

环境变量也是数据,在创建子进程的时候,环境变量已经被子进程继承下去了!!!

所以即便没传,也有上面继承下来的环境变量。

所以,程序替换中,环境变量信息不会被替换!会一路继承。

bash创建一个子进程,会传给它环境变量,./mycommand也会创建进程,程序替换后调用./otherExe,也会创建进程,一路继承。

如果不想让bash创建环境变量,非要在mycommand中创建环境变量也是可以的:

  • putenv("PRIVATE_ENV=2025516");
  • extern char** environ;
    execle("./otherExe", "otherExe", "-a", "-b", NULL, environ);

    这个environ就是父进程环境变量

自定义环境变量也是可以的,但是这样传过去就只有自己定义的环境变量了,其他的就看不到了。

所以我们想给子进程传递环境变量,该怎么传递??

  1. 新增环境变量
    1. 父进程地址空间中直接putenv
    2. bash直接添加
  2. 彻底替换
http://www.xdnf.cn/news/6761.html

相关文章:

  • WebGIS开发智慧机场项目实战(2)
  • 前端学习(4)—— JavaScript(基础语法)
  • 循环嵌套与枚举算法
  • C41-为什么要用指针
  • 后端框架(3):Spring(1)
  • 【技术原理】ELK技术栈的历史沿革与技术演进
  • Linux——一键部署应用脚本
  • 方法区与元空间解析
  • 软件架构风格系列(2):面向对象架构
  • (网络文件系统)N
  • 本地部署Scratch在线编辑器
  • Ngrok 配置:实现 Uniapp 前后端项目内网穿透
  • Recycling Krylov Subspace 方法解释与开源实现
  • 【Arthas实战】常见使用场景与命令分享
  • 电子电路:电容在电子电路中到底发挥着什么作用?
  • Unity 批量将图片从默认类型改为Sprite类型
  • 数字金融发展对商业银行信用风险的影响研究(stata分析范文)
  • 描述性统计图表
  • HC32L190 ADC采集
  • firewall防火墙
  • 前端方法的总结及记录
  • 隧道结构安全在线监测系统解决方案
  • 探秘雷克赛恩生产基地:解码国产投影技术深耕之路
  • 动态规划-63.不同路径II-力扣(LeetCode)
  • 操作系统知识总结
  • 丝杆升降机最大载荷的工程力学解析与选型实践
  • 懒汉式单例模式的线程安全实现
  • ros2中自定义的package查不到?
  • 事件响应策略规范模版
  • 基于Unity的简单2D游戏开发