安卓与串口通信-modbus篇

news2024/11/27 21:05:56

前言

在之前的两篇文章中,我们讲解了串口的基础知识和在安卓中使用串口通信的方法,如果还没看过之前文章的同学们,建议先看一遍,不然可能会不理解这篇文章讲的某些内容。

事实上,在实际应用中,我们很少会直接使用串口通信,一般都会使用到 Modbus。

因为正如我上篇文章所说,如果直接使用串口通信的话,需要我们自定义数据层协议,或者干脆就直接发送一个 byte 的数字进行通信,这显然是不方便的,也不安全的。

例如我上篇提到过的一个问题,我所使用的驱动版厂商定义的协议中没有定义数据长度(或者在数据中附上数据长度),也没有定义停止符号,这会导致出现“沾包”或“分包”情况时不好区分数据。

并且自定义协议还需要自己去解析并处理数据,使用起来不是那么方便。

所以,我司在尝试过直接使用串口通信后,最终还是决定放弃直接使用串口通信,而是改用 Modbus 通信。

本篇文章属于系列文章的扩展篇,我们将讲解 Modbus 的基础知识以及如何在安卓中使用 Modbus。

本文中部分图表来自文末标注的参考资料

Modbs 基础

简介

Modbus 是一种应用层报文传输协议,由 Modicon 公司在 1979 年发布,是为了解决 PLC 通信而研发的协议。

因为 Modbus 是开源的且无著作权要求、易于部署维护、可靠性强的特性,所以 Modbus 已经成为工业领域通信协议事实上的业界标准,并且现在是工业电子设备之间常用的连接方式。

由于 Modbus 定义的只是应用层的报文协议,所以它可以使用串口(RS232、RS485)、以太网作为物理层接口。

Modbus 分为三种传输模式:RTU、ASII、TCP。

在使用 Modbus 时,所有设备的传输模式必须相同。

RTU 使用二进制数据传输、ASCII 使用 ASCII 字符传输。

使用串口连接时支持 RTU 和 ASCII 模式。

使用以太网连接时支持 TCP 模式。

因为本系列文章的重点在于讲解串口通信,所以我们不过多讲解 TCP 模式,同时,由于 ASCII 模式在目前实际应用中比较少,我们一般都是使用的 RTU 模式。故,我们会重点讲解 Modbus RTU。如果对其他传输模式感兴趣的可以阅读参考资料 4 的文档。

额外说明一下,Modbus 和 RS232、RS485 的区别。

RS232、RS485定义的是物理层标准,即接线方式,电平高低,数据传输方式等。

而 Modbus 是应用层协议,即定义了上述物理层传输过来的数据应该以什么样的格式去解析。

Modbus RTU

使用串口作为物理层协议时,通常采用的是 RS485 。

而我们在第一篇文章就说过,RS485 支持一主多从多个设备同时连接,所以使用 RS485 的 Modbus 同样支持多个设备连接。在标准负载情况下,支持一个主机连接最多32个从机。并且在连接设备时,只能使用菊花链连接,不能使用星型网络:

1.png

另外,Modbus 是一种请求/应答协议,即只能通过主站(主机)发送请求给从站后,从站响应数据给主站,而不能从站直接主动发送数据给主站。

储存区数据模型

在 Modbus 中定义了4种不同的数据模型,具体如下:

名称数据类型访问类型说明
离散量输入单个比特(bit)只读I/O系统提供
线圈单个比特(bit)读写可通过应用程序改写
输入寄存器字(word,16bit)只读I/O系统提供
保持寄存器字(word,16bit)读写可通过应用程序改写

其中 线圈 和 离散量输入 又可以称为 输出线圈 和 输入线圈。

它们的数据长度都是一个 bit,即只能表示 1 或 0,表现在程序中就是一个 Boolean 类型的数据。对于安卓程序员来说,可能会疑惑啥是线圈,其实这两个模型之所以叫做线圈是因为 Modbus 是为了 PLC 通信而编写的协议,而在 PLC 中一些物理设备(例如继电器)只有两种状态:断开与接通(即 0 或 1 ,或者 Boolean 的 false 与 true ),这些物理设备的状态切换一般都是依赖于线圈的通/断电来实现,所以在 Modbus 中就将这种类型的数据称为 线圈。

而 输入寄存器 和 保持寄存器 又可以称为 输入寄存器 和 输出寄存器。

它们的数据长度是一个 word,即 16 bit,2 byte,表现在程序中可以看成一个 Int 类型。

显然,在同一个设备中不同的数据模型肯定不止一个可用的数据区块,理论上来说,每种数据模型最大可以定义 65536 个数据区块。

因此,每种数据模型的地址定义为如下:

数据模型地址范围
线圈00001-09999
离散输入10001-19999
输入寄存器30001-39999
保持寄存器40001-49999

可以看到,虽然我们上面说每种模型理论上支持 65536 个数据区块,但是实际使用中每种数据模型一般都只会定义最大 10000 个数据区块。

Modbus 允许将四种不同的数据模型存放在不同的数据区块,这样使用不同的功能码(下面会说什么是功能码)读到的是不同的数据:

2.png

同时,Modbus 也可以将不同的数据模型映射到同一个数据区块中,这样一来,不同的功能码读取到的可能是相同的数据:

3.png

功能码

在上一节我们介绍了储存区数据模型,那么我们要如何去读取不同的数据模型数据呢?或者说,在 Modbus 中是怎么区分不同的数据模型?

此时,就要用到 功能码。

在 Modbus 中定义了三种类型的功能码:

  • 公共功能码 : Modbus 组织定义的标准的公开的通用的功能码,包括已定义的和保留的功能码
  • 用户自定义功能码 : 用户可以自定义自己需要的功能码,范围在 65-72 和 100-110(都是十进制)之间。
  • 保留功能码 : 一些公司的传统设备中使用的功能码,对公共功能码无效。

4.png

公共功能码定义了如下几种:

5.png

而我们一般会使用到的有以下几种:

6.png

可以看到,我们常用的有 8 个功能码,其实仔细一看就能看出不过是读所有数据模型;以及可写数据模型和写单个/写多个的排列组合。

读取数据时所有数据模型均支持只读取单个和同时读取多个数据,并且使用的都是同一个功能码。

写入数据同样支持只写入单个数据和同时写入多个数据,但是写入单个和写入多个的功能码是分开的。

可能有细心的读者发现了,为什么表中的所有 寄存器地址 都是一样的啊,这是因为上表中的 PLC 地址使用的是绝对地址,一般用于文档中或程序中。

而实际设备的寄存器地址则使用的是相对地址。由于我们已经通过功能码区分开了不同的数据区块,所以为了节约传输时的字节占用,直接使用相对地址即可(如果使用绝对地址,那么现在的字节数不够表示所有地址)。

主/从站

上文中提到过,使用串口的 Modbus 是主-从协议。即,在同一时刻,只有一个主节点和一个或多个子节点连接在同一个串行总线上。

Modbus 的通信总是由主节点发起,子节点响应。并且子节点之间不会相互通信。

在 Modbus 中,主节点没有地址,每个子节点都有自己唯一的地址(1-247),通常称为从站地址。

主节点有两种方式发出请求:单播模式与广播模式。

7.png

在单播模式中,主站(主节点)发送一个带有从站(子节点)地址的请求给当前连接的所有设备,但是只有从站地址符合的从站会响应该请求,并返回数据。其他设备不会响应也不会执行任何操作(读取到地址不符合后直接抛弃这个请求报文)。在这个模式中会产生两个报文:主站的请求报文和从站的响应报文。

在广播模式中所有从站都不会发送响应报文给主站,但是会执行请求的操作,并且主站的请求会发送给所有从站。广播模式一般用于写数据。此时主站发送的请求报文中的从站地址为 0 ,表示广播。

数据帧

一个 Modbus RTU 的报文帧由 4 个部分组成:

8位从站地址+8位功能码+最大252*8位数据+16位差错校验

8.png

在 RTU 中通常使用的错误校验方式是 CRC 校验(眼熟吗?CRC 又出现了)

不知道你们有没有发现,这里的功能码使用了 2 byte ,但是上面介绍功能码时明明最大才到 127 ,那么剩下的一半去哪儿呢?

在 Modbus 定义中,从机如果能够正确处理主机的请求,则返回报文中的功能码将和主机请求的功能码一样,如果出现错误,无法正确的处理请求,则从机返回报文的功能码将是最高位为 1 的功能码,即 128-255 。

数据位在不同的功能码以及主机请求还有从机响应都有不同的数据内容和长度,例如请求读取线圈则数据位的内容为:2字节数据表示读取线圈起始地址+2字节数据表示要读取的线圈数量。

此时从机将会按照请求读取的线圈数量返回数据,数据格式为:1字节表示数据的字节数+N字节表示读取到线圈状态数据。如果读取到的线圈状态数据不是 8 位的整数,则会在后面填充 0 使其满足 8 位的倍数。

9.png

数据位在某些情况下,可以为空。

下面举一个数据帧的完整例子(例子来自参考资料 1)。

我们有一个从站是温湿度传感器,从站地址为 1,它会将采集到的湿度写入保持寄存器的 40001 区块中;温度写入保持寄存器的 40002 区块中。此时我们发送读取保持寄存器请求去获取它的温湿度信息。

则,主机的请求报文为:

0103040146013B5A59

分别拆解这个数据帧为:

01 :从站地址

03 :功能码,读保持寄存器

00 00 : 读取的起始寄存器地址(对应 40001 的相对地址)

00 02 :读取的寄存器长度(这里表示连续读取两个寄存器)

C4 0B : CRC校验码

从机在接收到请求后,响应报文为:

0103040146013B5A59

拆解数据:

01:从站地址

03: 功能码,读保持寄存器

04 :读取到的数据的字节长度(这里表示4字节)

01 46 01 3B :读取到的数据,前两个字节为湿度(换算成十进制为 326 ,即 32.6% ),后两个字节为温度(十进制为 315,即 31.5 摄氏度)

5A 59 : CRC校验码

这里提一句,别纠结为啥读取到的温湿度的值要除以 10 才是实际值,因为这是温湿度传感器厂家定义的。

在安卓中使用 Modbus

经过上面的介绍,相信大家已经对于 Modbus 有了一个大致的了解。

那么,如何在安卓中使用 Modbus 呢?如果你理解了 Modbus 的基础,并且前面的两篇文章也大致理解了,那么这就不是问题了。

核心思路就是通过上篇文章介绍的使用 android-serialport-api 或使用 USB Host 的方法打开串口,并获取到输入输出流,然后在发送和接收数据时按照 Modbus 协议标准封装或解析即可。

其中如何打开串口以及获取输入输出流已经在上篇文章介绍,因此现在需要解决的是如何封装/解析数据。

当然,你可以按照 Modbus 标准文档自己动手写一个。

或者,你也可以不用重复造轮子,直接使用现成的第三方库。

这里我们可以使用 modbus4j,但是,从它的名字就可以看出来,这是一个 java 库,好在我们只需要使用它的解析和封装的功能,所以在安卓中依旧可以使用。

modbus4j

老规矩,使用 modbus4j 前需要先引入依赖:

// 添加仓库地址
repositories {
	...
	maven { url 'https://jitpack.io' }
}

……

// 添加依赖
implementation 'com.github.MangoAutomation:modbus4j:3.1.0'

然后在正式使用之前,我们需要新建一个类继承自 SerialPortWrapper ,用于实现在安卓上的串口功能:

class AndroidWrapper : SerialPortWrapper {
    // 关闭串口
    override fun close() {
        TODO("Not yet implemented")
    }

    // 打开串口
    override fun open() {
        TODO("Not yet implemented")
    }

    // 获取输入流
    override fun getInputStream(): InputStream {
        TODO("Not yet implemented")
    }

    // 获取输出流
    override fun getOutputStream(): OutputStream {
        TODO("Not yet implemented")
    }

    // 获取波特率
    override fun getBaudRate(): Int {
        TODO("Not yet implemented")
    }

    // 获取数据位
    override fun getDataBits(): Int {
        TODO("Not yet implemented")
    }

    // 获取停止位
    override fun getStopBits(): Int {
        TODO("Not yet implemented")
    }

    // 获取校验位
    override fun getParity(): Int {
        TODO("Not yet implemented")
    }
}

在我们新建的这个类中重写上述几个方法,用于提供串口通信所需要的几个参数即可。

然后,初始化 modbus4j 并发送消息:

val modbusFactory = ModbusFactory()

val wrapper: SerialPortWrapper = AndroidWrapper()

// 创建管理对象
val master = modbusFactory.createRtuMaster(wrapper)
    
// 发送消息
val request = ……
val response = master.send(request) // requst 为要发送的数据,response 为接收到的响应数据

上面就是 modbus4j 的简单使用方法,如果同学们甚至都不想自己去完成串口通信的话,还可以用这个库 Modbus4Android ,这个库基于 android-serialport-api 和 上面的 modbus4j 封装了一个安卓上到手即用的 Modbus 库。

不过它使用的是 android-serialport-api 实现串口通信,如果需要使用 USB Host 的话可能还是需要自己去封装一个库了。(等我找到合适的测试设备后抽空我也封装一个)

并且,这个库使用了 RxJava 如果不喜欢 RxJava 的话也得自己封装一个了,其实封装起来也不算难,完全可以基于这个库自己改一改就好了。

Modbus4Android

使用这个库的第一步,依旧是导入依赖:

// 添加远程仓库
repositories {
   maven { url 'https://jitpack.io' }
}

……

// 添加依赖
dependencies {
   implementation 'com.github.licheedev:Modbus4Android:2.0.2'
}

接下来,为了方便使用,同时为了避免重复初始化,我们可以创建一个全局单例实例 ModbusManager

class ModbusManager : ModbusWorker() {



    /**
     * 释放整个ModbusManager,单例会被置null
     */
    @Synchronized
    override fun release() {
        super.release()
        sInstance = null
    }

    companion object {
        @Volatile
        private var sInstance: ModbusManager? = null
        fun getInstance(): ModbusManager {
            var manager = sInstance
            if (manager == null) {
                synchronized(ModbusManager::class.java) {
                    manager = sInstance
                    if (manager == null) {
                        manager = ModbusManager()
                        sInstance = manager
                    }
                }
            }
            return manager!!
        }
    }
}

然后初始化串口连接:

private fun initConnect(): Boolean {
    Log.i(TAG, "initConnect: 开始初始化连接 Modbus\nconfig=$config")

    val param = SerialParam
        .create(config.serialPath, config.serialRate) // 串口地址和波特率
        .setDataBits(config.serialDataBits) // 数据位
        .setParity(config.serialParity) // 校验位
        .setStopBits(config.serialStopBits) // 停止位
        .setTimeout(config.serialTimeout)  //超时时间
        .setRetries(config.serialRetries) // 重试次数

    try {
        // 初始化前先关闭,避免串口已经被打开过
        ModbusManager.getInstance().closeModbusMaster()
        val modbusMaster = ModbusManager.getInstance().syncInit(param)
        return true
        // 初始化(打开串口)成功
    } catch (e: ModbusInitException) {
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: InterruptedException) {
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: ExecutionException) {
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: ModbusTransportException) {
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    } catch (e: ModbusRespException) {
        Log.e(TAG, "initConnect: 初始化modbus出错!", e)
    }
    return false
}

完成上述步骤后,我们就可以开始发送请求并接收数据了。

这里依旧以读取线圈数据为例,我们可以使用同步请求:

val slaveId = 1 // 从站地址
val start = 00001 // 读取的起始位置
val len = 1 // 需要读取的长度

val response = ModbusManager.getInstance().syncReadCoil(slaveId, start, len)

其中的 response 即为响应数据信息。

另外,我们也可以使用异步读取的方式:

ModbusManager.getInstance().readCoil(slaveId, start, len, object : ModbusCallback<ReadCoilsResponse> {
    override fun onSuccess(response: ReadCoilsResponse?) {
        // 请求成功,收到回复为 response
    }
    override fun onFailure(tr: Throwable?) {
        // 请求失败
    }
    override fun onFinally() {
        // 请求完成
    }
})

该库支持的所有读取方法如下:

10.png

所有写数据方法如下:

11.png

总结

我们在这篇文章中介绍了在安卓中使用串口通信时大概率会接触到的一种应用层协议 – Modbus,并讲解了如何在安卓中使用 Modbus ,另外介绍了几个个人认为比较好用的第三方库。

自此,关于安卓上的串口通信内容就讲的差不多了。

下一篇看情况写一写各个校验方法的原理和算法实现或者是上文中挖的使用 USB Host 实现 Modbus 的坑,也可能这个系列就此完结吧,哈哈。

参考资料

  1. 这节课带你吃透Modbus通信协议
  2. 6分钟快速理解Modbus通信协议!
  3. Modbus
  4. modbus_proto_cn.pdf

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tip in/Out变速箱齿轮敲击过程详细分析

Tip in/Out变速箱齿轮敲击过程详细分析(模型由AMEsim例子改造而成&#xff0c;数据均虚构&#xff0c;仅学习用&#xff09; 1、发动机稳态工况2、Tip in/Out工况3、总结 1、发动机稳态工况 发动机输出力矩&#xff1a; 一轴齿轮驱动力矩&#xff08;离合器减振器输出力矩&am…

为什么要做问卷调查?企业获得用户心声的捷径

问卷调查作为一种重要的数据收集方法&#xff0c;在市场营销、社会学研究、用户研究等领域得到广泛应用。通过问卷调查&#xff0c;我们可以了解受访者的态度、行为、需求等信息&#xff0c;进而为企业和组织的决策提供支持。那么&#xff0c;为什么要做问卷调查呢&#xff1f;…

大语言模型架构设计

【大模型慢学】GPT起源以及GPT系列采用Decoder-only架构的原因探讨 - 知乎本文回顾GPT系列模型的起源论文并补充相关内容&#xff0c;中间主要篇幅分析讨论为何GPT系列从始至终选择采用Decoder-only架构。 本文首发于微信公众号&#xff0c;欢迎关注&#xff1a;AI推公式最近Ch…

一些云原生开源安全工具介绍

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/130789465 一、Kubernetes安全监测工具kube-bench kube-bench是一个用Golang开发的、由Aqua Security发布的自动化Kubernetes基准测试工具&#xff0c;它运行CIS Kubernetes基准中的测试项目。这些测试…

在 uniapp 中通过 Intent 的方式启动其他APP并且传参

文章目录 前言一、其他软件调用文档中的安卓原生代码二、在uniAPP中实现上述方式三、总结四、感谢 前言 由于业务需求需要&#xff0c;我方研发的安卓APP需要调用其他安卓APP&#xff0c;并且将保存返回的文件存储路径进行读取后操作。对方软件公司提供了对接文档和一个测试调…

docker安装华为gaussdb数据库

docker安装gaussdb docker镜像&#xff1a; http://docker.hub.com/ 这里我们使用docker hub镜像下载&#xff0c;该镜像下载较慢&#xff0c;可能有时访问不同&#xff0c;可以使用阿里云镜像下载&#xff0c;阿里云镜像配置参考《docker国内阿里云镜像加速》 拉取镜像 下载…

程序翻译的过程,linux环境下处理,生成 .i、.s、.o 文件(预处理、编译、汇编、链接)

1. 程序翻译的过程有四个步骤&#xff0c;预处理->编译->汇编->链接。 那么每个步骤是干什么&#xff1f; 预处理阶段&#xff1a;处理-> 头文件、宏替换、条件编译等等&#xff0c;我用 linux 环境查看一下&#xff0c;如下&#xff1a; 首先写一个简单的 .c 文…

【iptables 防火墙设置】

目录 一、iptables概述1、netfilter/iptables 关系 二、四表五链2.1、四表:2.2、五链&#xff1a; 三、规则链之间的匹配顺序四、规则链内的匹配顺序五、iptables的安装配置5.1、安装iptables5.2、配置iptables1、常用的管理选项2、常用的参数3、常用的控制类型4、iptables语法…

ThinkPHP6 模型层的模型属性,表映射关系,以及如何在控制层中使用模型层和模型层中的简单CRUD

ThinkPHP6 模型层的模型属性&#xff0c;表映射关系&#xff0c;以及模型层的CRUD及如何在控制层中使用模型层 1. model 模型层的默认映射规则 模型&#xff0c;即mvc模式中的model层&#xff0c;model层用来对接数据库&#xff0c;操作数据库的增删改查。 在tp6中&#xff…

springboot整合sharding-jdbc实现分库分表详解

目录 一、为什么需要分库分表 1.1 分库分表的优势 二、分库分表基本概念 2.1 垂直分表 2.2 水平分表 2.3 垂直分库 2.4 水平分库 三、分库分表带来的问题 3.1 分布式事务问题 3.2 跨节点关联查询问题 3.3 跨节点分页、排序问题 3.4 主键避重问题 四、分库分表常用…

Java --- 云尚办公之菜单管理模块

一、菜单管理 数据库表&#xff1a; CREATE TABLE sys_menu (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 编号,parent_id BIGINT(20) NOT NULL DEFAULT 0 COMMENT 所属上级,name VARCHAR(20) NOT NULL DEFAULT COMMENT 名称,type TINYINT(3) NOT NULL DEFAULT 0 COMMEN…

LSTM预测汇率涨跌分析

前言 本文主要是采用lstm对汇率涨跌进行预测&#xff0c;是一个二分类的预测问题。 步骤解析 数据构造 原始数据是单变量数据 import pandas as pdfile_path r"./huilv.csv" data pd.read_csv(file_path, usecols[1],encodinggbk) data[level] -1 美元 l…

打造高效接口自动化框架,YAML测试用例封装技巧大揭秘!

目录 前言&#xff1a; 一、框架介绍 本框架包含两个部分&#xff1a; 本框架的构建目标是&#xff1a; 二、框架目录结构 三、规范YAML测试用例封装步骤 四、框架使用 五、总结 前言&#xff1a; 本文介绍了一个基于Python和PyTest的接口自动化框架封装项目实战&#…

最佳实践,高效编写Web自动化测试强制交互方法封装技巧

目录 前言&#xff1a; 一、Web自动化测试的基本原理 二、封装强制交互方法 1、输入框强制交互 2、其他强制交互 三、封装基础类方法 四、总结 前言&#xff1a; Web自动化测试是现代软件开发中必不可少的部分。Web自动化测试可以帮助测试人员快速地验证页面功能并发现潜…

Fiddler 抓包工具下载安装基本使用(详)

在做软件测试或者Bug定位的时候会用到一些抓包工具&#xff0c;当然抓包工具还要一些其他用途可以做一些API的抓取&#xff0c;那么本篇内容就来讲 Fiddler 抓包工具的下载安装以及如何来实际的应用。讲了这些可能有的读者还不知道这个"Fiddler"怎么读呢&#xff1f;…

详解flutter刷新流程,让你的应用更流畅

本文已授权公众号【缦图技术团队】发布 详解flutter刷新流程&#xff0c;让你的应用更流畅 一、概述 Flutter 是谷歌推出的高性能、跨端UI框架&#xff0c;可以通过一套代码&#xff0c;支持 iOS、Android、Windows/MAC/Linux 等多个平台&#xff0c;且能达到原生性能。Flutte…

pthread_getspecific和pthread_setspecific详解

写在前面 在Linux系统中使用C/C进行多线程编程时&#xff0c;我们遇到最多的就是对同一变量的多线程读写问题&#xff0c;大多情况下遇到这类问题都是通过锁机制来处理&#xff0c;但这对程序的性能带来了很大的影响&#xff0c;当然对于那些系统原生支持原子操作的数据类型来…

【CV】Yolov8:ultralytics目标检测、关键点检测、语义分割

note Yolov8提供了一个全新的 SOTA 模型&#xff0c;包括 P5 640 和 P6 1280 分辨率的目标检测网络和基于 YOLACT 的实例分割模型。和 YOLOv5 一样&#xff0c;基于缩放系数也提供了 N/S/M/L/X 尺度的不同大小模型&#xff0c;用于满足不同场景需求骨干网络和 Neck 部分可能参…

KVM(一)Linux部署KVM及新建虚拟机

目录 一、准备工作 1.1 防火墙、SElinux 二、安装KVM 2.1 yum源 2.2 安装工具包 2.3 安装KVM组件 2.4 查看磁盘/新建目录 2.5 安装Linux GUI可视化界面 三、KVM桌面版新建虚拟机 3.1 挂载目录 3.2 新建raw/qcow2文件 3.3 新建虚拟机 3.4 KVM命令行新建虚拟机 一、…

Linux rootfs

前言 通过《initrd&init进程》我们知道rootfs 是文件系统的根目录&#xff0c;其包含了操作系统所需的所有文件和目录&#xff0c;包括程序、库文件、配置文件、设备文件等&#xff0c;它是系统启动时必须加载的文件系统之一。当系统启动后&#xff0c;内核会首先挂载 roo…