虚拟化 之一 详解 jailhouse 架构及原理、软硬件要求、源码文件、基本组件

  Jailhouse 是一个基于 Linux 实现的针对创建工业级应用程序的小型 Hypervisor,是由西门子公司的 Jan Kiszka 于 2013 年开发的,并得到了官方 Linux 内核的支持,在开源社区中获得了知名度和吸引力。

Jailhouse

  Jailhouse 是一种轻量级的虚拟化技术,可以将多个操作系统(或者裸机程序)同时运行在同一台硬件上。它是一个基于 Linux 的静态分区的 Hypervisor,但本身并不改造 Linux 内核,而是利用 Linux 系统的开放性,增加一个或多个实时操作系统,实现多系统在一个多核处理器上运行。
在这里插入图片描述
  Jailhouse 不会模拟不存在的硬件资源,也不包含任何的调度处理,而是利用虚拟化技术将硬件资源划分为多个被称为 Cell 的独立空间,并将每个 Cell 分配给不同的虚拟机。每个 Cell 独占自己的处理器核心、内存、I/O 设备和中断控制器等资源。这样可以确保不同虚拟机之间的资源互相隔离,提高系统的可靠性。
在这里插入图片描述

Root Cell

  当 Jailhouse 启动之后,原来的负责启动 Jailhouse 的 Linux 系统所在空间就被称为 Root Cell。对于所有 Jailhouse 虚拟机的管理都是在 Root Cell 中的 Linux 系统中通过 Jailhouse 提供的命令行工具来实现的。

Non-root Cell

  除了 Root Cell 之外的每个独立的 Cell 就是一个 Non-root Cell,每个 Non-root Cell 就对应一个虚拟机。Non-root Cell 通过 Root Cell 进行管理,只要资源够用,Non-root Cell 可以有任意多个。

inmate

  Non-root Cell 虚拟机中运行的系统(也可能是一个裸机程序)被称为 inmate,目前可以是 Linux、FreeRTOS、ERIKA3 RTOS、Zephyr 其中之一。Jailhouse 都是直接将其作为原始二进制文件(不是 ELF 文件),直接加载到其对应的配置文件中指定的内存中去运行。

  在 x86 平台下,由于 Jailhouse 只向 Non-root Cell 的 inmate 暴露最小环境,可用的资源不足以在不进行修改的情况下引导标准的 Linux 系统。为此,西门子官方提供了一个补丁队列,以便在 x86 平台下的 Non-root Cell 中启动 Linux。同时在构建时需要注意以下几点:

  1. 需要使能 CONFIG_JAILHOUSE_GUEST
  2. 禁用 CONFIG_SERIOCONFIG_PM_TRACE_RTC
  3. 一般还应该禁用所有不需要的驱动程序和特性,以避免不需要的 probe,并且使镜像大小和内存占用最小化

  在 ARM / ARM64 平台上,引导 Linux 内核要比在 x86 平台上简单得多,因此在这种情况下,我们不需要为 Non-root Cell 特别修改 Linux 内核。

内存布局

  Jailhouse 的实现需要使用一块连续内存,这块内存需要在启动 Linux 时保留出来。至于内存具体用哪一块,则取决于自己的设备,如下以 0x100000000 为例的示意图如下所示:
在这里插入图片描述

Cell 间通信

  虽然 Jailhouse 将硬件资源进行了划分到了不同的 Cell 中,通过虚拟机监控器实现了相互隔离,但在实际应用过程中,Cell 间也需进行通信。为此,Jailhouse 通过虚拟 ivshmem(Inter-VM shared memory) PCI 设备在 Cell 之间提供共享内存和信号机制。一个通道将两个分区 1:1 对应地连接起来。

环境要求

  Jailhouse 是一个依托于 Linux Kernel 的开放性从而直接使用硬件平台提供的虚拟化技术来实现的虚拟化解决方案。因此,Jailhouse 需要 Linux Kernel 的支持及硬件平台的虚拟化技术。

硬件要求

  Jailhouse 利用 Intel 的 VT-x、AMD 的 AMD-V 和 ARM 虚拟化扩展等硬件辅助虚拟化技术来划分物理资源和限制虚拟机对这些资源的访问,从而实现对系统资源的隔离控制。

x86 架构

  • 针对 Intel 平台,需要 64 位架构以及 VMX(Virtual Machine Extensions,Intel CPU 中使用 vmx 标识 Intel 的 VT-x 虚拟化技术) 技术,具体细节:
    • 具备 EPT(Extended Page Tables,扩展页表)支持。扩展页表是用于内存管理单元(MMU)的 Intel 第二代 x86 虚拟化技术。 直接在实模式下启动逻辑 CPU 就需要 EPT 的支持,这一功能在英特尔的行话中称为无限制访客模式,并在 Westmere 微体系结构中引入。

      Intel 的 Core i3、Core i5、Core i7 和 Core i9 CPU 等均支持 EPT

    • Preemption Timer 是一种可以周期性使 VM 触发 VMEXIT 的一种机制。即设置了 Preemption Timer 之后,可以使得虚拟机在指定的 TSC cycle 之后产生一次 VMEXIT 并设置对应的 exit_reason,trap 到 VMM 中。

    • 支持中断重映射的 Intel IOMMU(Intel 官方称为 VT-d(Virtualization Technology for Directed I/O))

  • 针对 AMD 平台,需要 64 位架构以及 SVM(AMD Secure Virtual Machine(这个是 AMD 内部的研发代号,后来统一使用 AMD-V 作为对外名称),AMD CPU 中使用 svm 来表示 AMD 的 AMD-V 虚拟化技术)技术:
    • 必须具备 NPT(Nested Page Tables,嵌套页表)支持。嵌套页表现在称为快速虚拟化索引(Rapid Virtualization Indexing,RVI)是 AMD 第二代处理器内存管理单元(MMU)硬件辅助虚拟化技术。
    • 推荐具备 Decode Assists 支持
    • AMD IOMMU(AMD 官方称为 AMD-Vi(AMD’s I/O Virtualization Technology))目前不被支持,但后续需要
  • 至少两个逻辑 CPU 核心。注意这里是逻辑 CPU 核心,不是物理 CPU 核心。
  • 在 x86 平台下,需要在 BIOS 或 UEFI 中开启虚拟化功能!
    在这里插入图片描述

  EPT 和 NPT 是 Intel 和 AMD 两家对于 Second Level Address Translation(SLAT,二级地址转换)在自家 CPU 上的具体实现。SLAT 是一种硬件辅助虚拟化技术,可以避免与软件管理的影子页表相关的开销。此外,IOMMU 也经常被我们称为 PCI 直通。

ARM 架构

  • ARMv8 架构或者是带有虚拟化扩展的 ARMv7 架构。ARM 的虚拟化扩展支持 SLAT,即由 Stage-2 MMU 提供的 Stage-2 页表。客户机使用 Stage-1 MMU。该支持在 ARMv7ve 架构中作为可选添加,并且在 ARMv8(32 位和 64 位)架构中也受支持。

  • 至少两个逻辑 CPU 核心

  • 支持如下 AArch32 架构的开发板

    • Banana Pi (see more)
    • Orange Pi Zero (256 MB version)
    • NVIDIA Jetson TK1
    • ARM Versatile Express with Cortex-A15 or A7 cores (includes ARM Fast Model)
    • emtrion emCON-RZ/G1x series based on Renesas RZ/G (see more)
  • 支持如下 AArch64 架构的开发板

    • AMD Seattle / SoftIron Overdrive 3000
    • LeMaker HiKey
    • NVIDIA Jetson TX1 and TX2
    • Xilinx ZCU102 (ZynqMP evaluation board)
    • NXP MCIMX8M-EVK

内核要求

  Jailhouse 的构建是依赖于 Linux Kernel 的,因此,必须使用对应的 Linux Kernel。但是,不同版本的 Linux Kernel 对于 Jailhouse 的支持情况不尽相同。在使用比较新的 Linux Kernel 时,需要对 Linux Kernel 进行打补丁,否则将出现各种错误!

x86 架构

  1. 必须禁用 Linux Kernel 对 VT-d IOMMU 的使用(DMAR)。这个可以通过在 GRUB 配置文件中设置 GRUB_CMDLINE_LINUX = "intel_iommu=off"(IOMMU 硬件有 intel/amd/arm 的等,一般用 intel 的硬件) 这个启动参数来处理。

    1. kvm 一定要用 intel_iommu=on,DPDK/SPDK 如果绑定 vfio-pci 那也一定要求 intel_iommu=on,如果绑定 uio/igb_uio 那么就不需要intel_iommu=on
    1. dmesg | grep -E "DMAR|IOMMU" 查看
  2. 要利用更快的 x2APIC,需要在内核中打开中断重新映射。这个需要再构建内核时启用 CONFIG_IRQ_REMAP 这个配置项

  3. Jailhouse 本身和每个 Cell 都需要一块连续的 RAM,这个必须在 Kernel 启动之前配置。通常是使用 memmapmem 这两个启动命令来实现。在 x86 平台上,这通常是在 GRUB 配置文件中通过添加 GRUB_CMDLINE_LINUX="memmap=82M\\\$0x3a000000" 这个启动参数来处理。

ARM 架构

  针对于 AArch32 架构,需要 Linux Kernel 版本大于等于 3.19;而对于 AArch64 架构,则需要 Linux Kernel 版本大于等于 4.7。此外,还需要适当的的引导程序(例如 U-Boot)的支持。

  • Linux Kernel 必须以 HYP 模式启动(工作在 HYP 模式下的 CPU 上,默认是 SVC 模式)。这里就涉及到了 ARM 架构中的不同特权等级,ARMv7 中使用的是 Privilege level 的概念,ARMv8 中则使用 Exception Level 这个概念
    在这里插入图片描述
    • Supervisor Call(SVC)指令使用户模式程序可以请求操作系统服务。
    • Hypervisor Call(HVC)指令使客户操作系统能够请求 Hypervisor 服务。
    • Secure monitor Call(SMC)指令使普通世界能够请求安全世界服务。
  • PSCI 对 CPU 离线的支持。PSCI(Power State Coordination Interface,电源状态协调接口) 是一个接口,这个接口实现了电源管理用例。The ARM Trusted Firmware 实现了 PSCI(Power State Coordination Interface) 接口作为运行时服务。Normal world software 可以通过 ARM SMC(Secure Monitor Call) 指令来访问 ARM Trusted Firmware 服务
  • Jailhouse 本身和每个 Cell 都需要一块连续的 RAM,这个必须在 Kernel 启动之前配置。在 ARM 平台上,这可以通过减少内核看到的内存量(通过 mem = 内核启动参数)或修改 Device Tree(即保留内存节点)来实现。

源码

  Jailhouse 是由西门子的 Jan Kiszka 在 2013年以 GPLv2 协议开源的一个虚拟化解决方案。并很快得到了官方 Linux 内核的支持,在开源社区中获得了知名度和吸引力。本文及后续博文以目前最新提交(e57d1eff6d55aeed5f977fe4e2acfb6ccbdd7560)版作为学习对象。

源码文件

  Jailhouse 作为一个极度精简的虚拟化实现方案,其代码量还是非常小的,几万行代码量就实现了一个功能强大的虚拟机。

  • .githubci:这两个目录下是用来持续集成环境中使用的构建 Jailhouse 所需的 Linux Kernel 的相关配置文件,目前仅支持 GitHub Actions。

  • config:这个目录下就是针对 armarm64x86 架构下用于生成 Jailhouse 的 Cell 的配置文件对应的源文件。在对应架构下构建 Jailhouse 时,对应架构目录下的每个 .c 就会被构建为对应的 .cell 文件。

    1. 配置文件的内容其实就是一个 C 语言的结构体变量,因此使用的就是 .c 扩展名
    2. 该目录下的 .c 文件实际上都是一些示例,在真正使用时,我们需要提供自己的配置文件!
  • Documentation:Jailhouse 的文档对应的源码,它使用的是 Doxygen 文档系统。使用 sudo apt install doxygen 后,使用命令 make docs 就可以在 Documentation/generated/ 构建出对应的文档。
    在这里插入图片描述

  • driver:Jailhouse 的驱动源码,最终会被编译为 jailhouse.ko,并被放到 /lib/modules/$(uname -r)/extra/driver/ 目录下来使用!

    • cell.c/h:实现 Cell 相关命令的处理
    • pci.c/h:实现对 PCI 设备的处理。在将 PCI 设备分配给 Non-root Cell 时,我们需要确保 Root Cell 中的 Linux 不再使用这些设备。只要设备被分配给了其他 Cell ,Root Cell 就不得再访问这些设备。不幸的是,我们不能仅仅使用 PCI 热插拔来在运行时删除/重新添加设备,因为,Linux 将重新编程 BAR(Base Address Registers)并定位到我们不希望/不允许的资源位置。
        所以,Jailhouse 充当了一个 PCI 虚拟驱动程序,在其他 Cell 使用设备时它会声明这些设备。在创建 Cell 时,设备将从其驱动程序中解绑,并绑定到 Jailhouse。当 Cell 被销毁时,Jailhouse 将释放其设备。当禁用 Jailhouse 时,它将释放所有已分配的设备。
        当释放设备时,它们将不再绑定到任何驱动程序,从 Linux 的角度来看,Jailhouse 虚拟驱动程序仍然会被视为有效的驱动程序。将设备重新分配给原始驱动程序必须手动完成。
    • sysfs.c/h:还通过 sysfs 虚拟文件系统(/sys/devices/jailhouse)向用户空间暴露 Jailhouse 的数据结构。
    • main.c/h:驱动入口
  • hypervisor:Jailhouse 用于管理各个虚拟机的工具的源码代码,最终会被编译为 jailhouse*.bin,被放到 /lib/firmware 目录下

    • arch:再实际使用中,架构相关的代码先被执行,其中调用架构无关的代码
    • 其他:架构无关的代码,其中的接口被 arch 中对应的接口调用
  • include:Jailhouse 对外提供的各种 C 头文件,其中包含了各种数据结构的定义,例如,cell-config.h 中就定义了各种设备的数据结构、Cell 的描述符 jailhouse_cell_desc 等等
    在这里插入图片描述
      Jailhouse 允许用户定义一些在编译时启用的特定于平台的设置或者调试配置参数。方法是新增 include/courahouse/config.h 这个文件,然后在该文件中将对应的配置项定义为 1。如下是当前可用的配置项:

    /* Print error sources with filename and line number to debug console */
    #define CONFIG_TRACE_ERROR 1
    
    /*
     * Set instruction pointer to 0 if cell CPU has caused an access violation.
     * Linux inmates will dump a stack trace in this case.
     */
    #define CONFIG_CRASH_CELL_ON_PANIC 1
    
    /* Enable code coverage data collection (see Documentation/gcov.txt) */
    #define CONFIG_JAILHOUSE_GCOV 1
    
    /*
     * Link inmates against a custom base address.  Only supported on ARM
     * architectures.  If this parameter is defined, inmates must be loaded to
     * the appropriate location.
     */
    #define CONFIG_INMATE_BASE 0x90000000
    
    /*
     * Only available on x86. This debugging option that needs to be activated
     * when running mmio-access tests.
     */
    #define CONFIG_TEST_DEVICE 1
    
  • inmates:这里面是 Jailhouse 虚拟机本身的固件源码,他们会被编译为 .bin 文件。

    • demos:这里面就是一些 inmate 的源码,编译之后就是一个个的 inmate 镜像(.bin 文件)。其都有一个对应的 .cell 文件,位于 configs/ 目录下。
    • lib:该目录下是一些可以在 inmate 的源码中使用的库函数的实现。
    • tests:该目录下是一些验证 Jailhouse 的用例
    • tools:该目录下是一些用来辅助处理 inmate 的工具的源码,每个 .c 文件经过编译之后成为一个 xxx.bin,最终所有的 .bin 会被安装到 /usr/local/libexec/jailhouse 目录下。目前该目录下就只有一个 linux-loader 的源码。
  • pyjailhouse:这里面是用来处理在 Non-root Cell 中运行 Linux 虚拟机时对 Linux Kernel 进行处理的一个 Python 脚本库。

  • scripts:编译系统使用的相关脚本

  • tools:这里面是一些 Jailhouse 实用工具(jailhouse )的源码(其中有些是 Python 脚本)。其中,Python 脚本会被放到 /usr/local/libexec/jailhouse 目录下;jailhouse 则被放到 /usr/local/sbin 目录下。

  • Makefile:构建系统的入口

  • Kbuild:也是个 Makefile 文件

  • setup.py:用来打包及安装 pyjailhouse 的脚本文件。

  • 其他:其他

构建

  Jailhouse 的构建过程非常简单,但是由于不同的平台下的虚拟化技术的差异,在构建时遇到的问题也不一样,受限于博文篇幅,我们将在后续博文中详细学习在不同平台下的构建及使用。

  1. 虚拟化 之二 详解 jailhouse(x86 平台)的构建过程、配置及使用
  2. 虚拟化 之三 详解 jailhouse(ARM 平台)的构建过程、配置及使用

基本组件

  完整的 Jailhouse 组件主要由内核模块(jailhouse.ko)、虚拟机管理程序固件(jailhouse*.bin)、管理工具(jailhouse 命令行程序及一些 Python 脚本)以及配置文件(.cell)这四部分组成。用户使用它们来启用虚拟机管理程序、创建 Cell、加载 inmate 二进制文件以及运行和停止它等。
在这里插入图片描述

jailhouse.ko

  jailhouse.ko 由源码根目录中的 driver 目录中源码在构建之后生成,最终会被安装到 /lib/modules/$(uname -r)/extra/driver/ 目录下。它就是一个标准的 Linux Driver 程序,实现为一个 struct miscdevice 设备(主设备号 MISC_MAJOR(10))。使用命令 cat /proc/misc 可以查看各杂项设备。
在这里插入图片描述
  Linux 中将设备分为字符设备(I2C、USB、SPI等)、块设备(存储器相关的设备如EMMC、SD卡、U盘等)和网络设备(网络相关的设备WIFI等)三大类,其中,杂项设备归属于字符设备。每个设备节点都有主设备号和次设备号 ,杂项设备的主设备号固定为10,次设备号根据设备不同而不同。
在这里插入图片描述

jailhouse_init()

  当加载 jailhouse.ko 之后,驱动源码 driver/main.c 中的 static int __init jailhouse_init(void) 函数就会进行各种初始化,主要就干了以下几个事:

  1. 开头的这一堆的宏定义主要就是为了解决 Jailhouse 用的一些符号 Linux Kernel 没有导出的问题。其核心就是通过 kallsyms_lookup_name 这个内核接口来查找需要的符号。
    在这里插入图片描述
      但是,5.7.0 以上版本的内核不再导出 kallsyms_lookup_name,对于在高版本内核不在导出的原因请参考 https://lwn.net/Articles/813350/。实际上,在 5.7.0 以上仍旧可以用 struct kprobe 来获取 kallsyms_lookup_name 函数的地址,然后再进一步获取到想要的符号。
  2. 通过 root_device_register("jailhouse") 创建 /sys/devices/jailhouse 这个设备,然后调用 jailhouse_sysfs_init(jailhouse_dev) 初始化其中的内容,此后,用户空间就可以通过 /sys/devices/jailhouse 访问 Jailhouse 的数据结构。
    zcs@zcs-MassDatas-GXXA203:~/WORKSPACE/Jailhouse/jailhouse$ tree -L 3 -p /sys/devices/jailhouse
    /sys/devices/jailhouse
    ├── [drwxr-xr-x]  cells									# 这个目录中包含了我们创建的那些 Cell 的信息
    │   ├── [drwxr-xr-x]  0									# 这个是 Cell 的 ID,Root Cell 的 ID 为 0,后续每创建一个 Cell ,ID 自动增 1
    │   │   ├── [-r--r--r--]  cpus_assigned					# 这个是我们在 Cell 的配置文件中分配给 Cell 的 CPU 原始的配置参数(按位使用置 1 表示使用,例如,fffb)
    │   │   ├── [-r--r--r--]  cpus_assigned_list			# 这个是分配给 Cell 的 CPU 的方便我们阅读的列表。例如 0-1,3-15
    │   │   ├── [-r--r--r--]  cpus_failed					# 这个是分配给 Cell 的 CPU 中失败的那些
    │   │   ├── [-r--r--r--]  cpus_failed_list				# 这个是分配给 Cell 的 CPU 中失败的那些的列表
    │   │   ├── [-r--r--r--]  name							# Cell 的名字
    │   │   ├── [-r--r--r--]  state							# Cell 的状态。"running", "running/locked", "shut down","failed" 之一
    │   │   └── [drwxr-xr-x]  statistics					# Cell 的统计数据
    │   │       ├── [drwxr-xr-x]  cpu0						
    │   │       ├── [drwxr-xr-x]  cpu1
    │   │       ├── [drwxr-xr-x]  cpu10
    │   │       ├── [drwxr-xr-x]  cpu11
    │   │       ├── [drwxr-xr-x]  cpu12
    │   │       ├── [drwxr-xr-x]  cpu13
    │   │       ├── [drwxr-xr-x]  cpu14
    │   │       ├── [drwxr-xr-x]  cpu15
    │   │       ├── [drwxr-xr-x]  cpu3
    │   │       ├── [drwxr-xr-x]  cpu4
    │   │       ├── [drwxr-xr-x]  cpu5
    │   │       ├── [drwxr-xr-x]  cpu6
    │   │       ├── [drwxr-xr-x]  cpu7
    │   │       ├── [drwxr-xr-x]  cpu8
    │   │       ├── [drwxr-xr-x]  cpu9						# 以上这些是分配给当前 Cell 使用的所有逻辑 CPU。每个 CPU 节点展开后的内容和下面这些是一样,只不过表示的是单个 CPU 的,下面这些是以上所有 CPU 的汇总
    │   │       ├── [-r--r--r--]  vmexits_cpuid
    │   │       ├── [-r--r--r--]  vmexits_cr
    │   │       ├── [-r--r--r--]  vmexits_exception
    │   │       ├── [-r--r--r--]  vmexits_hypercall
    │   │       ├── [-r--r--r--]  vmexits_management
    │   │       ├── [-r--r--r--]  vmexits_mmio
    │   │       ├── [-r--r--r--]  vmexits_msr_other
    │   │       ├── [-r--r--r--]  vmexits_msr_x2apic_icr
    │   │       ├── [-r--r--r--]  vmexits_pio
    │   │       ├── [-r--r--r--]  vmexits_total				# 全部 CPU 上发生的 VM Exits 总次数,其他的 _xxx 则表示由于 xxx 原因产生的 VM Exits 数量。例如,vmexits_xapic 就表示由于 xapic 产生的 VM Exits 次数
    │   │       ├── [-r--r--r--]  vmexits_xapic
    │   │       └── [-r--r--r--]  vmexits_xsetbv
    │   └── [drwxr-xr-x]  1									# 第二个 Cell,其中的内容与上面的一样
    │       ├── [-r--r--r--]  cpus_assigned
    │       ├── [-r--r--r--]  cpus_assigned_list
    │       ├── [-r--r--r--]  cpus_failed
    │       ├── [-r--r--r--]  cpus_failed_list
    │       ├── [-r--r--r--]  name
    │       ├── [-r--r--r--]  state
    │       └── [drwxr-xr-x]  statistics
    │           ├── [drwxr-xr-x]  cpu2						# 分配给当前 Cell 使用的所有逻辑 CPU。每个 CPU 节点展开后的内容和下面这些是一样
    │           ├── [-r--r--r--]  vmexits_cpuid
    │           ├── [-r--r--r--]  vmexits_cr
    │           ├── [-r--r--r--]  vmexits_exception
    │           ├── [-r--r--r--]  vmexits_hypercall
    │           ├── [-r--r--r--]  vmexits_management
    │           ├── [-r--r--r--]  vmexits_mmio
    │           ├── [-r--r--r--]  vmexits_msr_other
    │           ├── [-r--r--r--]  vmexits_msr_x2apic_icr
    │           ├── [-r--r--r--]  vmexits_pio
    │           ├── [-r--r--r--]  vmexits_total				# 全部 CPU 上发生的 VM Exits 总次数,其他的 _xxx 则表示由于 xxx 原因产生的 VM Exits 数量
    │           ├── [-r--r--r--]  vmexits_xapic
    │           └── [-r--r--r--]  vmexits_xsetbv
    ├── [-r--r--r--]  console								# 这个是 Jailhouse 的终端,我们可以从中直接读取 Jailhouse 的 Log
    ├── [-r--------]  core									# 这里面是 Jailhouse 固件以及配置信息,可以使用 tools/jailhouse-gcov-extract 来解析。访问时,确保是 `jailhouse disable` 状态!
    ├── [-r--r--r--]  enabled								# 指示 Jailhouse 是否启用。 1 表示启用,0 表示未启用
    ├── [-r--r--r--]  mem_pool_size							# 内存池中的页数
    ├── [-r--r--r--]  mem_pool_used							# 内存池中已用的页数
    ├── [lrwxrwxrwx]  module -> ../../module/jailhouse		# 这是一个由内核机制自动创建的符号链接,指向当前目录的所有者(创建者)
    ├── [drwxr-xr-x]  power									# 这个是与电源管理相关的内容
    │   ├── [-rw-r--r--]  async
    │   ├── [-rw-r--r--]  autosuspend_delay_ms
    │   ├── [-rw-r--r--]  control
    │   ├── [-r--r--r--]  runtime_active_kids
    │   ├── [-r--r--r--]  runtime_active_time
    │   ├── [-r--r--r--]  runtime_enabled
    │   ├── [-r--r--r--]  runtime_status
    │   ├── [-r--r--r--]  runtime_suspended_time
    │   └── [-r--r--r--]  runtime_usage
    ├── [-r--r--r--]  remap_pool_size						# 重映射池中的页数
    ├── [-r--r--r--]  remap_pool_used						# 重映射池中已用的页数
    └── [-rw-r--r--]  uevent								# 各种事件
    
    1. Root 设备是一个虚拟设备,以该 Root 设备为父设备调用 kobject_create_and_add 就可以让其他设备可以挂在它的下面
    2. 其中有些节点需要在执行相应的命令后才会有具体的内容
  3. 通过 misc_register(&jailhouse_misc_dev); 注册 struct miscdevice 设备。加载驱动之后,就会创建 /dev/jailhouse 这个设备。用户空间的 Jailhouse 的管理工具使用 ioctl() 系统调用通过 jailhouse.ko 创建的 /dev/jailhouse 这个文件向 jailhouse.ko 发送各种请求。
  4. 调用 jailhouse_pci_register() 将自身注册为一个虚拟的 PCI 设备驱动程序,以便它可以获取分配的设备。
  5. 调用 register_reboot_notifier(&jailhouse_shutdown_nb); 注册重启回调接口,当内核出现 Kernel Halt、Kernel Restart 或 Kernel Power Off 时,就会调用我们注册的回调函数。Jailhouse 注册之后主要用来关闭自身!
    在这里插入图片描述

jailhouse_ioctl()

  各种请求通过内核最终到达 driver/main.c 中的 jailhouse_ioctl 这个函数。static long jailhouse_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) 这个函数解析收到的请求,然后调用对应的接口来进一步处理。

static long jailhouse_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
{
	long err;

	switch (ioctl) {
	case JAILHOUSE_ENABLE:
		err = jailhouse_cmd_enable(
			(struct jailhouse_system __user *)arg);
		break;
	case JAILHOUSE_DISABLE:
		err = jailhouse_cmd_disable();
		break;
	case JAILHOUSE_CELL_CREATE:
		err = jailhouse_cmd_cell_create(
			(struct jailhouse_cell_create __user *)arg);
		break;
	case JAILHOUSE_CELL_LOAD:
		err = jailhouse_cmd_cell_load(
			(struct jailhouse_cell_load __user *)arg);
		break;
	case JAILHOUSE_CELL_START:
		err = jailhouse_cmd_cell_start((const char __user *)arg);
		break;
	case JAILHOUSE_CELL_DESTROY:
		err = jailhouse_cmd_cell_destroy((const char __user *)arg);
		break;
	default:
		err = -EINVAL;
		break;
	}

	return err;
}

jailhouse*.bin

  jailhouse*.bin 是由源码根目录中的 hypervisor 目录中的源码在构建之后会生成,针对不同的架构名字会有些区别(它最终会被安装到 /lib/firmware 目录下)。jailhouse*.bin 接收 jailhouse.ko 发来的超级调用,用于硬件资源的分配。
在这里插入图片描述

内存布局

  jailhouse*.bin 是一个具体特定结构的二进制文件,在 jailhouse*.bin 的开头是一个 struct jailhouse_header 结构。这个 BIN 文件中的部分内容是在构建时就填充好的,还有一部分是在驱动加载它时有驱动程序动态填充的。如下是 jailhouse*.bin 在内存中的布局:
在这里插入图片描述
  .header 定义于 hypervisor/setup.c 中,并被链接文件强制放到了 BIN 的开头。链接脚本文件 hypervisor.lds 会再构建时被构建系统根据hypervisor/hypervisor.lds.S 自动创建生成(就是简单的展开各种宏(架构不同,宏值不同))。

JAILHOUSE_BASE

  JAILHOUSE_BASEjailhouse*.bin 的链接地址,它的具体值被定义到了 hypervisor/arch 中不同架构的 jailhouse_header.h 中。针对同一架构,它一个固定的虚拟地址。
在这里插入图片描述

  1. x86平台反汇编 objdump --source --all-headers --demangle --line-numbers --wide hypervisor/hypervisor-intel.o > hypervisor/hypervisor-intel.lst 查看
    在这里插入图片描述

  2. ARM64 平台反汇编 aarch64-none-linux-gnu-objdump --source --all-headers --demangle --line-numbers --wide hypervisor/hypervisor.o > hypervisor/hypervisor.lst 查看
    在这里插入图片描述

入口点

  jailhouse*.bin 本身不是一个可以直接运行的程序,所以它没有显示定义入口点。作为虚拟机管理程序,它本身来处理虚拟化相关的问题。无论何种架构,都是通过特定架构 hypervisor/arch/*/entry.S 中的 int arch_entry(unsigned int cpu_id) 这个接口来开启虚拟化配置。

arch_entry()

  arch_entry() 函数必须在每个在线的 CPU 上被调用,以便将系统控制权交给 Jailhouse。Jailhouse 将等待指定数量(由 struct jailhouse_header 中的 .online_cpus 指定)的 CPU 都完成初始化,并且在所有启动初始化的 CPU 都完成之前,该函数不会返回。在虚拟机监控程序激活期间未初始化的 CPU 在 Jailhouse 再次停用之前不能被任何单元使用。

  • 函数原型: int arch_entry(unsigned int cpu_id)
  • 参数:
    • cpu_id:调用方 CPU 的唯一逻辑 ID
  • 返回值:0 表示成功;其他值表示失败,通常取值如下:
    • -EIO (-5):lacking hardware capabilities or unsupported hardware state (as configured by Linux)
    • -ENOMEM (-12): insufficient hypervisor-internal memory
    • -EBUSY (-16): a required hardware resource is already in use
    • -ENODEV (-19): CPU or I/O virtualization unit missing
    • -EINVAL (-22): invalid system configuration
    • -ERANGE (-34): a resource ID is out of supported range

  对于一次初始化尝试,初始化函数将始终在所有 CPU 上返回相同的代码。

entry()

  arch_entry() 内部最终通过调用定义于 hypervisor/setup.c 中的架构无关的 int entry(unsigned int cpu_id, struct per_cpu *cpu_data) 函数最终实现启动虚拟化功能。

Hypervisor 与 Cell 间接口

  Jailhouse 虚拟机管理程序在运行时提供了三种与 Cell 交互的接口。第一种是只读检测接口。第二种是一组超调用,Cell 可以通过执行特定于体系结构的指令来同步调用这些超调用,从而切换到 Hypervisor 模式。第三种接口由位于每个 Cell 内存区域中的变量组成,该内存区域在 Hypervisor 和特定 Cell 之间共享。

只读检测接口

  这种接口对于那些不仅仅在 Jailhouse 的 Cell 内部工作的 Cell 代码非常有用。该 ABI 是特定于体系结构的,到目前为止,它仅适用于 x86 架构。在 x8 6架构上,Jailhouse 在执行 cpuid 指令时修改返回的寄存器值如下所示:
在这里插入图片描述

Hypercalls

  超调用通常通过指定的指令发出,该指令会导致从客户模式切换到虚拟机管理程序模式。在引起模式切换之前,Cell 必须在预定义的寄存器或已知的内存位置准备好调用的参数。完成的超调用的返回码通过类似的通道传递。超调用 ABI 的详细信息是特定于体系结构的,将在以下部分中定义。
在这里插入图片描述
  这些调用会由定义于 hypervisor/control.c 中的 long hypercall(unsigned long code, unsigned long arg1, unsigned long arg2) 来进行分发然后进一步来处理。

long hypercall(unsigned long code, unsigned long arg1, unsigned long arg2)
{
	struct per_cpu *cpu_data = this_cpu_data();

	cpu_data->public.stats[JAILHOUSE_CPU_STAT_VMEXITS_HYPERCALL]++;

	switch (code) {
	case JAILHOUSE_HC_DISABLE:
		return hypervisor_disable(cpu_data);
	case JAILHOUSE_HC_CELL_CREATE:
		return cell_create(cpu_data, arg1);
	case JAILHOUSE_HC_CELL_START:
		return cell_start(cpu_data, arg1);
	case JAILHOUSE_HC_CELL_SET_LOADABLE:
		return cell_set_loadable(cpu_data, arg1);
	case JAILHOUSE_HC_CELL_DESTROY:
		return cell_destroy(cpu_data, arg1);
	case JAILHOUSE_HC_HYPERVISOR_GET_INFO:
		return hypervisor_get_info(cpu_data, arg1);
	case JAILHOUSE_HC_CELL_GET_STATE:
		return cell_get_state(cpu_data, arg1);
	case JAILHOUSE_HC_CPU_GET_INFO:
		return cpu_get_info(cpu_data, arg1, arg2);
	case JAILHOUSE_HC_DEBUG_CONSOLE_PUTC:
		if (!CELL_FLAGS_VIRTUAL_CONSOLE_PERMITTED(
			cpu_data->public.cell->config->flags))
			return trace_error(-EPERM);
		printk("%c", (char)arg1);
		return 0;
	default:
		return -ENOSYS;
	}
}
通信区域

  通信区域是一个每个单元内的内存区域,默认情况下,虚拟机管理程序和特定单元都可以对其进行读写。这是一种可选的通信机制。如果某个单元需要使用该区域,则必须通过其配置将该区域映射到单元的地址空间。如果单元配置为在通信区域方面是被动的(单元标志 JAILHOUSE_CELL_PASSIVE_COMMREG)并且该区域已被映射,那么必须在单元配置中将其声明为只读。
在这里插入图片描述

管理工具

  管理工具主要由源码 tools 目录下的 jailhouse.c 在构建之后生成的 jailhouse 可执行程序以及该目录下的一些 Python 脚本 jailhouse-* 组成。jailhouse 会被放到 /usr/local/sbin/jailhouse 目录下,而那些 Python 脚本最终会被安装到 usr/local/libexec/jailhouse 目录下。
在这里插入图片描述

可执行程序 jailhouse

  jailhouse 这个可执行程序就是所有管理命令的入口,它就是一个标准的用户空间 Linux C 程序。当我们执行 Jailhouse 命令时,命令首先来到了 tools/jailhouse.cint main(int argc, char *argv[])函数,它负责解析传入的各个选项及参数,然后调用相应的接口进一步处理。

int main(int argc, char *argv[])
{
	int fd;
	int err;

	if (argc < 2)
		help(argv[0], 1);

	if (strcmp(argv[1], "enable") == 0) {
		err = enable(argc, argv);
	} else if (strcmp(argv[1], "disable") == 0) {
		fd = open_dev();
		err = ioctl(fd, JAILHOUSE_DISABLE);
		if (err)
			perror("JAILHOUSE_DISABLE");
		close(fd);
	} else if (strcmp(argv[1], "cell") == 0) {
		err = cell_management(argc, argv);
	} else if (strcmp(argv[1], "console") == 0) {
		err = console(argc, argv);
	} else if (strcmp(argv[1], "config") == 0 ||
		   strcmp(argv[1], "hardware") == 0) {
		call_extension_script(argv[1], argc, argv);
		help(argv[0], 1);
	} else if (strcmp(argv[1], "--version") == 0) {
		printf("Jailhouse management tool %s\n", JAILHOUSE_VERSION);
		return 0;
	} else if (strcmp(argv[1], "--help") == 0) {
		help(argv[0], 0);
	} else {
		help(argv[0], 1);
	}

	return err ? 1 : 0;
}

Python 脚本 jailhouse-*

  对于那些 Python 脚本,我们也不直接使用,而是则由 jailhouse 来帮我们调用的。具体就在 tools/jailhouse.c 中的 static void call_extension_script(const char *cmd, int argc, char *argv[]) 函数中通过 Linux 系统的进程调用函数 execvp 来实现。

static void call_extension_script(const char *cmd, int argc, char *argv[])
{
	const struct extension *ext;
	char new_path[PATH_MAX];
	char script[64];

	if (argc < 3)
		return;

	for (ext = extensions; ext->cmd; ext++) {
		if (strcmp(ext->cmd, cmd) != 0 ||
		    strcmp(ext->subcmd, argv[2]) != 0)
			continue;

		snprintf(new_path, sizeof(new_path), "PATH=%s:%s:%s",
			dirname(argv[0]), JAILHOUSE_EXEC_DIR,
			getenv("PATH") ? : "");
		putenv(new_path);

		snprintf(script, sizeof(script), "jailhouse-%s-%s",
			 cmd, ext->subcmd);
		execvp(script, &argv[2]);

		perror("execvp");
		exit(1);
	}
}

jailhouse-gcov-extract

  Jailhouse 支持在运行时收集代码覆盖率信息(gcov)。gcov(GNU Coverage) 是一个测试代码覆盖率的工具,工作原理是基于代码插桩(code instrumentation)技术。在编译源代码时,通过添加 -ftest-coverage-fprofile-arcs 选项这两个 GCC 编译器选项,编译器会在生成的可执行文件中插入特殊的监控代码。这些监控代码将跟踪源代码中的每个执行路径,并记录下来它们被执行的次数。

  1. 为了使用该特性,必须新建 include/jailhouse/config.h 文件,并在文件中将 CONFIG_JAILHOUSE_GCOV 定义为 1
  2. 首先正常运行一个 Jailhouse 虚拟机,最后 sudo jailhouse disable 禁用 Jailhouse,但是不要卸载 jailhouse.ko
  3. 执行 ./tools/jailhouse-gcov-extract 提取数据生成 *.gcda 文件
  4. 使用其他上层工具(例如,lcov)来处理生成 *.gcda 文件即可

配置文件

  在 Jailhouse 中,所有的 Cell 的硬件资源必须是静态分配的。因此,在启动 Cell 之前,我们必须要有一个配置文件,这个配置文件告诉 Jailhouse 每个 Cell 可以使用哪些硬件资源。

  Jailhouse 采用以 .cell 为扩展名的二进制文件作为配置文件,而 .cell 文件是由一个包含一个 C 语言结构体变量来描述硬件资源的 .c 文件生成的。而我们需要根据 Jailhouse 给出的一些示例(configs)目录下书写自己的 .c 文件,并进一步编译为 .cell 文件来使用。

  1. 使用命令 jailhouse config check [-h] SYSCONFIG [CELLCONFIG [CELLCONFIG ...]] 可以检查我们的配置文件
    在这里插入图片描述

SYSCONFIG

  SYSCONFIG(全局配置文件)就是 Root Cell 对应的配置文件,它告诉 Jailhouse 当前系统下所有可用的资源有哪些。当我们执行 jailhouse enable SYSCONFIG 时,Jailhouse 就会将 SYSCONFIG 中描述符的资源放到 Root Cell 中。

  1. 对于 x86 架构,Jailhouse 提供了 sudo jailhouse hardware check 命令来自动检测当前系统配置,并提供了 sudo jailhouse config create sysconfig.csysconfig.c 名字可自定义) 来自动生成针对当前系统的配置文件。
  2. 注意,通过以上命令生成的 sysconfig.c 文件的用户是 root,我们可以使用命令 sudo chown zcs:zcs sysconfig.c 更改为自己的用户名和用户组,这样再后续编辑是比较方便。

  我们需要将生成的 sysconfig.c 文件放在 Jailhouse 源码的 configs/x86/ 目录中,重新构建 Jailhouse 时,构建系统会将自动为其中的 .c 生成一个相应的 .cell 文件。

CELLCONFIG

  CELLCONFIG(CELL 配置文件)就是 Non-root Cell 使用的配置文件,定义了 Non-root Cell 可以使用的物理资源,当我们创建 Non-root Cell 时,Jailhouse 就会根据 Non-root Cell 的配置文件,从全局配置文件(Root Cell)中分离出指定的资源。

  对于 Non-root Cell 的配置文件需要参考 configs 目录下对应架构下的 .c 文件来手动创建。同样,写好的 .c 文件需要放到 configs/x86/ 目录中,重新构建 Jailhouse 时,构建系统会将自动为其中的 .c 生成一个相应的 .cell 文件。

虚拟机固件

  Jailhouse 虚拟机中运行的系统镜像或者是一个裸机程序固件被称为 inmate,目前可以是 Linux、FreeRTOS、ERIKA3 RTOS、Zephyr 其中之一。对于 Linux,Jailhouse 无法运行未经修改的 Linux 内核!

参考

  1. https://software-dl.ti.com/processor-sdk-linux/esd/docs/06_03_00_106/linux/Foundational_Components/Virtualization/Jailhouse.html
  2. https://www.elecfans.com/d/2338769.html
  3. https://variwiki.com/index.php?title=Jailhouse_Guide
  4. https://blog.csdn.net/v6543210/article/details/113890847
  5. https://www.21ic.com/a/933932.html
  6. https://blog.csdn.net/v6543210/article/details/118031563
  7. https://www.21ic.com/a/933932.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决使用elmessage 没有样式的问题

错误情况 这里使用了一个消息提示&#xff0c;但是没有出现正确的样式&#xff0c; 错误原因和解决方法 出现这种情况是因为&#xff0c;在全局使用了按需导入&#xff0c;而又在局部组件中导入了ElMessage组件&#xff0c;我们只需要将局部组件的import删除就可以了 import…

企业商家如何精明选择软件开发公司

在当今信息化社会&#xff0c;企业的运营和发展已经离不开软件系统的支持。而选择一个合适的软件开发公司&#xff0c;则成为了企业商家在信息化道路上的一大挑战。那么&#xff0c;究竟如何挑选出既符合业务需求&#xff0c;又能保障项目成功的软件开发公司呢&#xff1f; 明…

MySQL查询数据库中所有表名表结构及注释以及生成数据库文档

MySQL查询数据库中所有表名表结构及注释 生成数据库文档在后面&#xff01;&#xff01;&#xff01; select t.TABLE_COMMENT -- 数据表注释 , c.TABLE_NAME -- 表名称 , c.COLUMN_COMMENT -- 数据项 , c.COLUMN_NAME -- 英文名称 , -- 字段描述 , upper(c.DATA_TYPE) as …

为什么要用AI大模型?

前言 2021 年 8 月份&#xff0c;李飞飞和 100 多位学者联名发表一份 200 多页的研究报告《On the Opportunities and Risk of Foundation Models》&#xff0c;深度地综述了当前大规模预训练模型面临的机遇和挑战。 语言模型已经深刻变革了自然语言处理领域的研究和实践。近…

20240613每日前端-------vue3实现聊天室(二)

看效果图&#xff1a; 今天具体讲下&#xff0c;聊天消息框的布局&#xff1a; 消息框大致分为两块&#xff1a; 别人发来的消息自己发出的消息 元素如下&#xff1a; 头像消息发送人发送时间 html代码设计如下&#xff1a; 整体先用一个div作为外边框&#xff0c;观察上面…

告别“人治”时代,物业运维平台能否成为行业新标准?

随着数字化时代的飞速发展&#xff0c;智能化、数字化已经遍及所有的行业。物业服务企业也不例外&#xff0c;你是否还在想象物业运维工作依旧停留在手动报修、纸质记录的古老时代&#xff1f;那么&#xff0c;你就OUT了&#xff0c;物业运维平台已经悄然崛起&#xff0c;正在以…

RuoYi: 企业级快速开发平台

目录 前言1 项目介绍1.1 简介1.2 特性 2 技术选型3 功能方面4 代码解释4.1 控制器层示例4.2 服务层示例4.3 数据访问层示例 4 推荐理由4.1 高效开发4.2 灵活性和扩展性4.3 完善的功能和安全性4.4 活跃的开源社区 结语 前言 在现代企业级应用开发中&#xff0c;高效、稳定、安全…

java(JVM)

JVM Java的JVM&#xff08;Java虚拟机&#xff09;是运行Java程序的关键部件。它不直接理解或执行Java源代码&#xff0c;而是与Java编译器生成的字节码&#xff08;Bytecode&#xff09;进行交互。下面是对Java JVM更详尽的解释&#xff1a; 1.字节码&#xff1a; 当你使用J…

SolidWorks对设计电脑硬件配置要求是怎么样的

SolidWorks&#xff0c;作为达索系统&#xff08;Dassault Systemes&#xff09;旗下的子公司&#xff0c;一直以其出色的机械设计软件解决方案而著称。它是基于Parasolid内核开发&#xff0c;是单核三维设计软件&#xff0c;面上使用比较多的版本有SolidWorks2022、SolidWorks…

13大最佳工程项目管理系统软件盘点

国内外主流的13款工程项目管理系统软件&#xff1a;Worktile、中建软件、泛微建筑项目管理软件、LiquidPlanner、Wrike、建文软件、广联达、Microsoft Project、泛普软件、Procore、Buildertrend、Fieldwire、Autodesk Construction Cloud。 在快速变化的工程领域&#xff0c;有…

java采集微信公众号数据

需求背景: 最新需要调用微信公众号api 去微信公众号采集 发布文章数据。 &#xff08;本片文章的意义&#xff1a;根据自己开发的方案来提供思路&#xff0c;当然那不会提供代码。代码是最没有 含金量的东西。&#xff09; 1:遇到的坑:首先 想到的是调用 https://api.weixin…

github 本地仓库上传及报错处理

一.本地文件上传 这里为上传部分&#xff0c;关于gitbash安装配置&#xff0c;读者可自行搜索&#xff0c;由于已经安装完成&#xff0c;未进行截图保存&#xff0c;这里便不做赘述。 1.登录git账号并创建一个仓库 点击仓库打开后会看到这个仓库的网址链接&#xff08;这个链…

Mac安装jadx并配置环境

jadx官网&#xff1a;GitHub - skylot/jadx: Dex to Java decompiler 第一种&#xff1a; 安装jadx命令&#xff1a; brew install jadx 启动jadx-gui命令&#xff1a; jadx-gui 可能遇到的问题&#xff1a; Downloading https://formulae.brew.sh/api/formula.jws.json** h…

Python-程序流程控制

目录 1. 分支语句 1.1 if 1.2 if-else 1.3 if-elif-else 2. 循环语句 2.1 while 2.2 for 3.跳转语句 3.1 break 3.2 continue 1. 分支语句 1.1 if aint(input("请输入成绩")) if a>100:print ("牛逼") if a<60:print("不牛逼")1.2 if-e…

《软件定义安全》之八:软件定义安全案例

第8章 软件定义安全案例 1.国外案例 1.1 Fortinet&#xff1a;传统安全公司的软件定义方案 Fortinet的软件定义安全架构强调与数据中心的结合&#xff0c;旨在将安全转型为软件定义的模式&#xff0c;使安全运维能够与数据中心的其他部分一样灵活、弹性。在Fortinet看来&…

【最新鸿蒙应开发】——HarmonyOS沙箱目录

鸿蒙应用沙箱目录 1. 应用沙箱概念 应用沙箱是一种以安全防护为目的的隔离机制&#xff0c;避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下&#xff0c;应用可见的目录范围即为应用沙箱目录。 对于每个应用&#xff0c;系统会在内部存储空间映射出一个专属的应用沙箱…

使用神卓互联来访问单位内部web【内网穿透神器】

在现代工作环境中&#xff0c;有时我们需要从外部访问单位内部的 web 资源&#xff0c;而神卓互联这款内网穿透神器就能完美地满足这一需求。 使用神卓互联来访问单位内部 web 其实并不复杂&#xff0c;以下是大致的使用步骤和配置方法。 首先&#xff0c;我们需要在单位内部的…

pointnet

train_classification.py 把第91行修改为自己的路径&#xff0c;就可以运行了 test_cla.py&#xff0c;需要训练完才能运行测试&#xff0c;我没训练完&#xff0c;所以报错显示我没有best.pth文件 网盘里面是我运行的训练和测试的视频&#xff0c;以及源代码&#xff0c;数…

YOLOv10在RK3588上的测试(进行中...)

1.代码源 国内镜像站在gitcode。这个镜像站也基本上包含了github上常用项目的镜像。然后它的主发布源在这里&#xff1a; GitCode - 全球开发者的开源社区,开源代码托管平台 yolov10是清华主导做的... 然后&#xff0c;在维护列表里看到了这个&#xff1a; 2024年05月31日&am…

【深度优先搜索 广度优先搜索】297. 二叉树的序列化与反序列化

本文涉及知识点 深度优先搜索 广度优先搜索 深度优先搜索汇总 图论知识汇总 LeetCode297. 二叉树的序列化与反序列化 序列化是将一个数据结构或者对象转换为连续的比特位的操作&#xff0c;进而可以将转换后的数据存储在一个文件或者内存中&#xff0c;同时也可以通过网络传…