虚拟化 之一 详解 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。同时在构建时需要注意以下几点:
- 需要使能
CONFIG_JAILHOUSE_GUEST - 禁用
CONFIG_SERIO和CONFIG_PM_TRACE_RTC - 一般还应该禁用所有不需要的驱动程序和特性,以避免不需要的 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 架构
-
必须禁用 Linux Kernel 对 VT-d IOMMU 的使用(DMAR)。这个可以通过在 GRUB 配置文件中设置
GRUB_CMDLINE_LINUX = "intel_iommu=off"(IOMMU 硬件有 intel/amd/arm 的等,一般用 intel 的硬件) 这个启动参数来处理。- kvm 一定要用 intel_iommu=on,DPDK/SPDK 如果绑定 vfio-pci 那也一定要求 intel_iommu=on,如果绑定 uio/igb_uio 那么就不需要intel_iommu=on
dmesg | grep -E "DMAR|IOMMU"查看
-
要利用更快的 x2APIC,需要在内核中打开中断重新映射。这个需要再构建内核时启用
CONFIG_IRQ_REMAP这个配置项 -
Jailhouse 本身和每个 Cell 都需要一块连续的 RAM,这个必须在 Kernel 启动之前配置。通常是使用
memmap或mem这两个启动命令来实现。在 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 作为一个极度精简的虚拟化实现方案,其代码量还是非常小的,几万行代码量就实现了一个功能强大的虚拟机。
-
.github和ci:这两个目录下是用来持续集成环境中使用的构建 Jailhouse 所需的 Linux Kernel 的相关配置文件,目前仅支持 GitHub Actions。 -
config:这个目录下就是针对arm、arm64、x86架构下用于生成 Jailhouse 的 Cell 的配置文件对应的源文件。在对应架构下构建 Jailhouse 时,对应架构目录下的每个.c就会被构建为对应的.cell文件。- 配置文件的内容其实就是一个 C 语言的结构体变量,因此使用的就是
.c扩展名 - 该目录下的
.c文件实际上都是一些示例,在真正使用时,我们需要提供自己的配置文件!
- 配置文件的内容其实就是一个 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 的构建过程非常简单,但是由于不同的平台下的虚拟化技术的差异,在构建时遇到的问题也不一样,受限于博文篇幅,我们将在后续博文中详细学习在不同平台下的构建及使用。
- 虚拟化 之二 详解 jailhouse(x86 平台)的构建过程、配置及使用
- 虚拟化 之三 详解 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) 函数就会进行各种初始化,主要就干了以下几个事:
- 开头的这一堆的宏定义主要就是为了解决 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函数的地址,然后再进一步获取到想要的符号。 - 通过
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 # 各种事件- Root 设备是一个虚拟设备,以该 Root 设备为父设备调用
kobject_create_and_add就可以让其他设备可以挂在它的下面 - 其中有些节点需要在执行相应的命令后才会有具体的内容
- Root 设备是一个虚拟设备,以该 Root 设备为父设备调用
- 通过
misc_register(&jailhouse_misc_dev);注册struct miscdevice设备。加载驱动之后,就会创建/dev/jailhouse这个设备。用户空间的 Jailhouse 的管理工具使用ioctl()系统调用通过jailhouse.ko创建的/dev/jailhouse这个文件向jailhouse.ko发送各种请求。 - 调用
jailhouse_pci_register()将自身注册为一个虚拟的 PCI 设备驱动程序,以便它可以获取分配的设备。 - 调用
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_BASE 是 jailhouse*.bin 的链接地址,它的具体值被定义到了 hypervisor/arch 中不同架构的 jailhouse_header.h 中。针对同一架构,它一个固定的虚拟地址。

-
x86平台反汇编
objdump --source --all-headers --demangle --line-numbers --wide hypervisor/hypervisor-intel.o > hypervisor/hypervisor-intel.lst查看

-
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.c 的 int 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 编译器选项,编译器会在生成的可执行文件中插入特殊的监控代码。这些监控代码将跟踪源代码中的每个执行路径,并记录下来它们被执行的次数。
- 为了使用该特性,必须新建
include/jailhouse/config.h文件,并在文件中将CONFIG_JAILHOUSE_GCOV定义为 1 - 首先正常运行一个 Jailhouse 虚拟机,最后
sudo jailhouse disable禁用 Jailhouse,但是不要卸载jailhouse.ko - 执行
./tools/jailhouse-gcov-extract提取数据生成*.gcda文件 - 使用其他上层工具(例如,
lcov)来处理生成*.gcda文件即可
配置文件
在 Jailhouse 中,所有的 Cell 的硬件资源必须是静态分配的。因此,在启动 Cell 之前,我们必须要有一个配置文件,这个配置文件告诉 Jailhouse 每个 Cell 可以使用哪些硬件资源。
Jailhouse 采用以 .cell 为扩展名的二进制文件作为配置文件,而 .cell 文件是由一个包含一个 C 语言结构体变量来描述硬件资源的 .c 文件生成的。而我们需要根据 Jailhouse 给出的一些示例(configs)目录下书写自己的 .c 文件,并进一步编译为 .cell 文件来使用。
- 使用命令
jailhouse config check [-h] SYSCONFIG [CELLCONFIG [CELLCONFIG ...]]可以检查我们的配置文件

SYSCONFIG
SYSCONFIG(全局配置文件)就是 Root Cell 对应的配置文件,它告诉 Jailhouse 当前系统下所有可用的资源有哪些。当我们执行 jailhouse enable SYSCONFIG 时,Jailhouse 就会将 SYSCONFIG 中描述符的资源放到 Root Cell 中。
- 对于 x86 架构,Jailhouse 提供了
sudo jailhouse hardware check命令来自动检测当前系统配置,并提供了sudo jailhouse config create sysconfig.c(sysconfig.c名字可自定义) 来自动生成针对当前系统的配置文件。 - 注意,通过以上命令生成的
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 内核!
参考
- https://software-dl.ti.com/processor-sdk-linux/esd/docs/06_03_00_106/linux/Foundational_Components/Virtualization/Jailhouse.html
- https://www.elecfans.com/d/2338769.html
- https://variwiki.com/index.php?title=Jailhouse_Guide
- https://blog.csdn.net/v6543210/article/details/113890847
- https://www.21ic.com/a/933932.html
- https://blog.csdn.net/v6543210/article/details/118031563
- https://www.21ic.com/a/933932.html
相关文章:
虚拟化 之一 详解 jailhouse 架构及原理、软硬件要求、源码文件、基本组件
Jailhouse 是一个基于 Linux 实现的针对创建工业级应用程序的小型 Hypervisor,是由西门子公司的 Jan Kiszka 于 2013 年开发的,并得到了官方 Linux 内核的支持,在开源社区中获得了知名度和吸引力。 Jailhouse Jailhouse 是一种轻量级的虚拟化…...
汇凯金业:黄金期货交易时间规则
黄金期货交易时间规则因交易所不同而有所差异。以下是几个主要交易所的黄金期货交易时间及其相关规则: 一、纽约商品交易所(COMEX) 纽约商品交易所(COMEX)是全球最大的黄金期货交易市场之一,其黄金期货交易时间如下: 电子交易时间(通过CME…...
LogicFlow 学习笔记——4. LogicFlow 基础 边 Edge
边 Edge 和节点一样,LogicFlow 也内置一些基础的边。LogicFlow 的内置边包括: 直线 - line直角折现 - polyline贝塞尔曲线 - bezier 新建 src/views/Example/LogicFlow/Example08.vue 并编写如下代码: <script setup lang"ts&quo…...
QPS、TPS、并发量、PV、UV
QPS、TPS、并发量、PV、UV 目录 QPS、TPS、并发量、PV、UVQPS(Queries Per Second)TPS (Transactions Per Second)并发量 (Concurrency)PV (Page Views)UV (Unique Visitors) QPS(Queries Per Second) 含义:每秒查询率应用场景:常用于计算机中各类搜索引…...
深中通道通车在即,苏州金龙新V系穿梭巴士引领大湾区交通发展新篇章
深中通道,总投资500亿元,历时七年建成的世界级跨海工程,即将投入运营。该桥连接深圳、中山,全长24公里,通过“桥、岛、隧、水下互通”设计,克服地域障碍。桥面“穿梭巴士”同步启动,提供24小时跨…...
集成学习 #数据挖掘 #Python
集成学习是一种机器学习方法,它通过结合多个模型的预测结果来提高整体性能和稳定性。这种方法的主要思想是“集合智慧”,通过将多个模型(比如决策树、随机森林、梯度提升机等)的预测集成起来,可以减少单个模型的过拟合…...
IDEA 中设置 jdk 的版本
本文介绍一下 IDEA 中设置 jdk 版本的步骤。 一共有三处需要配置。 第一处 File --> Project Structure Project 和 Modules 下都需要指定一下。 第二处 File --> Settings 第三处 运行时的配置...
AI日报|Luma推出AI视频模型,又一Sora级选手登场?SD3 Medium发布,图中文效果改善明显
文章推荐 AI日报|仅三个月就下架?微软GPT Builder出局AI竞争赛;马斯克将撤回对奥特曼的诉讼 谁是最会写作文的AI“考生”?“阅卷老师”ChatGPT直呼惊艳! ⭐️搜索“可信AI进展“关注公众号,获取当日最新…...
嵌入式系统复习(一)
第一章 嵌入式系统的定义、特点 嵌入式系统是以应用为中心,以计算机技术为基础,软件硬件可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。 特点:嵌入性 专用性 计算机系统 嵌入式系统典型组成…...
一次搞定:Java中数组拷贝VS数组克隆
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一…...
Java多线程编程与并发处理
引言 在现代编程中,多线程和并发处理是提高程序运行效率和资源利用率的重要方法。Java提供了丰富的多线程编程支持,包括线程的创建与生命周期管理、线程同步与锁机制、并发库和高级并发工具等。本文将详细介绍这些内容,并通过表格进行总结和…...
C++ 35 之 对象模型基础
#include <iostream> #include <string.h> using namespace std;class Students05{ public:// 只有非静态成员变量才算存储空间,其他都不算int s_a; // 非静态成员变量,算对象的存储空间double s_c;// 成员函数 不算对象的存储空间void f…...
PHP超级全局变量:功能、应用及最佳实践
PHP中的超级全局变量(Superglobal Variables)是预定义的数组,它们在脚本的全部作用域内都可以访问,无需使用global关键字。超级全局变量包含了关于请求、会话、服务器等各种信息,常见的有$_GET、$_POST、$_REQUEST、$_…...
python在windows创建的文件,换成linux系统格式
python在windows创建的文件,换成linux系统格式 dos2unix.exe的下载(下载的文件放入路径下:C:\Windows\System32) 链接:https://pan.baidu.com/s/10fC2tfvUtbh-axJ21cj_Xw?pwdm3zc 提取码:m3zc 批量修改文件格式 import subpr…...
最新区块链论文速读--CCF A会议 ICSE 2024 共13篇 附pdf下载 (2/2)
Conference:International Conference on Software Engineering (ICSE) CCF level:CCF A Categories:Software Engineering/System Software/Programming Languages Year:2024 Num:13 第1~7篇区块链文章请点击此处…...
C++ 34 之 单例模式
#include <iostream> #include <string.h> using namespace std;class King{// 公共的函数,为了让外部可以获取唯一的实例 public:// getInstance 获取单例 约定俗成static King* getInstance(){return true_king;}private: // 私有化// 构造函数设置为…...
SAP BW:传输转换源系统-源系统映射关系
最近有朋友再问问我源系统映射关系怎么配置,想着写一个怕以后忘了。 简单说下这个是干嘛的,其实就是配置一个源系统到目标系统的一个映射,这样传输的时候才知道传过来的数据源要变成目标系统的数据源。 比如下图,在开发环境&…...
React+TS前台项目实战(九)-- 全局常用组件弹窗Dialog封装
文章目录 前言Dialog公共弹窗组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局公共弹窗Dialog组件封装,将用到上篇封装的模态框Modal组件。有时在前台项目中,偶尔要用到一两个常用的组件,如 弹窗&#x…...
利用视觉分析技术提升水面漂浮物、水面垃圾检测效率
随着城市化进程的加速和工业化的发展,水体污染问题日益严重,水面漂浮物成为水环境治理的一大难题。传统的水面漂浮物检测方法主要依赖人工巡查和简单的传感器检测,存在着效率低、准确率不高等问题。为了提升水面漂浮物检测的效率和准确性&…...
NFT 智能合约实战-快速开始(1)NFT发展历史 | NFT合约标准(ERC-721、ERC-1155和ERC-998)介绍
文章目录 NFT 智能合约实战-快速开始(1)NFT发展历史国内NFT市场国内NFT合规性如何获得NFT?如何查询NFT信息?在 OpenSea 上查看我们的 NFT什么是ERC721NFT合约标准ERC-721、ERC-1155和ERC-998 对比ERC721IERC721.sol 接口内容关于合约需要接收 ERC721 资产 onERC721Received…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
从零开始了解数据采集(二十八)——制造业数字孪生
近年来,我国的工业领域正经历一场前所未有的数字化变革,从“双碳目标”到工业互联网平台的推广,国家政策和市场需求共同推动了制造业的升级。在这场变革中,数字孪生技术成为备受关注的关键工具,它不仅让企业“看见”设…...
聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇
根据 QYResearch 发布的市场报告显示,全球市场规模预计在 2031 年达到 9848 万美元,2025 - 2031 年期间年复合增长率(CAGR)为 3.7%。在竞争格局上,市场集中度较高,2024 年全球前十强厂商占据约 74.0% 的市场…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...
如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...
