Linux | 深入探究Linux进程控制:从fork函数到进程等待再到进程替换

目录

1、进程的创建:fork函数

2、父子进程的奇怪现象:为什么同一个地址有不同的值?——区分内存的虚拟地址和物理地址

代码:利用fork函数的返回值进行父子进程分流,执行不同的代码块

虚拟地址和物理地址:

fork调用和地址空间的关系:

3、进程的终止

进程终止的三种方式:return、exit 和 _exit

return

exit

_exit

注意:

进程退出码

4、进程等待:wait和waitpid

进程等待作用概述:

进程等待的方法:

wait 函数:

waitpid 函数:

进程退出的场景:

status

WIFEXITED(status)

WEXITSTATUS(status)

示例:

5、进程程序替换:exec函数族

函数声明:

系列函数的命名规则:

示例:


Linux操作系统以其强大的多任务处理能力和丰富的系统调用而著称。在Linux中,进程是执行中程序的实例,而进程控制是操作系统中一个至关重要的概念。本文将深入探讨Linux进程的创建、终止以及进程间的通信方式,并通过实现一个简单的shell程序来加深理解。

1、进程的创建:fork函数

在Linux中,fork函数是创建新进程的核心。当一个进程调用fork时,它会复制自己,生成一个几乎完全相同的副本,即子进程。这个子进程具有父进程的内存、文件描述符等资源的副本。fork函数的返回值在父进程中是子进程的PID,在子进程中则是0。

#include <sys/types.h>
#include <unistd.h>pid_t fork(void);

创建新的子进程通常有一下两种用法:

  1. 利用fork函数的返回值进行父子进程分流,执行不同的代码块。
  2. 进程替换,执行不同的程序。

fork() 函数的返回值有三种情况:

  1. 在父进程中:如果 fork() 成功,它将返回新创建的子进程的PID。这个值是一个正整数。
  2. 在子进程中:如果 fork() 成功,它将返回0。子进程认为自己是新创建的,并且它的PID是0。
  3. 在出错时:如果 fork() 调用失败,它将在父进程中返回-1,并设置全局变量 errno 以指示错误的原因。常见的错误原因包括但不限于:
    • EAGAIN:系统达到其进程数量的最大值,无法创建更多的进程。
    • ENOMEM:系统内存不足,无法分配足够的内存来创建新的进程。

示例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main() {pid_t pid = fork();  // 创建子进程if (pid < 0) {// fork失败fprintf(stderr, "Fork failed");return 1;} else if (pid == 0) {// 子进程printf("I am the child process, PID: %d\n", getpid());} else {// 父进程printf("I am the parent process, PID: %d, Child PID: %d\n", getpid(), pid);}return 0;
}

2ad484d3dc0a5f1c01c5429e2e417c86.png

2、父子进程的奇怪现象:为什么同一个地址有不同的值?——区分内存的虚拟地址和物理地址

代码:利用fork函数的返回值进行父子进程分流,执行不同的代码块

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{   int x = 1000;pid_t id = fork();if(id < 0){printf("fork() error\n");return -1;}else if(id == 0){// childx = 10086; // 子进程修改x的值(其实就是修改对应内存啦~)printf("child: x = %d, &x->%p\n", x, &x);}else{// father 父进程没有就该x的值printf("father: x = %d, &x->%p\n", x, &x);}return 0;
}

运行结果:

d2b254cafeff531f8ea9f6ac65352250.png

虚拟地址和物理地址:

  1. 虚拟地址:每个进程都有自己的虚拟地址空间,这是操作系统为进程提供的抽象。虚拟地址空间中的地址可以通过内存管理单元(MMU)转换为物理地址。
  2. 物理地址:物理地址是实际存储在内存芯片上的地址。物理内存是有限的,而虚拟地址空间通常远大于物理内存。

解释:在子进程中,x 的值被修改为 10086。这个修改会让操作系统对子进程分配新的虚拟地址和物理地址的映射关系,(也就是新的页表~),最终只影响子进程的虚拟地址空间对应的新的物理内存区域,而虚拟地址不变(相同也不要紧)。也就是说,C语言中的地址其实是虚拟地址,不是真实的物理地址!!!

fork调用和地址空间的关系:

  • fork() 被调用时,子进程获得父进程的虚拟地址空间的副本。这意味着子进程有自己的虚拟地址到物理地址的映射,但是初始时这个映射与父进程相同。
  • 子进程对虚拟地址空间的写操作会导致写时拷贝(copy-on-write, COW)。如果子进程尝试修改其地址空间中的任何页面,操作系统会首先为该页面创建一个新的物理页面,然后将该页面的内容复制到新的物理页面,并将子进程的虚拟地址映射到这个新的物理页面。

90645dd13257a0aef821616c4f6d6dcd.png

Linux中的写时拷贝机制是一种优化内存使用的技术。在父子进程共享数据时,只有当数据被修改时,才会为修改它的进程创建数据的副本。这减少了不必要的内存复制,提高了效率。(原理就是在实际场景中,读数据频率远大于写数据,比如你看的博客数量一定大于你写不博客的数量~)

3、进程的终止

进程的终止可以是正常或异常的。正常终止可以通过从main函数返回、调用exit函数或_exit函数来实现。exit函数在终止前会执行一些清理工作,如关闭文件描述符、执行注册的退出处理函数等。而_exit函数则直接终止进程,不进行任何清理。

进程终止的三种方式:returnexit_exit

return

    • return 是一个C语言关键字,用于从函数返回到调用者。
    • 它通常用于返回一个值给调用函数,并且可以用于任何函数,包括 main 函数。
    • 当在 main 函数中使用 return 语句时,程序会正常终止,并且返回一个退出状态码给操作系统。
    • return 可以带一个整数值,这个值通常用于表示程序的退出状态,其中 0 通常表示成功,非零值表示错误或异常退出。

示例:

int main() {// ...return 0; // 正常退出,返回状态码0
}

exit

    • exit 是C标准库函数,定义在 <stdlib.h> 头文件中。
    • exit 用于立即终止程序的执行,并返回一个状态码给操作系统。
    • exit 调用会进行一些清理操作,比如关闭所有打开的文件描述符、刷新标准I/O缓冲区、调用注册的退出处理函数等。
    • exit 同样接受一个整数值作为参数,表示退出状态码。

示例:

#include <stdlib.h>int main() {// ...exit(0); // 正常退出,返回状态码0
}

_exit

    • _exit 是一个原始的系统调用,通常定义在 <unistd.h> 头文件中。
    • _exit 用于立即终止程序的执行,并返回一个状态码给操作系统,但它不会执行任何清理操作。
    • exit 不同,_exit 不会刷新标准I/O缓冲区或调用任何退出处理函数,它直接终止进程。
    • 这使得 _exitexit 快,但只应在确定不需要进行任何清理工作时使用。

示例:

#include <unistd.h>int main() {// ..._exit(0); // 立即退出,不进行任何清理工作
}

注意:

  • return 是C语言关键字,用于从函数返回,包括 main 函数。
  • exit 是C标准库函数,用于正常退出程序,并进行必要的清理工作。
  • _exit 是系统调用,用于立即退出程序,不进行任何清理工作,通常用于紧急情况或确定不需要清理的场景。
  • return n 等价于 exit(n)
  • C标准库中的exit 在实现上,调用系统 _exit 接口之后,还会执行刷新缓冲区数据等善后操作。我们也可以从中发现,C语言的缓冲区不是在系统内部的,而是C语言自身开辟管理的空间。(因为系统提供的_exit不刷新缓冲区)。

8bd8c314d8aa6537f6379e18042ced4e.png

int main()
{printf("this is exit()\n");exit(0);
}
运行结果:
[lhy@localhost linux]# ./a.out
[lhy@localhost linux]# this is exit()int main()
{printf("this is _exit()\n");_exit(0);
}
运行结果:
[lhy@localhost linux]# ./a.out
[lhy@localhost linux]#// 这里因为退出后缓冲区没被刷新 所以没有显示print内容

进程退出码

进程的退出码是一个重要的状态指示,它告诉父进程或操作系统进程是如何终止的(就是你return 或者 exit 后面带的值)。退出码可以通过在终端中使用$?来查看。

11215a7f85e1c8d08744d84c31a557c0.png

4、进程等待:wait和waitpid

为了防止僵尸进程的产生并回收子进程资源,同时子进程来获取其退出状态,了解子进程是否成功完成任务,以及任务执行的结果。父进程需要等待子进程的结束。waitwaitpid函数允许父进程等待子进程的终止,并获取其退出状态。

对僵尸进程没有概念的同学可以参考这篇博客~

Linux | Linux进程万字全解:内核原理、进程状态转换、优先级调度策略与环境变量-CSDN博客

进程等待作用概述:

  1. 避免僵尸进程:当子进程完成其任务并退出时,它会转变为僵尸进程,等待父进程回收其资源。如果父进程不执行等待操作,子进程的进程描述符和部分资源不会被释放,导致系统资源浪费。
  2. 资源回收:父进程通过等待子进程,可以回收子进程使用的资源,包括内存、文件描述符等,确保系统资源的有效利用。
  3. 获取子进程状态:父进程可以通过等待子进程来获取其退出状态,了解子进程是否成功完成任务,以及任务执行的结果。
  4. 异常处理:如果子进程因为错误或异常退出,父进程可以通过等待子进程来获取这些信息,并进行相应的异常处理。
  5. 同步执行:在某些情况下,父进程可能需要等待所有子进程完成其任务后才能继续执行,进程等待提供了一种同步机制。

进程等待的方法:

waitwaitpid 是两个在 Unix 和类 Unix 系统中用于父进程等待子进程状态的系统调用。它们允许父进程获取子进程的退出状态,并在子进程退出后回收其资源。

wait 函数:

  • 函数原型
#include <sys/wait.h>
pid_t wait(int *status);
  • 功能wait 函数挂起父进程的执行,直到有一个子进程退出。如果有多个子进程已经退出,wait 将唤醒并返回第一个退出的子进程的信息。
  • 参数status 是一个输出型参数,用指向整数的指针来接收子进程的退出状态。
  • 返回值:返回终止的子进程的 PID。如果出现错误,返回 -1 并设置 errno
  • 特点wait 函数只能等待任何单个子进程的退出,不能指定等待特定的子进程。

waitpid 函数:

  • 函数原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
  • 功能waitpid 函数允许父进程等待指定的子进程 pid 退出。与 wait 不同,waitpid 可以等待特定的子进程。当pid参数设置为-1是,等待任何子进程。
  • 返回值:返回终止的子进程的 PID。如果指定的子进程没有退出,且 options 包含 WNOHANG,则返回 0。如果出现错误,返回 -1 并设置 errno
  • 参数
    • pid:指定要等待的子进程的 PID。如果设置为 -1,则等待任何子进程。
    • status:与 wait 相同,用于接收子进程的退出状态。
    • options:指定等待操作的一些选项,常用的选项有 WNOHANGWUNTRACED,也可以设置为0表示阻塞等待进程退出。

options解释:

0 :如果设置此选项,跟wait函数效果相同,阻塞等待进程退出。

WNOHANG:

  • WNOHANG 选项使得 waitpid 调用变为非阻塞。如果没有任何子进程已经终止,waitpid 会立即返回而不是挂起调用进程的执行。这种模式下,如果子进程尚未结束,waitpid 返回0,而不是等待子进程结束 。
  • 例如,在服务器程序中,你可能希望在服务请求的同时检查子进程的状态。使用 WNOHANG 可以避免服务器因为等待子进程而停止处理其他请求 。

WUNTRACED:

  • WUNTRACED 选项允许 waitpid 报告已经停止的子进程,而不仅仅是已经终止的子进程。这通常用于调试目的,当子进程被其他进程(如调试器)暂停时,waitpid 可以报告这些信息 。
  • 这个选项对于常规应用程序来说使用较少,主要是在需要跟踪子进程状态的特定场景下使用 。

进程退出的场景:

1、 运行完毕,结果正确——没人关心

2、运行完毕,结果错误——看错误码

3、根本没运行完就崩了——错误码失效

注意:当进程非正常退出,退出码已经失去原有意义!!!

比如当进程空指针直接错误终止返回,此时退出码没有来得及修改,返回值为0,但显然程序已经不是正常退出了

status

status 是一个整数变量,用于存储由 waitwaitpid 函数返回的子进程状态信息。当 waitwaitpid 被调用时,如果子进程已经终止,这些函数会将子进程的退出信息存储在 status 变量中。status 的值可以表示多种子进程终止的情况,包括正常退出、被信号终止、停止执行等。

status内部的结构:

458615c283b2eb5c492300b6b7d08a3d.png

WIFEXITED(status)

WIFEXITED 是一个宏((status >> 8) & 0xFF),用于检查 status 变量是否表示子进程是因调用 exit()_exit() 函数正常退出的。如果子进程正常退出,则 WIFEXITED(status) 宏返回一个非零值(通常是1),表示子进程的退出状态可用;如果子进程不是正常退出的,则返回0。

WEXITSTATUS(status)

WEXITSTATUS 是一个宏(status & 0x7F),用于提取子进程的退出状态码。当 WIFEXITED(status) 返回非零值,表明子进程是正常退出的,此时可以使用 WEXITSTATUS(status) 宏来获取子进程的退出码。退出码是由子进程在退出时通过 exit() 函数的参数指定的,通常用于指示程序的退出原因或状态。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid = fork(); // 创建子进程if (pid == -1) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程exit(123); // 正常退出} else {// 父进程int status;pid_t terminated_pid = wait(&status); // 等待子进程结束if (WIFEXITED(status)) {printf("子进程退出,返回状态码:%d\n", WEXITSTATUS(status));} else {printf("子进程非正常退出\n");}}return 0;
}

4ccfc88f93ce1403f365f19a1352f487.png

在这个示例中,父进程使用 wait() 系统调用等待子进程结束,并获取子进程的退出状态。通过 WIFEXITED() 宏检查子进程是否正常退出,并使用 WEXITSTATUS() 宏获取子进程的退出状态码。

5、进程程序替换:exec函数族

exec函数族用于替换当前进程的映像为新的程序。这在shell中常用于执行用户输入的命令。进程替换函数族包括execlexeclpexecleexecvexecvpexecve等。

函数声明:

#include <unistd.h>// 在当前进程映像中执行一个新的程序,path 是新程序的路径,arg 是主参数(通常是程序名)
// 后续参数是通过可变参数列表 (...) 传递的,以 NULL 结尾
int execl(const char *path, const char *arg, ...);// 类似于 execl,但在系统环境变量 PATH 中搜索程序文件
int execlp(const char *file, const char *arg, ...);// 类似于 execl,但允许传递环境指针 envp,envp 是环境变量数组,以 NULL 结尾
int execle(const char *path, const char *arg, ..., char *const envp[]);// 在当前进程映像中执行一个新的程序,path 是新程序的路径
// argv 是指向参数列表的指针数组,参数列表以 NULL 结尾
int execv(const char *path, char *const argv[]);// 类似于 execv,但在系统环境变量 PATH 中搜索程序文件
int execvp(const char *file, char *const argv[]);

系列函数的命名规则:

虽然函数杂乱,但是都是按照此规则命名的exec+数据容器(list列表或vector数组)+环境变量(路径或者自己维护的环境变量数组)

c5fd4a11d52ac2564522a3a2de65bb0b.png

#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);
}

示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t id = fork();if (id < 0) {// fork失败return -1;} else if (id == 0) {// 子进程// 执行 /bin/ps 程序并显示 -al 选项的结果execl("/bin/ps", "ps", "-al", NULL);// 如果execl成功执行,将不会到达这里perror("execl failed");exit(EXIT_FAILURE);} else {// 父进程int status = 0;// 等待子进程结束wait(&status);// 检查子进程是否正常退出,并打印退出码if (WIFEXITED(status)) {printf("exit_code: %d\n", WEXITSTATUS(status));} else {// 子进程非正常退出printf("exit_fail\n");}}return 0;
}

运行结果,发现子进程被替换为ps命令进行执行。其实bash的原理也不过如此~

b007a04e297c7ae7a6ac9314a921733e.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/404897.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐编译器插件:Fitten Code 更快更好的AI助手

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

【Linux 驱动】IMX6ULL gpio驱动

1. 概述 如果 pinctrl子系统将一个 PIN 复用为 GPIO 的话&#xff0c;那么接下来要用到 gpio 子系统了。gpio 子系统顾名思义&#xff0c;就是用于初始化 GPIO 并且提供相应的 API 函数&#xff0c;比如设置 GPIO为输入输出&#xff0c;设置读取 GPIO 的值等。 gpio 子系统的主…

MAE论文详解

文章目录 前言一、MAE理论二、MAE整体框架三、MAE简单实现四、实验总结 前言 MAE是Facebook团队在2021年11月发布的一篇论文&#xff0c;《Masked Autoencoders Are Scalable Vision Learners》&#xff0c;带掩膜的自编码器是可扩展的视觉学习器&#xff0c;MAE就是Masked Aut…

SpringBoot整合Liquibase

1、是什么&#xff1f; Liquibase官网 Liquibase是一个开源的数据库管理工具&#xff0c;可以帮助开发人员管理和跟踪数据库变更。它可以与各种关系型数据库和NoSQL数据库一起使用&#xff0c;并提供多种数据库任务自动化功能&#xff0c;例如数据库迁移、版本控制和监控。Li…

文本分类任务算法演变(二)

文本分类任务算法演变 1.深度学习-pipeline1.1fastText1.2LSTM1.2.1公式详解1.2.2可视化 1.3TextCNN1.4Gated CNN1.5TextRCNN1.6Bert1.6.1取[cls] token对应的向量1.6.2将整句话的向量取max/average pooling1.6.3将Bert编码后的向量再输入LSTM或CNN1.6.4将Bert中间层的结果取出…

Python生成432Hz音频

使用 numpy 来生成信号&#xff0c; 使用 matplotlib 可视化信号&#xff0c; 使用 sounddevice 播放声音。 以下生成和播放 432 Hz 的正弦波信号&#xff1a; import numpy as np import sounddevice as sd import matplotlib.pyplot as plt# 生成单音函数 def generate_to…

gstreamer系列 -- 获取媒体信息

Basic tutorial 9: Media information gathering

windows下的redis7.0.11的下载

天&#xff0c;我找redis7.0.11的安装包就找了好久&#xff0c;终于给我找到了。市面上好多是linux版本的。 安装包&#xff1a;Release Redis 7.0.11 for Windows zkteco-home/redis-windows GitHub 解压之后是这样的。 然后你要测试能不能启动&#xff1a; 1、指定配置文…

C语言-部分字符串函数详解 1-4

C语言-部分字符串函数详解 1-4 前言1.strlen1.1基本用法1.2注意事项\0size_t 1.3模拟实现 2.strcpy2.1基本用法2.2注意事项**源字符串必须以 \0 结束****会将源字符串中的 \0拷贝到目标空间****目标空间必须可修改****目标空间必须能容纳下源字符串的内容** 2.3模拟实现 3.strn…

RabbitMQ的核心概念

RabbitMQ是一个消息中间件&#xff0c;也是一个生产者消费者模型&#xff0c;负责接收&#xff0c;存储和转发消息。 核心概念 Producer 生产者&#xff0c;是RabbitMQ Server的客户端&#xff0c;向RabbitMQ发送消息。 Consumer 消费者&#xff0c;是RabbitMQ Server的客…

使用亮数据爬虫工具解锁复杂爬虫场景

在当今数据驱动型时代&#xff0c;数据采集和分析能力算是个人和企业的核心竞争力。然而&#xff0c;手动采集数据耗时费力且效率低下&#xff0c;而且容易被网站封禁。 我之前使用过一个爬虫工具&#xff0c;亮数据&#xff08;Bright Data&#xff09; &#xff0c;是一款低…

浅探空间智能

空间智能&#xff0c;这一概念在人工智能领域逐渐升温&#xff0c;部分归功于AI界的领军人物李飞飞博士所领导的创新项目。 Seeing is for doing and learning. 【精校】TED&#xff1a;李飞飞 | 空间智能让AI理解真实世界 2024.5 李飞飞在 X 上介绍称&#xff0c;「空间智能…

消防认证-火灾显示盘GB 17429-2011

一、消防认证 消防认证是指消防产品符合国家相关技术要求和标准&#xff0c;且通过了国家认证认可监督管理委员会审批&#xff0c;获得消防认证资质的认证机构颁发的证书&#xff0c;消防产品具有完好的防火功能&#xff0c;是住房和城乡建设领域验收的重要指标。 二、认证依据…

10结构型设计模式——桥接模式

一、桥接模式 桥接模式&#xff08;Bridge Pattern&#xff09;是结构型的设计模式之一。桥接模式基于类的最小设计原则&#xff0c;通过使用封装&#xff0c;聚合以及继承等行为来让不同的类承担不同的责任。它的主要特点是把抽象&#xff08;abstraction&#xff09;与行为实…

SystemUI下拉框新增音量控制条

Android产品下拉框一直只有亮度条没有音量控制条。 为了方便控制音量&#xff0c;普遍都是底部导航栏添加音量加减按钮&#xff0c;在Android10以后&#xff0c;大家普遍用上了手势导航&#xff0c;去掉底部导航栏。 目前需要再下拉框中可以直接控制音量。 文章目录 前言需求及…

day33

一、linux系统中的库 库在linux系统中是一个二进制文件&#xff0c;它是由XXX.c&#xff08;不包含main函数&#xff09;文件编译而来的&#xff0c;分为静态库和动态库。 库在系统中的内容是不可见的&#xff0c;是一个二进制乱码 当程序需要使用库中的相关函数时&#xff0c;…

安装docker 遇到异常Could not resolve host: mirrorlist.centos.org; 未知的错误

问题 安装docker 遇到异常 Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx86_64&repoos&infrastock error was 14: curl#6 - “Could not resolve host: mirrorlist.centos.org; 未知的错误” 1、安装Docker依赖包 yum install …

基于SpringBoot的论坛系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图详细视频演示技术栈系统测试为什么选择我官方认证玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据库参…

Android 12系统源码_多屏幕(二)模拟辅助设备功能开关实现原理

前言 上一篇我们通过为Android系统开启模拟辅助设备功能开关&#xff0c;最终实现了将一个Activity显示到多个屏幕的效果。 本篇文章我们具体来分析一下当我们开启模拟辅助设备功能开关的时候&#xff0c;Android系统做了什么哪些操作。 一、模拟辅助设备功能开关应用位置 …

存储实验:华为异构存储在线接管与在线数据迁移(Smart Virtualization Smart Migration 特性)

目录 目的实验环境实验步骤参考文档1. 主机安装存储多路径2. v2存储创建Lun&#xff0c;映射给主机&#xff1b;主机分区格式化&#xff0c;写数据3. 将v2存储映射该成映射到v3存储上(v3存储和v2之间链路搭建&#xff0c;测通&#xff0c;远端设备&#xff09;&#xff08;Smar…