概述

AxVisor 是一个基于 ArceOS 框架实现的 Hypervisor(也叫 Virtual Machine Manager,VMM)。其目标是利用 ArceOS 提供的基础操作系统功能作为基础实现一个统一的模块化 Hypervisor。

AxVisor

统一是指使用同一套代码同时支持 x86_64、AArch64 、RISC-V LoongArch 这四种架构,以最大化复用架构无关代码,简化代码开发和维护成本。

模块化则是指 Hypervisor 的功能被分解为多个模块,每个模块实现一个特定的功能,模块之间通过标准接口进行通信,以实现功能的解耦和复用。

Copyright © 2025 • Created by ArceOS Team

硬件平台

AxVisor 被设计为可以在 x86_64、AArch64 、RISC-V LoongArch 四大芯片架构上运行,目前,已经在如下平台进行了验证:

  • QEMU ARM64 virt (qemu-max)
  • Rockchip RK3568 / RK3588
  • 黑芝麻华山 A1000
  • 更多硬件平台逐步添加中

Copyright © 2025 • Created by ArceOS Team

客户机系统支持

目前,AxVisor 已经在对如下系统作为客户机的情况进行了验证。

ArceOS

ArceOS 是一个用 Rust 编写的专为嵌入式系统和物联网设备设计的轻量级操作系统,提供简单、高效、可定制的功能,适合需要实时响应和低资源开销的应用场景。

Starry-OS

Starry-OS 是一款轻量级、模块化且高效的操作系统,专为嵌入式系统和物联网设备设计。它具有实时性支持、跨平台能力以及灵活的定制选项,适合在资源受限的环境中运行。

NimbOS

NimbOS 是一款用 Rust 编写的专为资源受限环境和嵌入式设备设计的实时操作系统,具有轻量化、实时支持、低功耗、模块化架构等优点。

Linux

Copyright © 2025 • Created by ArceOS Team

Run AxVisor on QEMU

目前,AxVisor 支持在 aarch64x86_64 以及 riscv64 三个体系结构下的 QEMU 上运行。

Copyright © 2025 • Created by ArceOS Team

Run AxVisor on QEMU AArch64

目前,在 QEMU AArch64 平台上已经对独立运行 ArceOS 和 Linux 以及同时运行 ArceOS + Linux 的情况进行了验证。

ArceOS

首先,获取 ArceOS 主线代码 git clone https://github.com/arceos-org/arceos.git,然后执行 make PLATFORM=aarch64-qemu-virt SMP=1 A=examples/helloworld 获取 helloworld_aarch64-qemu-virt.bin

从文件系统加载运行

  1. 制作一个磁盘镜像文件,并将客户机镜像放到文件系统中

    1. 使用 make disk_img 命令生成一个空的 FAT32 磁盘镜像文件 disk.img

    2. 手动挂载 disk.img,然后将自己的客户机镜像复制到该文件系统中

      $ mkdir -p tmp
      $ sudo mount disk.img tmp
      $ sudo cp helloworld_aarch64-qemu-virt.bin tmp/
      $ sudo umount tmp
      
  2. 修改对应的 ./configs/vms/arceos-aarch64.toml 文件中的配置项

    • image_location="fs" 表示从文件系统加载
    • kernel_path 指出内核镜像在文件系统中的路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
    • 其他
  3. 执行 make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml FEATURES=page-alloc-64g APP_FEATURES=fs run 构建 AxVisor,并在 QEMU 中启动。

          d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
       d88P888                           888     888 Y88b.
       d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
    d88P   888 888     888      88888888 888     888       "888
    d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-qemu-virt-hv
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = info
    smp = 1
    
    [  0.021701 0 axruntime:130] Logging is enabled.
    [  0.027394 0 axruntime:131] Primary CPU 0 started, dtb = 0x48000000.
    [  0.029626 0 axruntime:133] Found physcial memory regions:
    [  0.031888 0 axruntime:135]   [PA:0x40080000, PA:0x400f5000) .text (READ | EXECUTE | RESERVED)
    [  0.034860 0 axruntime:135]   [PA:0x400f5000, PA:0x4010b000) .rodata (READ | RESERVED)
    [  0.036593 0 axruntime:135]   [PA:0x4010b000, PA:0x40111000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.038382 0 axruntime:135]   [PA:0x40111000, PA:0x40151000) boot stack (READ | WRITE | RESERVED)
    [  0.039937 0 axruntime:135]   [PA:0x40151000, PA:0x40377000) .bss (READ | WRITE | RESERVED)
    [  0.041525 0 axruntime:135]   [PA:0x40377000, PA:0x48000000) free memory (READ | WRITE | FREE)
    [  0.043321 0 axruntime:135]   [PA:0x9000000, PA:0x9001000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.044954 0 axruntime:135]   [PA:0x9040000, PA:0x9041000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.046523 0 axruntime:135]   [PA:0x9100000, PA:0x9101000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.048067 0 axruntime:135]   [PA:0x8000000, PA:0x8020000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.049632 0 axruntime:135]   [PA:0xa000000, PA:0xa004000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.051230 0 axruntime:135]   [PA:0x10000000, PA:0x3eff0000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.052817 0 axruntime:135]   [PA:0x4010000000, PA:0x4020000000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.054762 0 axruntime:208] Initialize global memory allocator...
    [  0.056225 0 axruntime:209]   use TLSF allocator.
    [  0.069167 0 axmm:60] Initialize virtual memory management...
    [  0.098576 0 axruntime:150] Initialize platform devices...
    [  0.099990 0 axhal::platform::aarch64_common::gic:67] Initialize GICv2...
    [  0.106140 0 axtask::api:73] Initialize scheduling...
    [  0.114781 0 axtask::api:79]   use FIFO scheduler.
    [  0.116139 0 axdriver:152] Initialize device drivers...
    [  0.117557 0 axdriver:153]   device model: static
    [  0.143851 0 virtio_drivers::device::blk:59] config: 0x1000e000
    [  0.146209 0 virtio_drivers::device::blk:64] found a block device of size 65536KB
    [  0.151708 0 axdriver::bus::pci:104] registered a new Block device at 00:02.0: "virtio-blk"
    [  0.513409 0 axfs:41] Initialize filesystems...
    [  0.514900 0 axfs:44]   use block device 0: "virtio-blk"
    [  0.636117 0 fatfs::dir:139] Is a directory
    [  0.717647 0 fatfs::dir:139] Is a directory
    [  0.817118 0 fatfs::dir:139] Is a directory
    [  0.916598 0 fatfs::dir:139] Is a directory
    [  0.942786 0 axruntime:176] Initialize interrupt handlers...
    [  0.947032 0 axruntime:186] Primary CPU 0 init OK.
    [  0.948651 0:2 axvisor:17] Starting virtualization...
    [  0.950696 0:2 axvisor:19] Hardware support: true
    [  0.959113 0:4 axvisor::vmm::timer:103] Initing HV Timer...
    [  0.960950 0:4 axvisor::hal:117] Hardware virtualization support enabled on core 0
    [  1.084200 0:2 axvisor::vmm::config:33] Creating VM [1] "arceos"
    [  1.089658 0:2 axvm::vm:113] Setting up memory region: [0x40000000~0x41000000] READ | WRITE | EXECUTE
    [  1.132639 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x8000000~0x8050000] -> [0x8000000~0x8050000]
    [  1.137699 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9000000~0x9001000] -> [0x9000000~0x9001000]
    [  1.140182 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9010000~0x9011000] -> [0x9010000~0x9011000]
    [  1.142061 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9030000~0x9031000] -> [0x9030000~0x9031000]
    [  1.143926 0:2 axvm::vm:156] Setting up passthrough device memory region: [0xa000000~0xa004000] -> [0xa000000~0xa004000]
    [  1.147133 0:2 axvm::vm:191] VM created: id=1
    [  1.149137 0:2 axvm::vm:206] VM setup: id=1
    [  1.150930 0:2 axvisor::vmm::config:40] VM[1] created success, loading images...
    [  1.152892 0:2 axvisor::vmm::images::fs:102] Loading VM images from filesystem
    [  1.201653 0:2 axvisor::vmm:29] Setting up vcpus...
    [  1.205788 0:2 axvisor::vmm::vcpus:176] Initializing VM[1]'s 1 vcpus
    [  1.208187 0:2 axvisor::vmm::vcpus:207] Spawning task for VM[1] Vcpu[0]
    [  1.211894 0:2 axvisor::vmm::vcpus:219] Vcpu task Task(5, "VM[1]-VCpu[0]") created cpumask: [0, ]
    [  1.215220 0:2 axvisor::vmm:36] VMM starting, booting VMs...
    [  1.217058 0:2 axvm::vm:273] Booting VM[1]
    [  1.218682 0:2 axvisor::vmm:42] VM[1] boot success
    [  1.223139 0:5 axvisor::vmm::vcpus:240] VM[1] Vcpu[0] waiting for running
    [  1.225580 0:5 axvisor::vmm::vcpus:243] VM[1] Vcpu[0] running...
    
          d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
       d88P888                           888     888 Y88b.
       d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
    d88P   888 888     888      88888888 888     888       "888
    d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-qemu-virt
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = warn
    smp = 1
    
    Hello, world!
    [  1.249320 0:5 axvisor::vmm::vcpus:288] VM[1] run VCpu[0] SystemDown
    [  1.251119 0:5 axhal::platform::aarch64_common::psci:98] Shutting down...
    

从内存加载运行

  1. 修改对应的 ./configs/vms/arceos-aarch64.toml 中的配置项

    • image_location="memory" 配置项
    • kernel_path 指定内核镜像在工作空间中的相对/绝对路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
    • 其他
  2. 执行 make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/arceos-aarch64.toml FEATURES=page-alloc-64g run 构建 AxVisor,并在 QEMU 中启动。

    
          d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
       d88P888                           888     888 Y88b.
       d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
    d88P   888 888     888      88888888 888     888       "888
    d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-qemu-virt-hv
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = info
    smp = 1
    
    [  0.023017 0 axruntime:130] Logging is enabled.
    [  0.028629 0 axruntime:131] Primary CPU 0 started, dtb = 0x48000000.
    [  0.030723 0 axruntime:133] Found physcial memory regions:
    [  0.032913 0 axruntime:135]   [PA:0x40080000, PA:0x400d6000) .text (READ | EXECUTE | RESERVED)
    [  0.035838 0 axruntime:135]   [PA:0x400d6000, PA:0x400ef000) .rodata (READ | RESERVED)
    [  0.037540 0 axruntime:135]   [PA:0x400ef000, PA:0x400f5000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.039264 0 axruntime:135]   [PA:0x400f5000, PA:0x40135000) boot stack (READ | WRITE | RESERVED)
    [  0.040750 0 axruntime:135]   [PA:0x40135000, PA:0x4035b000) .bss (READ | WRITE | RESERVED)
    [  0.042266 0 axruntime:135]   [PA:0x4035b000, PA:0x48000000) free memory (READ | WRITE | FREE)
    [  0.043993 0 axruntime:135]   [PA:0x9000000, PA:0x9001000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.045562 0 axruntime:135]   [PA:0x9040000, PA:0x9041000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.047107 0 axruntime:135]   [PA:0x9100000, PA:0x9101000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.048584 0 axruntime:135]   [PA:0x8000000, PA:0x8020000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.050079 0 axruntime:135]   [PA:0xa000000, PA:0xa004000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.051598 0 axruntime:135]   [PA:0x10000000, PA:0x3eff0000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.053122 0 axruntime:135]   [PA:0x4010000000, PA:0x4020000000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.054983 0 axruntime:208] Initialize global memory allocator...
    [  0.056366 0 axruntime:209]   use TLSF allocator.
    [  0.069022 0 axmm:60] Initialize virtual memory management...
    [  0.098512 0 axruntime:150] Initialize platform devices...
    [  0.099837 0 axhal::platform::aarch64_common::gic:67] Initialize GICv2...
    [  0.105803 0 axtask::api:73] Initialize scheduling...
    [  0.114452 0 axtask::api:79]   use FIFO scheduler.
    [  0.115748 0 axruntime:176] Initialize interrupt handlers...
    [  0.121138 0 axruntime:186] Primary CPU 0 init OK.
    [  0.122705 0:2 axvisor:17] Starting virtualization...
    [  0.124615 0:2 axvisor:19] Hardware support: true
    [  0.132838 0:4 axvisor::vmm::timer:103] Initing HV Timer...
    [  0.134591 0:4 axvisor::hal:117] Hardware virtualization support enabled on core 0
    [  0.260703 0:2 axvisor::vmm::config:33] Creating VM [1] "arceos"
    [  0.266264 0:2 axvm::vm:113] Setting up memory region: [0x40000000~0x41000000] READ | WRITE | EXECUTE
    [  0.301715 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x8000000~0x8050000] -> [0x8000000~0x8050000]
    [  0.306525 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9000000~0x9001000] -> [0x9000000~0x9001000]
    [  0.309071 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9010000~0x9011000] -> [0x9010000~0x9011000]
    [  0.310897 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9030000~0x9031000] -> [0x9030000~0x9031000]
    [  0.312663 0:2 axvm::vm:156] Setting up passthrough device memory region: [0xa000000~0xa004000] -> [0xa000000~0xa004000]
    [  0.315628 0:2 axvm::vm:191] VM created: id=1
    [  0.317606 0:2 axvm::vm:206] VM setup: id=1
    [  0.319489 0:2 axvisor::vmm::config:40] VM[1] created success, loading images...
    [  0.322154 0:2 axvisor::vmm::images:24] Loading VM[1] images from memory
    [  0.329972 0:2 axvisor::vmm:29] Setting up vcpus...
    [  0.334059 0:2 axvisor::vmm::vcpus:176] Initializing VM[1]'s 1 vcpus
    [  0.336430 0:2 axvisor::vmm::vcpus:207] Spawning task for VM[1] Vcpu[0]
    [  0.340105 0:2 axvisor::vmm::vcpus:219] Vcpu task Task(5, "VM[1]-VCpu[0]") created cpumask: [0, ]
    [  0.343484 0:2 axvisor::vmm:36] VMM starting, booting VMs...
    [  0.345017 0:2 axvm::vm:273] Booting VM[1]
    [  0.346616 0:2 axvisor::vmm:42] VM[1] boot success
    [  0.351053 0:5 axvisor::vmm::vcpus:240] VM[1] Vcpu[0] waiting for running
    [  0.353230 0:5 axvisor::vmm::vcpus:243] VM[1] Vcpu[0] running...
    
          d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
       d88P888                           888     888 Y88b.
       d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
    d88P   888 888     888      88888888 888     888       "888
    d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-qemu-virt
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = warn
    smp = 1
    
    Hello, world!
    [  0.376780 0:5 axvisor::vmm::vcpus:288] VM[1] run VCpu[0] SystemDown
    [  0.378516 0:5 axhal::platform::aarch64_common::psci:98] Shutting down...
    

NimbOS

NimbOS 仓库的 release 页面已经编译生成了可以直接运行的 NimbOS 二进制镜像文件压缩包:

  • 不带 _usertests 后缀的 NimbOS 二进制镜像包中编译的 NimbOS 启动后会进入 NimbOS 的 shell,本示例启动的就是这个 NimbOS
  • usertests 后缀的 NimbOS 二进制镜像压缩包中编译的 NimbOS 启动后会自动运行用户态测例用于测试,这个镜像用于 AxVisor 的CI测试,见 setup-nimbos-guest-image/action.yml

从文件系统加载运行

  1. 制作一个磁盘镜像文件,并将客户机镜像放到文件系统中

    1. 使用 make disk_img 命令生成一个空的 FAT32 磁盘镜像文件 disk.img

    2. 手动挂载 disk.img,然后拉取并解压二进制镜像

      $ mkdir -p tmp
      $ sudo mount disk.img tmp
      $ wget https://github.com/arceos-hypervisor/nimbos/releases/download/v0.7/aarch64.zip
      $ unzip aarch64.zip # 得到 nimbos.bin
      $ sudo mv nimbos.bin tmp/nimbos-aarch64.bin
      $ sudo umount tmp
      
  2. 直接使用 configs/vms/nimbos-aarch64.toml 文件中的配置项

    • image_location="fs" 表示从文件系统加载
    • kernel_path 指出内核镜像在文件系统中的路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
  3. 执行 make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/nimbos-aarch64.toml FEATURES=page-alloc-64g APP_FEATURES=fs defconfig 创建 .axconfig.toml 配置文件

  4. 执行 make ACCEL=n ARCH=aarch64 LOG=info VM_CONFIGS=configs/vms/nimbos-aarch64.toml FEATURES=page-alloc-64g APP_FEATURES=fs run 构建 AxVisor,并在 QEMU 中启动。

    qemu-system-aarch64 -m 4G -smp 1 -cpu cortex-a72 -machine virt,virtualization=on,gic-version=2 -kernel /home/hky/workspace/arceos/axvisor/axvisor_aarch64-qemu-virt-hv.bin -device virtio-blk-pci,drive=disk0 -drive id=disk0,if=none,format=raw,file=disk.img -nographic -machine virtualization=on,gic-version=2
    
         d8888                            .d88888b.   .d8888b.
         d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
         d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
     d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
     d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
     d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
     arch = aarch64
     platform = aarch64-qemu-virt-hv
     target = aarch64-unknown-none-softfloat
     build_mode = release
     log_level = info
     smp = 1
    
     [  0.003970 0 axruntime:130] Logging is enabled.
     [  0.004676 0 axruntime:131] Primary CPU 0 started, dtb = 0x48000000.
     [  0.004981 0 axruntime:133] Found physcial memory regions:
     [  0.005312 0 axruntime:135]   [PA:0x40080000, PA:0x400f7000) .text (READ | EXECUTE | RESERVED)
     [  0.005665 0 axruntime:135]   [PA:0x400f7000, PA:0x4010d000) .rodata (READ | RESERVED)
     [  0.005900 0 axruntime:135]   [PA:0x4010d000, PA:0x40113000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
     [  0.006139 0 axruntime:135]   [PA:0x40113000, PA:0x40153000) boot stack (READ | WRITE | RESERVED)
     [  0.006326 0 axruntime:135]   [PA:0x40153000, PA:0x40379000) .bss (READ | WRITE | RESERVED)
     [  0.006551 0 axruntime:135]   [PA:0x40379000, PA:0xc0000000) free memory (READ | WRITE | FREE)
     [  0.006794 0 axruntime:135]   [PA:0x9000000, PA:0x9001000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.007020 0 axruntime:135]   [PA:0x9040000, PA:0x9041000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.007212 0 axruntime:135]   [PA:0x9100000, PA:0x9101000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.007407 0 axruntime:135]   [PA:0x8000000, PA:0x8020000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.007622 0 axruntime:135]   [PA:0xa000000, PA:0xa004000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.007839 0 axruntime:135]   [PA:0x10000000, PA:0x3eff0000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.008055 0 axruntime:135]   [PA:0x4010000000, PA:0x4020000000) mmio (READ | WRITE | DEVICE | RESERVED)
     [  0.008269 0 axruntime:210] Initialize global memory allocator...
     [  0.008467 0 axruntime:211]   use TLSF allocator.
     [  0.010533 0 axmm:72] Initialize virtual memory management...
     [  0.047038 0 axruntime:150] Initialize platform devices...
     [  0.047194 0 axhal::platform::aarch64_common::gic:67] Initialize GICv2...
     [  0.047925 0 axtask::api:73] Initialize scheduling...
     [  0.049134 0 axtask::api:79]   use FIFO scheduler.
     [  0.049330 0 axdriver:152] Initialize device drivers...
     [  0.049527 0 axdriver:153]   device model: static
     [  0.053846 0 virtio_drivers::device::blk:59] config: 0x1000e000
     [  0.054155 0 virtio_drivers::device::blk:64] found a block device of size 65536KB
     [  0.054761 0 axdriver::bus::pci:104] registered a new Block device at 00:02.0: "virtio-blk"
     [  0.086031 0 axfs:41] Initialize filesystems...
     [  0.086183 0 axfs:44]   use block device 0: "virtio-blk"
     [  0.097047 0 fatfs::dir:139] Is a directory
     [  0.106014 0 fatfs::dir:139] Is a directory
     [  0.116688 0 fatfs::dir:139] Is a directory
     [  0.127754 0 fatfs::dir:139] Is a directory
     [  0.132672 0 axruntime:176] Initialize interrupt handlers...
     [  0.133290 0 axruntime:188] Primary CPU 0 init OK.
     [  0.133509 0:2 axvisor:17] Starting virtualization...
     [  0.133794 0:2 axvisor:19] Hardware support: true
     [  0.134681 0:4 axvisor::vmm::timer:101] Initing HV Timer...
     [  0.134940 0:4 axvisor::hal:117] Hardware virtualization support enabled on core 0
     [  0.148721 0:2 axvisor::vmm::config:33] Creating VM [1] "nimbos"
     [  0.149366 0:2 axvm::vm:113] Setting up memory region: [0x40000000~0x41000000] READ | WRITE | EXECUTE
     [  0.153657 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x8000000~0x8050000] -> [0x8000000~0x8050000]
     [  0.154615 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9000000~0x9001000] -> [0x9000000~0x9001000]
     [  0.154961 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9010000~0x9011000] -> [0x9010000~0x9011000]
     [  0.155253 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9030000~0x9031000] -> [0x9030000~0x9031000]
     [  0.155531 0:2 axvm::vm:156] Setting up passthrough device memory region: [0xa000000~0xa004000] -> [0xa000000~0xa004000]
     [  0.155952 0:2 axvm::vm:191] VM created: id=1
     [  0.156231 0:2 axvm::vm:206] VM setup: id=1
     [  0.156471 0:2 axvisor::vmm::config:40] VM[1] created success, loading images...
     [  0.156741 0:2 axvisor::vmm::images::fs:102] Loading VM images from filesystem
     [  0.222442 0:2 axvisor::vmm:30] Setting up vcpus...
     [  0.222902 0:2 axvisor::vmm::vcpus:176] Initializing VM[1]'s 1 vcpus
     [  0.223240 0:2 axvisor::vmm::vcpus:207] Spawning task for VM[1] Vcpu[0]
     [  0.223695 0:2 axvisor::vmm::vcpus:219] Vcpu task Task(5, "VM[1]-VCpu[0]") created cpumask: [0, ]
     [  0.224085 0:2 axvisor::vmm:37] VMM starting, booting VMs...
     [  0.224301 0:2 axvm::vm:273] Booting VM[1]
     [  0.224513 0:2 axvisor::vmm:43] VM[1] boot success
     [  0.225051 0:5 axvisor::vmm::vcpus:240] VM[1] Vcpu[0] waiting for running
     [  0.225358 0:5 axvisor::vmm::vcpus:243] VM[1] Vcpu[0] running...
    
     NN   NN  iii               bb        OOOOO    SSSSS
     NNN  NN       mm mm mmmm   bb       OO   OO  SS
     NN N NN  iii  mmm  mm  mm  bbbbbb   OO   OO   SSSSS
     NN  NNN  iii  mmm  mm  mm  bb   bb  OO   OO       SS
     NN   NN  iii  mmm  mm  mm  bbbbbb    OOOO0    SSSSS
                 ___    ____    ___    ___
                 |__ \  / __ \  |__ \  |__ \
                 __/ / / / / /  __/ /  __/ /
                 / __/ / /_/ /  / __/  / __/
             /____/ \____/  /____/ /____/
    
     arch = aarch64
     platform = qemu-virt-arm
     build_mode = release
     log_level = warn
    
     Initializing kernel heap at: [0xffff0000401100e0, 0xffff0000405100e0)
     Initializing frame allocator at: [PA:0x40511000, PA:0x48000000)
     Mapping .text: [0xffff000040080000, 0xffff000040094000)
     Mapping .rodata: [0xffff000040094000, 0xffff00004009b000)
     Mapping .data: [0xffff00004009b000, 0xffff00004010a000)
     Mapping .bss: [0xffff00004010e000, 0xffff000040511000)
     Mapping boot stack: [0xffff00004010a000, 0xffff00004010e000)
     Mapping physical memory: [0xffff000040511000, 0xffff000048000000)
     Mapping MMIO: [0xffff000009000000, 0xffff000009001000)
     Mapping MMIO: [0xffff000008000000, 0xffff000008020000)
     Initializing drivers...
     Initializing task manager...
     /**** APPS ****
     cyclictest
     exit
     fantastic_text
     forktest
     forktest2
     forktest_simple
     forktest_simple_c
     forktree
     hello_c
     hello_world
     matrix
     poweroff
     sleep
     sleep_simple
     stack_overflow
     thread_simple
     user_shell
     usertests
     yield
     **************/
     Running tasks...
     test kernel task: pid = TaskId(2), arg = 0xdead
     test kernel task: pid = TaskId(3), arg = 0xbeef
     Rust user shell
     >> 
     >> 
    

从内存中加载运行

参考别的 guest VM 的运行指导,修改对应的 ./configs/vms/nimbos-aarch64.toml 中的配置项即可:

  • image_location 配置项修改为 image_location="memory"
  • 并设置 kernel_path 为 nimbos 二进制内核镜像在工作空间中的相对/绝对路径

Linux

首先,获取 Linux 主线代码 git clone git@github.com:arceos-hypervisor/linux-6.2.0.git,然后执行 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig 再执行 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) 以获取 Image

然后,执行 dtc -I dts -O dtb -o linux-qemu.dtb configs/vms/linux-qemu.dts 编译 Linux 客户机需要使用的设备树文件 linux-qemu.dtb

从文件系统加载运行

  1. 执行 make ubuntu_img ARCH=aarch64 制作一个简单的根文件系统镜像 disk.img 作为 Linux 客户机启动之后的文件系统,然后手动挂载 disk.img,然后将 Image 和 linux-qemu.dtb 复制到该文件系统中

    $ mkdir -p tmp
    $ sudo mount disk.img tmp
    $ sudo cp Image tmp/boot/
    $ sudo cp linux-qemu.dtb tmp/boot/
    $ sudo umount tmp
    
  2. 修改对应的 ./configs/vms/linux-qemu-aarch64.toml 文件中的配置项

    • image_location="fs" 表示从文件系统加载
    • kernel_path 指出内核镜像在文件系统中的路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
    • 其他
  3. 执行 make ARCH=aarch64 VM_CONFIGS=configs/vms/linux-qemu-aarch64.toml LOG=debug BUS=mmio NET=y FEATURES=page-alloc-64g,ext4fs APP_FEATURES=fs MEM=8g BLK=y run 构建 AxVisor,并在 QEMU 中启动。

从内存加载运行

  1. 执行 make ubuntu_img ARCH=aarch64 制作一个简单的根文件系统镜像 disk.img 作为 Linux 客户机启动之后的文件系统

  2. 修改对应的 ./configs/vms/linux-qemu-aarch64.toml 中的配置项

    • image_location="memory" 配置项
    • kernel_path 指定内核镜像在工作空间中的相对/绝对路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
    • 其他
  3. 执行 make ARCH=aarch64 VM_CONFIGS=configs/vms/linux-qemu-aarch64.toml LOG=debug BUS=mmio NET=y FEATURES=page-alloc-64g MEM=8g run 构建 AxVisor,并在 QEMU 中启动。

    
          d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
       d88P888                           888     888 Y88b.
       d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
    d88P   888 888     888      88888888 888     888       "888
    d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-qemu-virt-hv
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = debug
    smp = 1
    
    [  0.021692 0 axruntime:130] Logging is enabled.
    [  0.027480 0 axruntime:131] Primary CPU 0 started, dtb = 0x48000000.
    [  0.029740 0 axruntime:133] Found physcial memory regions:
    [  0.032052 0 axruntime:135]   [PA:0x40080000, PA:0x400d9000) .text (READ | EXECUTE | RESERVED)
    [  0.035160 0 axruntime:135]   [PA:0x400d9000, PA:0x42a86000) .rodata (READ | RESERVED)
    [  0.036925 0 axruntime:135]   [PA:0x42a86000, PA:0x42a8c000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  0.038841 0 axruntime:135]   [PA:0x42a8c000, PA:0x42acc000) boot stack (READ | WRITE | RESERVED)
    [  0.040473 0 axruntime:135]   [PA:0x42acc000, PA:0x42cf2000) .bss (READ | WRITE | RESERVED)
    [  0.042098 0 axruntime:135]   [PA:0x42cf2000, PA:0xc0000000) free memory (READ | WRITE | FREE)
    [  0.043965 0 axruntime:135]   [PA:0x9000000, PA:0x9001000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.045674 0 axruntime:135]   [PA:0x9040000, PA:0x9041000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.047300 0 axruntime:135]   [PA:0x9100000, PA:0x9101000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.048928 0 axruntime:135]   [PA:0x8000000, PA:0x8020000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.050556 0 axruntime:135]   [PA:0xa000000, PA:0xa004000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.052173 0 axruntime:135]   [PA:0x10000000, PA:0x3eff0000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.053811 0 axruntime:135]   [PA:0x4010000000, PA:0x4020000000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  0.055851 0 axruntime:208] Initialize global memory allocator...
    [  0.057323 0 axruntime:209]   use TLSF allocator.
    [  0.060322 0 axalloc:230] initialize global allocator at: [0x42cf2000, 0xc0000000)
    [  0.073349 0 axmm:60] Initialize virtual memory management...
    [  0.179788 0 axmm:63] kernel address space init OK: AddrSpace {
       va_range: VA:0x0..VA:0xfffffffff000,
       page_table_root: PA:0x42cfa000,
    }
    [  0.186432 0 axruntime:150] Initialize platform devices...
    [  0.187828 0 axhal::platform::aarch64_common::gic:67] Initialize GICv2...
    [  0.194027 0 axtask::api:73] Initialize scheduling...
    [  0.199817 0 axtask::task:115] new task: Task(1, "idle")
    [  0.204617 0 axtask::task:115] new task: Task(3, "gc")
    [  0.207868 0 axalloc:118] expand heap memory: [0x432fb000, 0x4333b000)
    [  0.210474 0 axalloc:118] expand heap memory: [0x4333b000, 0x433bb000)
    [  0.213193 0 axtask::api:79]   use FIFO scheduler.
    [  0.214543 0 axruntime:176] Initialize interrupt handlers...
    [  0.218724 0 axruntime:186] Primary CPU 0 init OK.
    [  0.220417 0:2 axvisor:17] Starting virtualization...
    [  0.222465 0:2 axvisor:19] Hardware support: true
    [  0.224491 0:2 axtask::task:115] new task: Task(4, "")
    [  0.226959 0:2 axalloc:118] expand heap memory: [0x433bb000, 0x434bb000)
    [  0.229776 0:2 axtask::run_queue:234] task add: Task(4, "") on run_queue 0
    [  0.235809 0:3 axtask::run_queue:418] task block: Task(3, "gc")
    [  0.237961 0:4 axvisor::vmm::timer:103] Initing HV Timer...
    [  0.239881 0:4 axvisor::hal:117] Hardware virtualization support enabled on core 0
    [  0.243229 0:4 axtask::run_queue:357] task exit: Task(4, ""), exit_code=0
    [  0.247022 0:4 axtask::run_queue:260] task unblock: Task(3, "gc") on run_queue 0
    [  0.249708 0:2 axvisor::hal:78] IRQ handler 26
    [  0.257112 0:2 axvisor::hal:78] IRQ handler 26
    [  0.267113 0:2 axvisor::hal:78] IRQ handler 26
    [  0.277201 0:2 axvisor::hal:78] IRQ handler 26
    [  0.287106 0:2 axvisor::hal:78] IRQ handler 26
    [  0.297082 0:2 axvisor::hal:78] IRQ handler 26
    [  0.307110 0:2 axvisor::hal:78] IRQ handler 26
    [  0.317084 0:2 axvisor::hal:78] IRQ handler 26
    [  0.327094 0:2 axvisor::hal:78] IRQ handler 26
    [  0.337102 0:2 axvisor::hal:78] IRQ handler 26
    [  0.347177 0:2 axvisor::hal:78] IRQ handler 26
    [  0.357112 0:2 axvisor::hal:78] IRQ handler 26
    [  0.367086 0:2 axvisor::hal:78] IRQ handler 26
    [  0.377094 0:2 axvisor::hal:78] IRQ handler 26
    [  0.386480 0:2 axvisor::vmm::config:33] Creating VM [1] "linux-qemu"
    [  0.387827 0:2 axvisor::hal:78] IRQ handler 26
    [  0.393430 0:2 axvm::vm:113] Setting up memory region: [0x80000000~0xc0000000] READ | WRITE | EXECUTE
    [  1.583826 0:2 axvisor::hal:78] IRQ handler 26
    [  1.586436 0:2 axaddrspace::address_space::backend::linear:22] map_linear: [GPA:0x80000000, GPA:0xc0000000) -> [PA:0x80000000, PA:0xc0000000) READ | WRITE | EXECUTE
    [  1.594936 0:2 axvisor::hal:78] IRQ handler 26
    [  1.604917 0:2 axvisor::hal:78] IRQ handler 26
    [  1.614900 0:2 axvisor::hal:78] IRQ handler 26
    [  1.624917 0:2 axvisor::hal:78] IRQ handler 26
    [  1.634917 0:2 axvisor::hal:78] IRQ handler 26
    [  1.644916 0:2 axvisor::hal:78] IRQ handler 26
    [  1.654915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.664915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.674915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.684915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.694903 0:2 axvisor::hal:78] IRQ handler 26
    [  1.704908 0:2 axvisor::hal:78] IRQ handler 26
    [  1.714920 0:2 axvisor::hal:78] IRQ handler 26
    [  1.724914 0:2 axvisor::hal:78] IRQ handler 26
    [  1.734915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.744914 0:2 axvisor::hal:78] IRQ handler 26
    [  1.754913 0:2 axvisor::hal:78] IRQ handler 26
    [  1.764913 0:2 axvisor::hal:78] IRQ handler 26
    [  1.774916 0:2 axvisor::hal:78] IRQ handler 26
    [  1.784914 0:2 axvisor::hal:78] IRQ handler 26
    [  1.794914 0:2 axvisor::hal:78] IRQ handler 26
    [  1.804913 0:2 axvisor::hal:78] IRQ handler 26
    [  1.814915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.824915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.834914 0:2 axvisor::hal:78] IRQ handler 26
    [  1.844915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.854913 0:2 axvisor::hal:78] IRQ handler 26
    [  1.864923 0:2 axvisor::hal:78] IRQ handler 26
    [  1.874918 0:2 axvisor::hal:78] IRQ handler 26
    [  1.884924 0:2 axvisor::hal:78] IRQ handler 26
    [  1.894922 0:2 axvisor::hal:78] IRQ handler 26
    [  1.904899 0:2 axvisor::hal:78] IRQ handler 26
    [  1.914915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.924915 0:2 axvisor::hal:78] IRQ handler 26
    [  1.934916 0:2 axvisor::hal:78] IRQ handler 26
    [  1.944916 0:2 axvisor::hal:78] IRQ handler 26
    [  1.954902 0:2 axvisor::hal:78] IRQ handler 26
    [  1.964918 0:2 axvisor::hal:78] IRQ handler 26
    [  1.974916 0:2 axvisor::hal:78] IRQ handler 26
    [  1.984900 0:2 axvisor::hal:78] IRQ handler 26
    [  1.994922 0:2 axvisor::hal:78] IRQ handler 26
    [  2.004915 0:2 axvisor::hal:78] IRQ handler 26
    [  2.014914 0:2 axvisor::hal:78] IRQ handler 26
    [  2.024915 0:2 axvisor::hal:78] IRQ handler 26
    [  2.034917 0:2 axvisor::hal:78] IRQ handler 26
    [  2.044913 0:2 axvisor::hal:78] IRQ handler 26
    [  2.054912 0:2 axvisor::hal:78] IRQ handler 26
    [  2.064917 0:2 axvisor::hal:78] IRQ handler 26
    [  2.074909 0:2 axvisor::hal:78] IRQ handler 26
    [  2.084897 0:2 axvisor::hal:78] IRQ handler 26
    [  2.094911 0:2 axvisor::hal:78] IRQ handler 26
    [  2.104912 0:2 axvisor::hal:78] IRQ handler 26
    [  2.114910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.124910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.134897 0:2 axvisor::hal:78] IRQ handler 26
    [  2.144913 0:2 axvisor::hal:78] IRQ handler 26
    [  2.154911 0:2 axvisor::hal:78] IRQ handler 26
    [  2.164912 0:2 axvisor::hal:78] IRQ handler 26
    [  2.174910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.184910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.194914 0:2 axvisor::hal:78] IRQ handler 26
    [  2.204916 0:2 axvisor::hal:78] IRQ handler 26
    [  2.214910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.224910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.234903 0:2 axvisor::hal:78] IRQ handler 26
    [  2.244914 0:2 axvisor::hal:78] IRQ handler 26
    [  2.254911 0:2 axvisor::hal:78] IRQ handler 26
    [  2.264896 0:2 axvisor::hal:78] IRQ handler 26
    [  2.274910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.284909 0:2 axvisor::hal:78] IRQ handler 26
    [  2.294913 0:2 axvisor::hal:78] IRQ handler 26
    [  2.297840 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x8000000~0x8050000] -> [0x8000000~0x8050000]
    [  2.300794 0:2 axaddrspace::address_space::backend::linear:22] map_linear: [GPA:0x8000000, GPA:0x8050000) -> [PA:0x8000000, PA:0x8050000) READ | WRITE | DEVICE
    [  2.304533 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9000000~0x9001000] -> [0x9000000~0x9001000]
    [  2.306624 0:2 axvisor::hal:78] IRQ handler 26
    [  2.308195 0:2 axaddrspace::address_space::backend::linear:22] map_linear: [GPA:0x9000000, GPA:0x9001000) -> [PA:0x9000000, PA:0x9001000) READ | WRITE | DEVICE
    [  2.310767 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9010000~0x9011000] -> [0x9010000~0x9011000]
    [  2.312735 0:2 axaddrspace::address_space::backend::linear:22] map_linear: [GPA:0x9010000, GPA:0x9011000) -> [PA:0x9010000, PA:0x9011000) READ | WRITE | DEVICE
    [  2.315260 0:2 axvisor::hal:78] IRQ handler 26
    [  2.316282 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x9030000~0x9031000] -> [0x9030000~0x9031000]
    [  2.318198 0:2 axaddrspace::address_space::backend::linear:22] map_linear: [GPA:0x9030000, GPA:0x9031000) -> [PA:0x9030000, PA:0x9031000) READ | WRITE | DEVICE
    [  2.320626 0:2 axvm::vm:156] Setting up passthrough device memory region: [0xa000000~0xa004000] -> [0xa000000~0xa004000]
    [  2.322538 0:2 axaddrspace::address_space::backend::linear:22] map_linear: [GPA:0xa000000, GPA:0xa004000) -> [PA:0xa000000, PA:0xa004000) READ | WRITE | DEVICE
    [  2.325021 0:2 axvisor::hal:78] IRQ handler 26
    [  2.327226 0:2 axvm::vm:191] VM created: id=1
    [  2.329017 0:2 arm_vcpu::vcpu:88] set vcpu entry:GPA:0x80080000
    [  2.330596 0:2 arm_vcpu::vcpu:94] set vcpu ept root:PA:0x434bb000
    [  2.332432 0:2 axvm::vm:206] VM setup: id=1
    [  2.334321 0:2 axvisor::vmm::config:40] VM[1] created success, loading images...
    [  2.335835 0:2 axvisor::hal:78] IRQ handler 26
    [  2.338162 0:2 axvisor::vmm::images:24] Loading VM[1] images from memory
    [  2.340095 0:2 axvisor::vmm::images:55] loading VM image from memory GPA:0x80080000 43637248
    [  2.343410 0:2 axaddrspace::address_space:203] start GPA:0x80080000 end GPA:0x82a1da00 area size 0x40000000
    [  2.345222 0:2 axvisor::hal:78] IRQ handler 26
    [  2.354929 0:2 axvisor::hal:78] IRQ handler 26
    [  2.364922 0:2 axvisor::hal:78] IRQ handler 26
    [  2.374933 0:2 axvisor::hal:78] IRQ handler 26
    [  2.384936 0:2 axvisor::hal:78] IRQ handler 26
    [  2.394937 0:2 axvisor::hal:78] IRQ handler 26
    [  2.404922 0:2 axvisor::hal:78] IRQ handler 26
    [  2.414931 0:2 axvisor::hal:78] IRQ handler 26
    [  2.424933 0:2 axvisor::hal:78] IRQ handler 26
    [  2.434932 0:2 axvisor::hal:78] IRQ handler 26
    [  2.444935 0:2 axvisor::hal:78] IRQ handler 26
    [  2.454932 0:2 axvisor::hal:78] IRQ handler 26
    [  2.464935 0:2 axvisor::hal:78] IRQ handler 26
    [  2.474919 0:2 axvisor::hal:78] IRQ handler 26
    [  2.484932 0:2 axvisor::hal:78] IRQ handler 26
    [  2.495088 0:2 axvisor::hal:78] IRQ handler 26
    [  2.504934 0:2 axvisor::hal:78] IRQ handler 26
    [  2.514933 0:2 axvisor::hal:78] IRQ handler 26
    [  2.524932 0:2 axvisor::hal:78] IRQ handler 26
    [  2.534915 0:2 axvisor::hal:78] IRQ handler 26
    [  2.541916 0:2 axvisor::vmm::images:81] copy size: 2560
    [  2.543664 0:2 axvisor::vmm::images:55] loading VM image from memory GPA:0x80000000 6252
    [  2.545260 0:2 axvisor::hal:78] IRQ handler 26
    [  2.546363 0:2 axaddrspace::address_space:203] start GPA:0x80000000 end GPA:0x8000186c area size 0x40000000
    [  2.548185 0:2 axvisor::vmm::images:81] copy size: 2156
    [  2.552657 0:2 axvisor::vmm:29] Setting up vcpus...
    [  2.554935 0:2 axvisor::hal:78] IRQ handler 26
    [  2.557954 0:2 axvisor::vmm::vcpus:176] Initializing VM[1]'s 1 vcpus
    [  2.560474 0:2 axvisor::vmm::vcpus:207] Spawning task for VM[1] Vcpu[0]
    [  2.563430 0:2 axtask::task:115] new task: Task(5, "VM[1]-VCpu[0]")
    [  2.564932 0:2 axvisor::hal:78] IRQ handler 26
    [  2.567172 0:2 axvisor::vmm::vcpus:219] Vcpu task Task(5, "VM[1]-VCpu[0]") created cpumask: [0, ]
    [  2.569574 0:2 axtask::run_queue:234] task add: Task(5, "VM[1]-VCpu[0]") on run_queue 0
    [  2.572270 0:2 axvisor::vmm:36] VMM starting, booting VMs...
    [  2.573899 0:2 axvm::vm:273] Booting VM[1]
    [  2.574910 0:2 axvisor::hal:78] IRQ handler 26
    [  2.576666 0:2 axvisor::vmm:42] VM[1] boot success
    [  2.579350 0:2 axtask::run_queue:418] task block: Task(2, "main")
    [  2.581788 0:3 axtask::task:433] task drop: Task(4, "")
    [  2.583866 0:3 axtask::run_queue:418] task block: Task(3, "gc")
    [  2.585237 0:5 axvisor::hal:78] IRQ handler 26
    [  2.587055 0:5 axvisor::vmm::vcpus:240] VM[1] Vcpu[0] waiting for running
    [  2.589337 0:5 axvisor::vmm::vcpus:243] VM[1] Vcpu[0] running...
    [    0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd083]
    [    0.000000] Linux version 6.9.0-rc5-00159-gc942a0cd3603 (zcs@server) (aarch64-linux-gnu-gcc (Linaro GCC 6.3-2017.05) 6.3.1 20170404, GNU ld (Linaro_Binutils-2017.05) 2.27.0.20161019) #1 SMP PREEMPT Thu Mar 27 08:38:03 UTC 2025
    [    0.000000] KASLR enabled
    [    0.000000] random: crng init done
    [    0.000000] Machine model: linux,dummy-virt
    [    0.000000] efi: UEFI not found.
    [    0.000000] [Firmware Bug]: Kernel image misaligned at boot, please fix your bootloader!
    [    0.000000] earlycon: pl11 at MMIO 0x0000000009000000 (options '')
    [    0.000000] printk: legacy bootconsole [pl11] enabled
    [    0.000000] NUMA: No NUMA configuration found
    [    0.000000] NUMA: Faking a node at [mem 0x0000000080000000-0x00000000bfffffff]
    [    0.000000] NUMA: NODE_DATA [mem 0xbfdfd9c0-0xbfdfffff]
    [    0.000000] Zone ranges:
    [    0.000000]   DMA      [mem 0x0000000080000000-0x00000000bfffffff]
    [    0.000000]   DMA32    empty
    [    0.000000]   Normal   empty
    [    0.000000] Movable zone start for each node
    [    0.000000] Early memory node ranges
    [    0.000000]   node   0: [mem 0x0000000080000000-0x00000000bfffffff]
    [    0.000000] Initmem setup node 0 [mem 0x0000000080000000-0x00000000bfffffff]
    [    0.000000] cma: Reserved 32 MiB at 0x00000000bcc00000 on node -1
    [    0.000000] psci: probing for conduit method from DT.
    [    0.000000] psci: PSCIv1.1 detected in firmware.
    [    0.000000] psci: Using standard PSCI v0.2 function IDs
    [    0.000000] psci: Trusted OS migration not required
    [    0.000000] psci: SMC Calling Convention v1.0
    [    0.000000] percpu: Embedded 24 pages/cpu s58728 r8192 d31384 u98304
    [    0.000000] Detected PIPT I-cache on CPU0
    [    0.000000] CPU features: detected: Spectre-v2
    [    0.000000] CPU features: detected: Spectre-v3a
    [    0.000000] CPU features: detected: Spectre-v4
    [    0.000000] CPU features: detected: Spectre-BHB
    [    0.000000] CPU features: kernel page table isolation forced ON by KASLR
    [    0.000000] CPU features: detected: Kernel page table isolation (KPTI)
    [    0.000000] CPU features: detected: ARM erratum 1742098
    [    0.000000] CPU features: detected: ARM errata 1165522, 1319367, or 1530923
    [    0.000000] alternatives: applying boot alternatives
    [    0.000000] Kernel command line: earlycon console=ttyAMA0 root=/dev/vda rw audit=0 default_hugepagesz=32M hugepagesz=32M hugepages=4
    [    0.000000] audit: disabled (until reboot)
    [    0.000000] Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes, linear)
    [    0.000000] Inode-cache hash table entries: 65536 (order: 7, 524288 bytes, linear)
    [    0.000000] Fallback order for Node 0: 0 
    [    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 258048
    [    0.000000] Policy zone: DMA
    [    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
    [    0.000000] software IO TLB: SWIOTLB bounce buffer size adjusted to 1MB
    [    0.000000] software IO TLB: area num 1.
    [    0.000000] software IO TLB: mapped [mem 0x00000000bca00000-0x00000000bcb00000] (1MB)
    [    0.000000] Memory: 819184K/1048576K available (16320K kernel code, 4790K rwdata, 11436K rodata, 9920K init, 754K bss, 196624K reserved, 32768K cma-reserved)
    [    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
    [    0.000000] rcu: Preemptible hierarchical RCU implementation.
    [    0.000000] rcu:     RCU event tracing is enabled.
    [    0.000000] rcu:     RCU restricting CPUs from NR_CPUS=512 to nr_cpu_ids=1.
    [    0.000000]  Trampoline variant of Tasks RCU enabled.
    [    0.000000]  Tracing variant of Tasks RCU enabled.
    [    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
    [    0.000000] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1
    [    0.000000] RCU Tasks: Setting shift to 0 and lim to 1 rcu_task_cb_adjust=1.
    [    0.000000] RCU Tasks Trace: Setting shift to 0 and lim to 1 rcu_task_cb_adjust=1.
    [    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
    [    0.000000] Root IRQ handler: gic_handle_irq
    [    0.000000] GICv2m: range[mem 0x08020000-0x08020fff], SPI[80:143]
    [    0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention.
    [    0.000000] arch_timer: cp15 timer(s) running at 62.50MHz (virt).
    [    0.000000] clocksource: arch_sys_counter: mask: 0x1ffffffffffffff max_cycles: 0x1cd42e208c, max_idle_ns: 881590405314 ns
    [    0.000582] sched_clock: 57 bits at 63MHz, resolution 16ns, wraps every 4398046511096ns
    [    0.067811] Console: colour dummy device 80x25
    [    0.088580] Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=250000)
    [    0.091632] pid_max: default: 32768 minimum: 301
    [    0.105076] LSM: initializing lsm=capability
    [    0.124408] Mount-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
    [    0.126743] Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
    [    0.313628] spectre-v4 mitigation disabled by command-line option
    [    0.345800] cacheinfo: Unable to detect cache hierarchy for CPU 0
    [    0.451040] rcu: Hierarchical SRCU implementation.
    [    0.452834] rcu:     Max phase no-delay instances is 1000.
    [    0.536606] EFI services will not be available.
    [    0.542738] smp: Bringing up secondary CPUs ...
    [    0.550670] smp: Brought up 1 node, 1 CPU
    [    0.551832] SMP: Total of 1 processors activated.
    [    0.553025] CPU: All CPU(s) started at EL1
    [    0.557774] CPU features: detected: 32-bit EL0 Support
    [    0.560104] CPU features: detected: 32-bit EL1 Support
    [    0.561614] CPU features: detected: CRC32 instructions
    [    0.624983] alternatives: applying system-wide alternatives
    [    1.013809] devtmpfs: initialized
    [    1.283345] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns
    [    1.302370] futex hash table entries: 256 (order: 2, 16384 bytes, linear)
    [    1.359878] pinctrl core: initialized pinctrl subsystem
    [    1.466896] DMI not present or invalid.
    [    1.634290] NET: Registered PF_NETLINK/PF_ROUTE protocol family
    [    1.746001] DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations
    [    1.753769] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations
    [    1.765784] DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
    [    1.835254] thermal_sys: Registered thermal governor 'step_wise'
    [    1.835916] thermal_sys: Registered thermal governor 'power_allocator'
    [    1.841594] cpuidle: using governor menu
    [    1.868129] hw-breakpoint: found 6 breakpoint and 4 watchpoint registers.
    [    1.873418] ASID allocator initialised with 32768 entries
    [    1.990036] Serial: AMBA PL011 UART driver
    [    2.870540] 9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 14, base_baud = 0) is a PL011 rev1
    [    2.884892] printk: legacy console [ttyAMA0] enabled
    [    2.884892] printk: legacy console [ttyAMA0] enabled
    [    2.889249] printk: legacy bootconsole [pl11] disabled
    [    2.889249] printk: legacy bootconsole [pl11] disabled
    [    3.023638] Modules: 2G module region forced by RANDOMIZE_MODULE_REGION_FULL
    [    3.025756] Modules: 0 pages in range for non-PLT usage
    [    3.026019] Modules: 513424 pages in range for PLT usage
    [    3.126024] HugeTLB: registered 32.0 MiB page size, pre-allocated 4 pages
    [    3.128534] HugeTLB: 0 KiB vmemmap can be freed for a 32.0 MiB page
    [    3.130469] HugeTLB: registered 1.00 GiB page size, pre-allocated 0 pages
    [    3.131849] HugeTLB: 0 KiB vmemmap can be freed for a 1.00 GiB page
    [    3.133547] HugeTLB: registered 2.00 MiB page size, pre-allocated 0 pages
    [    3.134940] HugeTLB: 0 KiB vmemmap can be freed for a 2.00 MiB page
    [    3.136327] HugeTLB: registered 64.0 KiB page size, pre-allocated 0 pages
    [    3.138049] HugeTLB: 0 KiB vmemmap can be freed for a 64.0 KiB page
    [    3.173964] Demotion targets for Node 0: null
    [    3.249986] ACPI: Interpreter disabled.
    [    3.379952] iommu: Default domain type: Translated
    [    3.382071] iommu: DMA domain TLB invalidation policy: strict mode
    [    3.398457] SCSI subsystem initialized
    [    3.424136] usbcore: registered new interface driver usbfs
    [    3.427239] usbcore: registered new interface driver hub
    [    3.430188] usbcore: registered new device driver usb
    [    3.480030] pps_core: LinuxPPS API ver. 1 registered
    [    3.481191] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti <giometti@linux.it>
    [    3.483999] PTP clock support registered
    [    3.491134] EDAC MC: Ver: 3.0.0
    [    3.517522] scmi_core: SCMI protocol bus registered
    [    3.567538] FPGA manager framework
    [    3.575630] Advanced Linux Sound Architecture Driver Initialized.
    [    3.698975] vgaarb: loaded
    [    3.740264] clocksource: Switched to clocksource arch_sys_counter
    [    3.769981] VFS: Disk quotas dquot_6.6.0
    [    3.772637] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes)
    [    3.787827] pnp: PnP ACPI: disabled
    [    4.076486] NET: Registered PF_INET protocol family
    [    4.090068] IP idents hash table entries: 16384 (order: 5, 131072 bytes, linear)
    [    4.138853] tcp_listen_portaddr_hash hash table entries: 512 (order: 1, 8192 bytes, linear)
    [    4.141016] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear)
    [    4.143752] TCP established hash table entries: 8192 (order: 4, 65536 bytes, linear)
    [    4.146811] TCP bind hash table entries: 8192 (order: 6, 262144 bytes, linear)
    [    4.149988] TCP: Hash tables configured (established 8192 bind 8192)
    [    4.161535] UDP hash table entries: 512 (order: 2, 16384 bytes, linear)
    [    4.164626] UDP-Lite hash table entries: 512 (order: 2, 16384 bytes, linear)
    [    4.174972] NET: Registered PF_UNIX/PF_LOCAL protocol family
    [    4.199967] RPC: Registered named UNIX socket transport module.
    [    4.201931] RPC: Registered udp transport module.
    [    4.203097] RPC: Registered tcp transport module.
    [    4.204199] RPC: Registered tcp-with-tls transport module.
    [    4.205786] RPC: Registered tcp NFSv4.1 backchannel transport module.
    [    4.207904] PCI: CLS 0 bytes, default 64
    [    4.246417] kvm [1]: HYP mode not available
    [    4.310161] Initialise system trusted keyrings
    [    4.323677] workingset: timestamp_bits=42 max_order=18 bucket_order=0
    [    4.346518] squashfs: version 4.0 (2009/01/31) Phillip Lougher
    [    4.361970] NFS: Registering the id_resolver key type
    [    4.366154] Key type id_resolver registered
    [    4.367310] Key type id_legacy registered
    [    4.370932] nfs4filelayout_init: NFSv4 File Layout Driver Registering...
    [    4.372907] nfs4flexfilelayout_init: NFSv4 Flexfile Layout Driver Registering...
    [    4.380570] 9p: Installing v9fs 9p2000 file system support
    [    4.996390] Key type asymmetric registered
    [    4.998188] Asymmetric key parser 'x509' registered
    [    5.001702] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 245)
    [    5.004042] io scheduler mq-deadline registered
    [    5.005987] io scheduler kyber registered
    [    5.009909] io scheduler bfq registered
    [    5.533535] pl061_gpio 9030000.pl061: PL061 GPIO chip registered
    [    7.293859] Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled
    [    7.463278] msm_serial: driver initialized
    [    7.483055] SuperH (H)SCI(F) driver initialized
    [    7.494097] STM32 USART driver initialized
    [    7.837823] loop: module loaded
    [    7.845491] virtio_blk virtio1: 1/0/0 default/read/poll queues
    [    7.881698] virtio_blk virtio1: [vda] 409600 512-byte logical blocks (210 MB/200 MiB)
    [    8.067674] megasas: 07.727.03.00-rc1
    [    8.417069] tun: Universal TUN/TAP device driver, 1.6
    [    8.530353] thunder_xcv, ver 1.0
    [    8.532526] thunder_bgx, ver 1.0
    [    8.534871] nicpf, ver 1.0
    [    8.599326] hns3: Hisilicon Ethernet Network Driver for Hip08 Family - version
    [    8.600813] hns3: Copyright (c) 2017 Huawei Corporation.
    [    8.604631] hclge is initializing
    [    8.607113] e1000: Intel(R) PRO/1000 Network Driver
    [    8.608211] e1000: Copyright (c) 1999-2006 Intel Corporation.
    [    8.611096] e1000e: Intel(R) PRO/1000 Network Driver
    [    8.612176] e1000e: Copyright(c) 1999 - 2015 Intel Corporation.
    [    8.614814] igb: Intel(R) Gigabit Ethernet Network Driver
    [    8.616017] igb: Copyright (c) 2007-2014 Intel Corporation.
    [    8.618576] igbvf: Intel(R) Gigabit Virtual Function Network Driver
    [    8.619876] igbvf: Copyright (c) 2009 - 2012 Intel Corporation.
    [    8.637868] sky2: driver version 1.30
    [    8.706118] VFIO - User Level meta-driver version: 0.3
    [    8.871414] usbcore: registered new interface driver usb-storage
    [    9.042230] rtc-pl031 9010000.pl031: registered as rtc0
    [    9.048077] rtc-pl031 9010000.pl031: setting system clock to 2025-03-28T00:22:57 UTC (1743121377)
    [    9.089588] i2c_dev: i2c /dev entries driver
    [    9.482386] sdhci: Secure Digital Host Controller Interface driver
    [    9.483722] sdhci: Copyright(c) Pierre Ossman
    [    9.527371] Synopsys Designware Multimedia Card Interface Driver
    [    9.581108] sdhci-pltfm: SDHCI platform and OF driver helper
    [    9.693508] ledtrig-cpu: registered to indicate activity on CPUs
    [    9.786376] usbcore: registered new interface driver usbhid
    [    9.787707] usbhid: USB HID core driver
    [    9.987141] hw perfevents: enabled with armv8_pmuv3 PMU driver, 7 counters available
    [   10.250455] NET: Registered PF_PACKET protocol family
    [   10.263124] 9pnet: Installing 9P2000 support
    [   10.266705] Key type dns_resolver registered
    [   10.912946] registered taskstats version 1
    [   10.946074] Loading compiled-in X.509 certificates
    [   11.433115] input: gpio-keys as /devices/platform/gpio-keys/input/input0
    [   11.482267] clk: Disabling unused clocks
    [   11.485549] PM: genpd: Disabling unused power domains
    [   11.488619] ALSA device list:
    [   11.490399]   No soundcards found.
    [   11.791974] EXT4-fs (vda): mounted filesystem a30d5de0-4011-48c0-9e48-a4556726e425 r/w with ordered data mode. Quota mode: none.
    [   11.797909] VFS: Mounted root (ext4 filesystem) on device 254:0.
    [   11.815481] devtmpfs: mounted
    [   12.252692] Freeing unused kernel memory: 9920K
    [   12.262413] Run /sbin/init as init process
    [   12.331806] Run /etc/init as init process
    [   12.336529] Run /bin/init as init process
    [   12.355530] Run /bin/sh as init process
    /bin/sh: 0: can't access tty; job control turned off
    # 
    

ArceOS + Linux

首先,根据以上两个章节的介绍分别制作 helloworld_aarch64-qemu-virt.binImagelinux-qemu.dtb 镜像文件

从文件系统加载运行

  1. 执行 make ubuntu_img ARCH=aarch64 制作一个简单的根文件系统镜像 disk.img 作为 Linux 客户机启动之后的文件系统,然后手动挂载 disk.img,然后将 Image 和 linux-qemu.dtb 复制到该文件系统中

    $ mkdir -p tmp
    $ sudo mount disk.img tmp
    $ sudo cp Image tmp/boot/
    $ sudo cp linux-qemu.dtb tmp/boot/
    $ sudo cp helloworld_aarch64-qemu-virt.bin tmp/boot/
    $ sudo umount tmp
    
  2. 修改对应的 ./configs/vms/linux-qemu-aarch64.tomlarceos-aarch64.toml 中的配置项

    • image_location="fs" 表示从文件系统加载
    • kernel_path 指出内核镜像在文件系统中的路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
    • 其他
  3. 执行 make ARCH=aarch64 VM_CONFIGS=configs/vms/arceos-aarch64.toml:configs/vms/linux-qemu-aarch64-vm2.toml LOG=info BUS=mmio NET=y FEATURES=page-alloc-64g MEM=8g SECOND_SERIAL=y SMP=2 run 构建 AxVisor,并在 QEMU 中启动。此时,终端将阻等待 Telnet 命令

    qemu-system-aarch64 -m 8g -smp 2 -cpu cortex-a72 -machine virt,virtualization=on,gic-version=2 -kernel /home/zcs/WORKSPACE/arceos_hypervisor/axvisor/axvisor_aarch64-qemu-virt-hv.bin -device virtio-blk-device,drive=disk0 -drive id=disk0,if=none,format=raw,file=disk.img -device virtio-net-device,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 -nographic -machine virtualization=on,gic-version=2 -serial mon:stdio -serial telnet:localhost:4321,server
    qemu-system-aarch64: -serial telnet:localhost:4321,server: info: QEMU waiting for connection on: disconnected:telnet:127.0.0.1:4321,server=on
    
  4. 启动另一个终端,然后执行 telnet localhost 4321,上一个终端将继续运行,并输出第一个虚拟机启动信息,第二个虚拟机启动信息将在当前终端输出。

从内存加载运行

  1. 执行 make ubuntu_img ARCH=aarch64 制作一个简单的根文件系统镜像 disk.img 作为 Linux 客户机启动之后的文件系统

  2. 修改对应的 ./configs/vms/linux-qemu-aarch64.tomlarceos-aarch64.toml 中的配置项

    • image_location="memory" 配置项
    • kernel_path 指定内核镜像在工作空间中的相对/绝对路径
    • entry_point 指出内核镜像的入口地址
    • kernel_load_addr 指出内核镜像的加载地址
    • 其他
  3. 执行 make ARCH=aarch64 VM_CONFIGS=configs/vms/arceos-aarch64.toml:configs/vms/linux-qemu-aarch64-vm2.toml LOG=info BUS=mmio NET=y FEATURES=page-alloc-64g MEM=8g SECOND_SERIAL=y SMP=2 run 构建 AxVisor,并在 QEMU 中启动。此时,终端将阻等待 Telnet 命令

    qemu-system-aarch64 -m 8g -smp 2 -cpu cortex-a72 -machine virt,virtualization=on,gic-version=2 -kernel /home/zcs/WORKSPACE/arceos_hypervisor/axvisor/axvisor_aarch64-qemu-virt-hv.bin -device virtio-blk-device,drive=disk0 -drive id=disk0,if=none,format=raw,file=disk.img -device virtio-net-device,netdev=net0 -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 -nographic -machine virtualization=on,gic-version=2 -serial mon:stdio -serial telnet:localhost:4321,server
    qemu-system-aarch64: -serial telnet:localhost:4321,server: info: QEMU waiting for connection on: disconnected:telnet:127.0.0.1:4321,server=on
    
  4. 启动另一个终端,然后执行 telnet localhost 4321,上一个终端将继续运行,并输出第一个虚拟机启动信息,第二个虚拟机启动信息将在当前终端输出。

注意事项

在同时启动 ArceOS 和 Linux 客户机时,若启动 axvisor 后只有一个 vm 有输出,另一个无显示,可能是 qemu 配置选项不全,需查看当前 qemu 版本,建议使用 9.2.2 版本,安装过程如下

# 安装编译所需的依赖包
sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \
              gawk build-essential bison flex texinfo gperf libtool patchutils bc \
              zlib1g-dev libexpat-dev pkg-config  libglib2.0-dev libpixman-1-dev libsdl2-dev \
              git tmux python3 python3-pip ninja-build
# 下载源码
wget https://download.qemu.org/qemu-9.2.2.tar.xz
# 解压
tar xvJf qemu-9.2.2.tar.xz
cd qemu-9.2.2
#生成设置文件
./configure --enable-kvm --enable-slirp --enable-debug --target-list=aarch64-softmmu,x86_64-softmmu
#编译
make -j$(nproc)

之后编辑 ~/.bashrc 文件,在文件的末尾加入几行:

export PATH=/path/to/qemu-9.2.2/build:$PATH

最后,执行 source ~/.bashrc 更新当前 shell 即可

已知问题

  1. ArceOS 从内存中加载启动时,如果没有 disk.img 将报错
  2. Linux 无法从文件系统加载启动
  3. 多客户机,例如 ArceOS + Linux,如果其中一个虚拟机结束,将无法正常启动第二个虚拟机

Copyright © 2025 • Created by ArceOS Team

Run AxVisor on QEMU x86_64

目前,在 qemu-system-x86_64 平台上已经对独立运行 ArceOS, Nimbos 以及 Starry 进行验证。

axvm-bios

axvm-bios 是针对 x86_64 用户虚拟机的一个极简BIOS实现。

它可以承担引导 NimbOS 以及 ArceOS 启动的任务,二进制文件链接.

Guest VM configuration

需要修改 guest VM 的配置文件 xxx-x86_64.toml 中的 bios_path 以及 bios_load_addr 域,可参考 arceos-x86_64.toml

#
# Vm kernel configs
#
[kernel]
# ...
# The file path of the BIOS image.
bios_path = "axvm-bios.bin"
# The load address of the BIOS image.
bios_load_addr = 0x8000

QEMU arguments

目前 qemu-system-x86_64 平台提供的 virt CPU 实现不支持 VMX feature,需要在启动时开启 -enable-kvm 参数(Makefile 定义的环境变量为 ACCEL=y)。

参考运行命令:

make ACCEL=y ARCH=x86_64 LOG=info VM_CONFIGS=configs/vms/nimbos-x86_64.toml APP_FEATURES=fs run

Copyright © 2025 • Created by ArceOS Team

Run AxVisor on QEMU riscv64

参考 QEMU-aarch64 的内容。

Copyright © 2025 • Created by ArceOS Team

黑芝麻 A1000

目前,在黑芝麻 A1000 平台上已经对同时运行 ArceOS + Linux 的情况进行了验证。

ArceOS

TODO

Linux

TODO

ArceOS + Linux

  1. 镜像准备

    1. 编译一个适合黑芝麻a1000平台的linux镜像,以及对应的dtb文件
    2. 编译一个只使用串口设备的arceos hello_world
      • 在qemu使用的配置信息上进行修改即可
      • 将串口更改为使用uart8250,将相应的串口地址进行改动
      • 将起始地址和内存信息配置为guest arceos toml中使用的对应内存
      • 配合guest arceos toml中使用的内存,在mem.rs文件中增加对应的一级页表信息,使arceos能够使用相应的内存
  2. 修改目前的build.rsimage.rsconfig.rs文件,使其支持两个os从内存中加载(目前实现方式比较暴力)

  3. 配置arceos-umhv/arceos-vmm/configs/platforms/aarch64-bsta1000b-virt-hv.toml文件

    # Architecture identifier.
    arch = "aarch64"                    # str    //host 架构
    # Platform identifier.
    platform = "aarch64-bsta1000b-hv"   # str    //host 平台
    
    #
    # Platform configs
    #
    [plat]
    # Platform family.
    family = "aarch64-bsta1000b"        # str
    
    # Base address of the whole physical memory.
    phys-memory-base = 0x1_9800_0000   //host物理内存起始地址
    # Size of the whole physical memory.
    phys-memory-size = 0x1800_0000    //host自身占用的物理内存大小
    # Base physical address of the kernel image.
    kernel-base-paddr = 0x1_a000_0000  //host内核起始物理地址
    # Base virtual address of the kernel image.
    kernel-base-vaddr = "0x0000_0001_a000_0000"   //host内核起始虚拟地址
    # Linear mapping offset, for quick conversions between physical and virtual
    # addresses.
    phys-virt-offset = "0x0000_0000_0000_0000"
    # Offset of bus address and phys address. some boards, the bus address is
    # different from the physical address.
    phys-bus-offset = 0
    # Kernel address space base.
    kernel-aspace-base = "0x0000_0000_0000_0000"
    # Kernel address space size.
    kernel-aspace-size = "0x0000_ffff_ffff_f000"
    
    #
    # Device specifications
    #
    [devices]
    # MMIO regions with format (`base_paddr`, `size`).
    //host 使用的物理设备
    mmio-regions = [
        [0x20008000, 0x1000], # uart8250 UART0
        [0x32000000, 0x8000], # arm,gic-400
        [0x32011000, 0x1000], # CPU CSR
        [0x33002000, 0x1000], # Top CRM
        [0x70035000, 0x1000], # CRM reg
        [0x70038000, 0x1000], # aon pinmux
    ] # [(uint, uint)]
    # VirtIO MMIO regions with format (`base_paddr`, `size`).
    virtio-mmio-regions = []    # [(uint, uint)]
    
    # Base physical address of the PCIe ECAM space.
    pci-ecam-base = 0x30E0_2000
    
    # UART Address
    uart-paddr = 0x2000_8000        # uint
    # UART IRQ number
    uart-irq = 0xd5                 # uint
    
    # GIC CPU Interface base address
    gicc-paddr = 0x3200_2000        # uint
    # GIC Distributor base address
    gicd-paddr = 0x3200_1000        # uint
    
    # BST A1000B board registers
    cpu-csr-base = 0x3201_1000          # uint
    a1000base-topcrm = 0x3300_2000      # uint
    a1000base-safetycrm = 0x7003_5000   # uint
    a1000base-aoncfg = 0x7003_8000      # uint
    
    # PSCI
    psci-method = "smc"     # str
    
    # RTC (PL031) Address (Need to read from DTB).
    rtc-paddr = 0x0         # uint
    
    
  4. 配置两个guest的相关信息

    1. 配置arceos-umhv/arceos-vmm/configs/vms/linux-a1000-aarch64-smp1.toml文件,配置guest linux的初始信息

      id = 0                 //guest vm id
      name = "linux-a1000"   //gueest vm name
      vm_type = 1     
      cpu_num = 1           //分配的vcpu数量
      phys_cpu_ids = [0x00]   
      phys_cpu_sets = [0x02]   // vcpu绑定在2核
      entry_point = 0x8100_0000  //linux内核起始地址
      kernel_load_addr = 0x8100_0000  //guest linux vm 镜像加载地址
      dtb_load_addr = 0x82e0_0000     //guest linux dtb 加载地址
      
      image_location = "memory"      //加载方式为从内存加载
      kernel_path = "/mnt/cicv/xh/arceos-hypervisor/a1000_port/image/Image_6.1.54.bin" //guest linux 镜像 所在路径
      dtb_path = "/mnt/cicv/xh/arceos-hypervisor/a1000_port/dts_dtb/bsta1000b-fada-chery-smp1.dtb" //guest linux dtb 所在路径
      # ramdisk_path = ""
      # ramdisk_load_addr = 0
      # disk_path = "disk.img"
      # Memory regions with format (`base_paddr`, `size`, `flags`).
      //guest linux 所使用的物理内存
      memory_regions = [
          [0x8000_0000, 0x7000_0000, 0x7, 1],#ram 1792MB
      ]
      
      
      # Emu_devices
      # Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig
      emu_devices = [
      ]
      
      # Pass-through devices
      //guest linux 所控制的直通设备
      passthrough_devices = [
      	["most-devices", 0x0000_0000, 0x0000_0000, 0x8000_0000, 0x1],
      ]
      
      
    2. 配置arceos-umhv/arceos-vmm/configs/vms/arceos-aarch64-hv.toml文件,配置guest arceos 初始信息

      id = 1          //guest vm id
      name = "arceos"    //guest vm name
      vm_type = 1          // guest vm type
      cpu_num = 1          // guest vcpu num
      phys_cpu_sets = [1]  //将guest vcpu与1核进行绑定
      # entry_point = 0x1_b008_0000
      entry_point = 0x1_b000_0000  //guest arceos 内核启动地址
      
      # The location of image: "memory" | "fs"
      # image_location = "fs" 
      image_location = "memory"    //guest arceos 加载方式为从内存中加载
      # kernel_path = "/mnt/cicv/xh/arceos/examples/helloworld/helloworld_aarch64-bsta1000b.bin"
      kernel_path = "/mnt/cicv/xh/arceos/examples/helloworld/helloworld_aarch64-qemu-virt.bin"   //guest arceos kernel 镜像地址
      # /mnt/cicv/xh/arceos/examples/helloworld/helloworld_aarch64-qemu-virt.bin
      kernel_load_addr = 0x1_b000_0000 //guest arceos 内核加载地址
      
      # bios_path = ""
      # bios_load_addr = 0
      # ramdisk_path = ""
      # ramdisk_load_addr = 0
      # disk_path = ""
      # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`).
      # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`.
      //guest arceos 使用的内存信息
      memory_regions = [
          # [0x8000_0000, 0x0800_0000, 0x7, 1],   # Low RAM		    16M 0b00111 R|W|EXECUTE
          [0x1_b000_0000, 0x0800_0000, 0x7, 1]
      ]
      
      # Emu_devices
      # Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig
      emu_devices = []
      
      # Pass-through devices
      # Name Base-Ipa Base-Pa Length Alloc-Irq
      passthrough_devices = [
      
      
          ["uart8250", 0x20008000, 0x20008000, 0x1000, 0x01],   # uart8250 UART0
      
      ]
      
  5. 使用make A=(pwd) ARCH=aarch64 VM_CONFIGS=configs/vms/linux-a1000-aarch64-smp1.toml:configs/vms/arceos-aarch64.toml PLAT_NAME=aarch64-bsta1000b-virt-hv FEATURES=page-alloc-64g,hv LOG=info SMP=2 fada 编译出镜像

  6. 将镜像替换原有的内核,断电重启

    baudrate: 115200
    
    Load ATF and UBOOT from Zone A
    
    NOTICE:  BL31: Built : 10:13:03, Mar 30 2023
    
    
    U-Boot 2019.04+2.1.1+g8fc26249.202303300858+ (Mar 30 2023 - 08:58:21 +0800)Bst A1000B, Build: jenkins-a1000_uboot_hvte_rootfs_all-4984
    
    Press 'ctrl+C/c' to stop autoboot:  0
    7020 bytes read in 5 ms (1.3 MiB/s)
    normal mode
    8204856 bytes read in 186 ms (42.1 MiB/s)
    62598 bytes read in 12 ms (5 MiB/s)
    ## Loading kernel from FIT Image at 90000000 ...
       Trying 'kernel' kernel subimage
         Description:  ArceOS for BST A1000B
         Type:         Kernel Image
         Compression:  gzip compressed
         Data Start:   0x900000fc
         Data Size:    8143392 Bytes = 7.8 MiB
         Architecture: AArch64
         OS:           Linux
         Load Address: 0x1a0000000
         Entry Point:  0x1a0000000
         Hash algo:    md5
         Hash value:   de3de880d16c71162738fe3a09493347
         Hash algo:    sha1
         Hash value:   b1201ff1b8418e5e2f1f27cf2f3daf0275f9a605
       Verifying Hash Integrity ... md5+ sha1+ OK
    ## Flattened Device Tree blob at 80000000
       Booting using the fdt blob at 0x80000000
       Uncompressing Kernel Image ... load_buf:00000001a0000000,  image_buf:00000000900000fc
    image_len:7c4220 comp:1
    OK
       Loading Device Tree to 00000001ce7ed000, end 00000001ce7ff485 ... OK
    enable hyp val 30
    
    Starting kernel ...
    
    
           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-bsta1000b-virt-hv
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = info
    smp = 2
    
    [  3.041875 axruntime:130] Logging is enabled.
    [  3.047985 axruntime:131] Primary CPU 0 started, dtb = 0x1ce7ed000.
    [  3.056196 axruntime:133] Found physcial memory regions:
    [  3.063360 axruntime:135]   [PA:0x1a0000000, PA:0x1a0062000) .text (READ | EXECUTE | RESERVED)
    [  3.074146 axruntime:135]   [PA:0x1a0062000, PA:0x1a1901000) .rodata (READ | RESERVED)
    [  3.084172 axruntime:135]   [PA:0x1a1901000, PA:0x1a1909000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  3.096775 axruntime:135]   [PA:0x1a1909000, PA:0x1a1989000) boot stack (READ | WRITE | RESERVED)
    [  3.107851 axruntime:135]   [PA:0x1a1989000, PA:0x1a1baf000) .bss (READ | WRITE | RESERVED)
    [  3.118354 axruntime:135]   [PA:0x1a1baf000, PA:0x1b0000000) free memory (READ | WRITE | FREE)
    [  3.129143 axruntime:135]   [PA:0x80000000, PA:0xf0000000) reserved memory (READ | WRITE | EXECUTE | RESERVED)
    [  3.141460 axruntime:135]   [PA:0x1b0000000, PA:0x1f0000000) reserved memory (READ | WRITE | EXECUTE | RESERVED)
    [  3.153968 axruntime:135]   [PA:0x20008000, PA:0x20009000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.165139 axruntime:135]   [PA:0x32000000, PA:0x32008000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.176310 axruntime:135]   [PA:0x32011000, PA:0x32012000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.187481 axruntime:135]   [PA:0x33002000, PA:0x33003000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.198652 axruntime:135]   [PA:0x70035000, PA:0x70036000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.209824 axruntime:135]   [PA:0x70038000, PA:0x70039000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.220994 axruntime:208] Initialize global memory allocator...
    [  3.228824 axruntime:209]   use TLSF allocator.
    [  3.235195 axmm:60] Initialize virtual memory management...
    [  3.263999 axruntime:150] Initialize platform devices...
    [  3.271067 axhal::platform::aarch64_common::gic:67] Initialize GICv2...
    [  3.279717 axtask::api:73] Initialize scheduling...
    [  3.286374 axtask::api:79]   use FIFO scheduler.
    [  3.292742 axhal::platform::aarch64_common::psci:115] Starting CPU 100 ON ...
    [  3.301975 axruntime:176] Initialize interrupt handlers...
    [  3.301975 axruntime::mp:37] Secondary CPU 1 started.
    [  3.309263 axruntime:186] Primary CPU 0 init OK.
    [  3.316168 axruntime::mp:47] Secondary CPU 1 init OK.
    [  3.329406 0:2 arceos_vmm:17] Starting virtualization...
    [  3.336566 0:2 arceos_vmm:19] Hardware support: true
    [  3.343360 0:6 arceos_vmm::vmm::timer:103] Initing HV Timer...
    [  3.349408 1:7 arceos_vmm::vmm::timer:103] Initing HV Timer...
    [  3.351081 0:6 arceos_vmm::hal:117] Hardware virtualization support enabled on core 0
    [  3.358813 1:7 arceos_vmm::hal:117] Hardware virtualization support enabled on core 1
    [  3.378942 0:2 arceos_vmm::vmm::config:34] Creating VM [0] "linux-a1000"
    [  3.387547 0:2 axvm::vm:113] Setting up memory region: [0x80000000~0xf0000000] READ | WRITE | EXECUTE
    [  3.398997 0:2 arceos_vmm::hal:27] Failed to allocate memory region [PA:0x80000000~PA:0xf0000000]: NoMemory
    [  3.429479 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x0~0x80000000] -> [0x0~0x80000000]
    [  3.462795 0:2 axvm::vm:191] VM created: id=0
    [  3.468812 0:2 axvm::vm:206] VM setup: id=0
    [  3.474734 0:2 arceos_vmm::vmm::config:41] VM[0] created success, loading images...
    [  3.484473 0:2 arceos_vmm::vmm::images:38] Loading VM images from memory
    [  3.528456 0:2 arceos_vmm::vmm::config:34] Creating VM [1] "arceos"
    [  3.536585 0:2 axvm::vm:113] Setting up memory region: [0x1b0000000~0x1b8000000] READ | WRITE | EXECUTE
    [  3.548225 0:2 arceos_vmm::hal:27] Failed to allocate memory region [PA:0x1b0000000~PA:0x1b8000000]: NoMemory
    [  3.561771 0:2 axvm::vm:156] Setting up passthrough device memory region: [0x20008000~0x20009000] -> [0x20008000~0x20009000]
    [  3.575339 0:2 axvm::vm:191] VM created: id=1
    [  3.581443 0:2 axvm::vm:206] VM setup: id=1
    [  3.587364 0:2 arceos_vmm::vmm::config:41] VM[1] created success, loading images...
    [  3.597103 0:2 arceos_vmm::vmm::images:64] Loading VM images from memory
    [  3.605823 0:2 arceos_vmm::vmm:30] Setting up vcpus...
    [  3.612765 0:2 arceos_vmm::vmm::vcpus:178] Initializing VM[0]'s 1 vcpus
    [  3.621353 0:2 arceos_vmm::vmm::vcpus:209] Spawning task for VM[0] Vcpu[0]
    [  3.630239 0:2 arceos_vmm::vmm::vcpus:221] Vcpu task Task(8, "VM[0]-VCpu[0]") created cpumask: [1, ]
    [  3.641597 0:2 arceos_vmm::vmm::vcpus:178] Initializing VM[1]'s 1 vcpus
    [  3.648676 1:8 arceos_vmm::vmm::vcpus:242] VM[0] Vcpu[0] waiting for running
    [  3.650188 0:2 arceos_vmm::vmm::vcpus:209] Spawning task for VM[1] Vcpu[0]
    [  3.668141 0:2 arceos_vmm::vmm::vcpus:221] Vcpu task Task(9, "VM[1]-VCpu[0]") created cpumask: [0, ]
    [  3.679503 0:2 arceos_vmm::vmm:37] VMM starting, booting VMs...
    [  3.687330 0:2 axvm::vm:273] Booting VM[0]
    [  3.693156 0:2 arceos_vmm::vmm:43] VM[0] boot success
    [  3.698675 1:8 arceos_vmm::vmm::vcpus:245] VM[0] Vcpu[0] running...
    [  3.700029 0:2 axvm::vm:273] Booting VM[1]
    [  3.714064 0:2 arceos_vmm::vmm:43] VM[1] boot success
    [  3.720942 0:9 arceos_vmm::vmm::vcpus:242] VM[1] Vcpu[0] waiting for running
    [  3.730010 0:9 arceos_vmm::vmm::vcpus:245] VM[1] Vcpu[0] running...
    a
           d8888                            .d88888b.   .d8888b.
          d88888                           d88P" "Y88b d88P  Y88b
         d88P888                           888     888 Y88b.
        d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
       d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
      d88P   888 888     888      88888888 888     888       "888
     d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
    d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
    
    arch = aarch64
    platform = aarch64-qemu-virt
    target = aarch64-unknown-none-softfloat
    build_mode = release
    log_level = debug
    smp = 1
    
    [  3.805794 0 axruntime:130] Logging is enabled.
    [  3.812095 0 axruntime:131] Primary CPU 0 started, dtb = 0x0.
    [  3.819733 0 axruntime:133] Found physcial memory regions:
    [  3.827086 0 axruntime:135]   [PA:0x1b0000000, PA:0x1b0007000) .text (READ | EXECUTE | RESERVED)
    [  3.838065 0 axruntime:135]   [PA:0x1b0007000, PA:0x1b0009000) .rodata (READ | RESERVED)
    [  3.848281 0 axruntime:135]   [PA:0x1b0009000, PA:0x1b000d000) .data .tdata .tbss .percpu (READ | WRITE | RESERVED)
    [  3.861076 0 axruntime:135]   [PA:0x1b000d000, PA:0x1b004d000) boot stack (READ | WRITE | RESERVED)
    [  3.872342 0 axruntime:135]   [PA:0x1b004d000, PA:0x1b004e000) .bss (READ | WRITE | RESERVED)
    [  3.883036 0 axruntime:135]   [PA:0x1b004e000, PA:0x1b8000000) free memory (READ | WRITE | FREE)
    [  3.894017 0 axruntime:135]   [PA:0x20008000, PA:0x20009000) mmio (READ | WRITE | DEVICE | RESERVED)
    [  3.905379 0 axruntime:150] Initialize platform devices...
    [  3.912731 0 axruntime:188] Primary CPU 0 init OK.
    Hello, world!
    [    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd050]
    [    0.000000] Linux version 6.1.54-rt15-00068-g09f2347c9237 (tanghanwe@ubuntu-virtual-machine) (aarch64-linux-gnu-gcc (Ubuntu 9.4.0-15
    [    0.000000] Machine model: BST A1000B FAD-A
    [    0.000000] earlycon: uart8250 at MMIO32 0x0000000020008000 (options '')
    [    0.000000] printk: bootconsole [uart8250] enabled
    [    0.000000] Invalid option string for rodata: 'n'
    [    0.000000] Reserved memory: created DMA memory pool at 0x000000008b000000, size 32 MiB
    [    0.000000] OF: reserved mem: initialized node bst_atf@8b000000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created DMA memory pool at 0x000000008fec0000, size 0 MiB
    [    0.000000] OF: reserved mem: initialized node bst_tee@8fec0000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created DMA memory pool at 0x000000008ff00000, size 1 MiB
    [    0.000000] OF: reserved mem: initialized node bstn_cma@8ff00000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created DMA memory pool at 0x000000009a000000, size 32 MiB
    [    0.000000] OF: reserved mem: initialized node bst_cv_cma@9a000000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created DMA memory pool at 0x000000009c000000, size 16 MiB
    [    0.000000] OF: reserved mem: initialized node vsp@0x9c000000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created DMA memory pool at 0x00000000a1000000, size 16 MiB
    [    0.000000] OF: reserved mem: initialized node bst_isp@0xa1000000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created CMA memory pool at 0x00000000b2000000, size 864 MiB
    [    0.000000] OF: reserved mem: initialized node coreip_pub_cma@0xb2000000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created CMA memory pool at 0x00000000e8000000, size 8 MiB
    [    0.000000] OF: reserved mem: initialized node noc_pmu@0xe8000000, compatible id shared-dma-pool
    [    0.000000] Reserved memory: created CMA memory pool at 0x00000000e8800000, size 8 MiB
    [    0.000000] OF: reserved mem: initialized node canfd@0xe8800000, compatible id shared-dma-pool
    [    0.000000] Zone ranges:
    [    0.000000]   DMA      [mem 0x0000000080000000-0x00000000efffffff]
    [    0.000000]   DMA32    empty
    [    0.000000]   Normal   empty
    [    0.000000] Movable zone start for each node
    [    0.000000] Early memory node ranges
    [    0.000000]   node   0: [mem 0x0000000080000000-0x000000008affffff]
    [    0.000000]   node   0: [mem 0x000000008b000000-0x000000008cffffff]
    [    0.000000]   node   0: [mem 0x000000008d000000-0x000000008fcfffff]
    [    0.000000]   node   0: [mem 0x000000008fd00000-0x000000008fdfffff]
    [    0.000000]   node   0: [mem 0x000000008fe00000-0x000000008febffff]
    [    0.000000]   node   0: [mem 0x000000008fec0000-0x00000000b1ffffff]
    [    0.000000]   node   0: [mem 0x00000000b2000000-0x00000000efffffff]
    [    0.000000] Initmem setup node 0 [mem 0x0000000080000000-0x00000000efffffff]
    [    0.000000] cma: Reserved 128 MiB at 0x0000000083000000
    [    0.000000] psci: probing for conduit method from DT.
    [    0.000000] psci: Using PSCI v0.1 Function IDs from DT
    [    0.000000] percpu: Embedded 19 pages/cpu s40872 r8192 d28760 u77824
    [    0.000000] Detected VIPT I-cache on CPU0
    [    0.000000] CPU features: detected: Qualcomm erratum 1009, or ARM erratum 1286807, 2441009
    [    0.000000] CPU features: detected: ARM errata 1165522, 1319367, or 1530923
    [    0.000000] alternatives: applying boot alternatives
    [    0.000000] Built 1 zonelists, mobility grouping on.  Total pages: 451584
    [    0.000000] Kernel command line: earlycon=uart8250,mmio32,0x20008000 console=ttyS0,115200n8 memreserve=64M@0xf8000000 rdinit=/sbin/n
    [    0.000000] Unknown kernel command line parameters "memreserve=64M@0xf8000000", will be passed to user space.
    [    0.000000] Dentry cache hash table entries: 262144 (order: 9, 2097152 bytes, linear)
    [    0.000000] Inode-cache hash table entries: 131072 (order: 8, 1048576 bytes, linear)
    [    0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
    [    0.000000] Memory: 148300K/1835008K available (11392K kernel code, 7766K rwdata, 3884K rodata, 1856K init, 2597K bss, 654516K rese)
    [    0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
    [    0.000000] rcu: Hierarchical RCU implementation.
    [    0.000000] rcu:     RCU restricting CPUs from NR_CPUS=8 to nr_cpu_ids=1.
    [    0.000000]  Tracing variant of Tasks RCU enabled.
    [    0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies.
    [    0.000000] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1
    [    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
    [    0.000000] Root IRQ handler: gic_handle_irq
    [    0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention.
    [    0.000000] arch_timer: cp15 timer(s) running at 325.00MHz (virt).
    [    0.000000] clocksource: arch_sys_counter: mask: 0x7ffffffffffffff max_cycles: 0x4af477f6aa, max_idle_ns: 440795207830 ns
    [    0.000000] sched_clock: 59 bits at 325MHz, resolution 3ns, wraps every 4398046511103ns
    [    0.009203] Console: colour dummy device 80x25
    [    0.014105] Calibrating delay loop (skipped), value calculated using timer frequency.. 650.00 BogoMIPS (lpj=3250000)
    [    0.025674] pid_max: default: 32768 minimum: 301
    [    0.030832] LSM: Security Framework initializing
    [    0.035944] SELinux:  Initializing.
    [    0.039963] Mount-cache hash table entries: 4096 (order: 3, 32768 bytes, linear)
    [    0.048098] Mountpoint-cache hash table entries: 4096 (order: 3, 32768 bytes, linear)
    [    0.057524] cacheinfo: Unable to detect cache hierarchy for CPU 0
    [    0.064574] cblist_init_generic: Setting adjustable number of callback queues.
    [    0.072506] cblist_init_generic: Setting shift to 0 and lim to 1.
    [    0.079357] rcu: Hierarchical SRCU implementation.
    [    0.079359] rcu:     Max phase no-delay instances is 1000.
    [    0.079396] printk: bootconsole [uart8250] printing thread started
    [    0.090833] smp: Bringing up secondary CPUs ...
    [    0.090836] smp: Brought up 1 node, 1 CPU
    [    0.090841] SMP: Total of 1 processors activated.
    [    0.090847] CPU features: detected: 32-bit EL0 Support
    [    0.090852] CPU features: detected: Data cache clean to the PoU not required for I/D coherence
    [    0.090856] CPU features: detected: Common not Private translations
    [    0.090858] CPU features: detected: CRC32 instructions
    [    0.090864] CPU features: detected: RCpc load-acquire (LDAPR)
    [    0.090866] CPU features: detected: Privileged Access Never
    [    0.090869] CPU features: detected: RAS Extension Support
    [    0.090920] CPU: All CPU(s) started at EL1
    [    0.090923] alternatives: applying system-wide alternatives
    [    0.092365] devtmpfs: initialized
    [    0.112929] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns
    [    0.112943] futex hash table entries: 256 (order: 2, 16384 bytes, linear)
    [    0.143754] pinctrl core: initialized pinctrl subsystem
    [    0.144394] NET: Registered PF_NETLINK/PF_ROUTE protocol family
    [    0.145208] DMA: preallocated 256 KiB GFP_KERNEL pool for atomic allocations
    [    0.145267] DMA: preallocated 256 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations
    [    0.145333] DMA: preallocated 256 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations
    [    0.328274] printk: console [ttyS0] enabled
    [    0.328278] printk: bootconsole [uart8250] disabled
    [    0.328291] printk: bootconsole [uart8250] printing thread stopped
    [    0.328548] dw-apb-uart 2000a000.serial: uart clock frequency (&p->uartclk):25000000
    [    0.328553] dw-apb-uart 2000a000.serial: uart clock frequency (baudclk):25000000
    [    0.328557] dw-apb-uart 2000a000.serial: uart clock frequency (apb_pclk):100000000
    [    0.328665] 2000a000.serial: ttyS1 at MMIO 0x2000a000 (irq = 32, base_baud = 1562500) is a 16550A
    [    0.328889] dw-apb-uart 20009000.serial: uart clock frequency (&p->uartclk):25000000
    [    0.328894] dw-apb-uart 20009000.serial: uart clock frequency (baudclk):25000000
    [    0.328898] dw-apb-uart 20009000.serial: uart clock frequency (apb_pclk):100000000
    [    0.328996] 20009000.serial: ttyS2 at MMIO 0x20009000 (irq = 33, base_baud = 1562500) is a 16550A
    [    0.329813] =======lt9211_probe in...
    [    0.329816] =======lt9211_probe in1...
    [    0.329818] nlt9211 4-002d: =======lt9211_probe in2...
    [    0.329900] max96789 1-0040: *************MAX96789 RGB To MIPIDSI Config*************
    [    0.329935] printk: console [ttyS0] printing thread started
    [    0.412081] ====update_chnl_id in ...!
    [    0.649752] MAX config start
    [    1.999794] End of MAX config status 0
    [    1.999945] Mali<2>:
    [    1.999947] Inserting Mali v900 device driver.
    [    1.999950] Mali<2>:
    [    1.999950] Compiled: Jan 22 2025, time: 15:53:22.
    [    1.999953] Mali<2>:
    [    1.999954] Driver revision: -6.1.54.REL.B231218-68-g09f2347c9237
    [    1.999956] Mali<2>:
    [    1.999957] mali_module_init() registering driver
    [    2.000028] Mali<2>:
    [    2.000029] mali_probe(): Called for platform device 33300000.gpu
    [    2.000113] Mali<2>:
    [    2.000114] mali-450 device tree detected.
    [    2.000252] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPP2 not found
    [    2.000260] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPPMMU2 not found
    [    2.000265] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPP3 not found
    [    2.000271] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPPMMU3 not found
    [    2.000276] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPP4 not found
    [    2.000282] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPPMMU4 not found
    [    2.000286] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPP5 not found
    [    2.000292] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPPMMU5 not found
    [    2.000297] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPP6 not found
    [    2.000302] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPPMMU6 not found
    [    2.000307] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPP7 not found
    [    2.000311] mali-utgard 33300000.gpu: error -ENXIO: IRQ IRQPPMMU not found
    [    2.000437] Mali<2>:
    [    2.000439] Mali SWAP: Swap out threshold vaule is 60M
    [    2.000453] Mali<2>:
    [    2.000454] Mali memory settings (shared: 0xFFFFFFFF)
    [    2.000458] Mali<2>:
    [    2.000459] Using device defined frame buffer settings (0x01000000@0xB8000000)
    [    2.000463] Mali<2>:
    [    2.000464] Memory Validator installed for Mali physical address base=0xB8000000, size=0x01000000
    [    2.000469] Mali<2>:
    [    2.000471] Mali PMU: Creating Mali PMU core
    [    2.000477] Mali<2>:
    [    2.000478] Couldn't find pmu_switch_delay in device tree configuration.
    [    2.000481] Mali<2>:
    [    2.000482] Get pmu config from device tree configuration.
    [    2.000484] Mali<2>:
    [    2.000485] Using hw detect pmu config:
    [    2.000488] Mali<2>:
    [    2.000490] domain_config[0] = 0x1
    [    2.000492] Mali<2>:
    [    2.000493] domain_config[1] = 0x2
    [    2.000495] Mali<2>:
    [    2.000496] domain_config[2] = 0x4
    [    2.000498] Mali<2>:
    [    2.000499] domain_config[9] = 0x1
    [    2.000501] Mali<2>:
    [    2.000502] domain_config[10] = 0x2
    [    2.000505] Mali<2>:
    [    2.000506] Mali PM domain: Creating Mali PM domain (mask=0x00000001)
    [    2.000508] Mali<2>:
    [    2.000509] Mali PM domain: Creating Mali PM domain (mask=0x00000002)
    [    2.000511] Mali<2>:
    [    2.000512] Mali PM domain: Creating Mali PM domain (mask=0x00000004)
    [    2.000514] Mali<2>:
    [    2.000515] Mali PM domain: Creating Mali PM domain (mask=0x00001000)
    [    2.000521] Mali<2>:
    [    2.000522] Broadcast: Creating Mali Broadcast unit: Mali_Broadcast
    [    2.000533] Mali<2>:
    [    2.000534] Mali PP: Creating Mali PP core: Mali_PP0
    [    2.000535] Mali<2>:
    [    2.000536] Mali PP: Base address of PP core: 0x33308000
    [    2.000589] Mali<2>:
    [    2.000590] Found Mali GPU Mali-450 MP r0p0
    [    2.000648] Mali<2>:
    [    2.000650] Mali DLBU: Initializing
    [    2.000666] Mali<2>:
    [    2.000667] Mali L2 cache: Created Mali_L2:   8K, 4-way, 64byte cache line, 128bit external bus
    [    2.000675] Mali<2>:
    [    2.000676] Mali L2 cache: Created Mali_L2:  64K, 4-way, 64byte cache line, 128bit external bus
    [    2.000686] Mali<2>:
    [    2.000688] Mali MMU: Creating Mali MMU: Mali_GP_MMU
    [    2.000709] Mali<2>:
    [    2.000711] mali_mmu_probe_irq_acknowledge: intstat 0x3
    [    2.000713] Mali<2>:
    [    2.000714] Probe: Page fault detect: PASSED
    [    2.000716] Mali<2>:
    [    2.000717] Probe: Bus read error detect: PASSED
    [    2.000726] Mali<2>:
    [    2.000727] Mali GP: Creating Mali GP core: Mali_GP
    [    2.000769] Mali<2>:
    [    2.000770] Mali MMU: Creating Mali MMU: Mali_PP0_MMU
    [    2.000789] Mali<2>:
    [    2.000790] mali_mmu_probe_irq_acknowledge: intstat 0x3
    [    2.000792] Mali<2>:
    [    2.000793] Probe: Page fault detect: PASSED
    [    2.000794] Mali<2>:
    [    2.000796] Probe: Bus read error detect: PASSED
    [    2.000804] Mali<2>:
    [    2.000805] Mali PP: Creating Mali PP core: Mali_PP0
    [    2.000806] Mali<2>:
    [    2.000807] Mali PP: Base address of PP core: 0x33308000
    [    2.000838] Mali<2>:
    [    2.000840] Mali MMU: Creating Mali MMU: Mali_PP1_MMU
    [    2.000865] Mali<2>:
    [    2.000867] mali_mmu_probe_irq_acknowledge: intstat 0x3
    [    2.000869] Mali<2>:
    [    2.000870] Probe: Page fault detect: PASSED
    [    2.000871] Mali<2>:
    [    2.000872] Probe: Bus read error detect: PASSED
    [    2.000881] Mali<2>:
    [    2.000882] Mali PP: Creating Mali PP core: Mali_PP1
    [    2.000883] Mali<2>:
    [    2.000884] Mali PP: Base address of PP core: 0x3330a000
    [    2.000909] Mali<2>:
    [    2.000910] Starting new virtual group for MMU PP broadcast core Mali_PP_MMU_Broadcast
    [    2.000912] Mali<2>:
    [    2.000913] Mali DLBU: Creating Mali dynamic load balancing unit: Mali_DLBU
    [    2.000918] Mali<2>:
    [    2.000919] Broadcast: Creating Mali Broadcast unit: Mali_Broadcast
    [    2.000925] Mali<2>:
    [    2.000926] Mali MMU: Creating Mali MMU: Mali_PP_MMU_Broadcast
    [    2.000930] Mali<2>:
    [    2.000931] Mali PP: Creating Mali PP core: Mali_PP_Broadcast
    [    2.000932] Mali<2>:
    [    2.000933] Mali PP: Base address of PP core: 0x33316000
    [    2.000971] Mali<2>:
    [    2.000972] 2+0 PP cores initialized
    [    2.000985] Mali<2>:
    [    2.000987] Mali GPU Timer: 1000
    [    2.000990] Mali<2>:
    [    2.000990] Mali GPU Utilization: No platform utilization handler installed
    [    2.000993] Mali<2>:
    [    2.000994] Mali DVFS init: platform function callback incomplete, need check mali_gpu_device_data in platform .
    [    2.001356] Mali<2>:
    [    2.001358] mali_probe(): Successfully initialized driver for platform device 33300000.gpu
    [    2.001417] Mali:
    [    2.001419] Mali device driver loaded
    [    2.001470] cacheinfo: Unable to detect cache hierarchy for CPU 0
    [    2.006214] brd: module loaded
    [    2.008623] loop: module loaded
    [    2.009038] null_blk: disk nullb0 created
    [    2.009042] null_blk: module loaded
    [    2.009046] dummy-irq: no IRQ given.  Use irq=N
    [    2.010260] slave@0 enforce active low on chipselect handle
    [    2.021387] qspi0-nor0@0 enforce active low on chipselect handle
    [    2.179081] spi-nor spi6.0: w25q256jw (32768 Kbytes)
    [    2.179172] 2 fixed-partitions partitions found on MTD device spi6.0
    [    2.179176] Creating 2 MTD partitions on "spi6.0":
    [    2.179189] 0x000000000000-0x000001e00000 : "nor0_part0"
    [    2.179739] 0x000001e00000-0x000002000000 : "nor0_part1"
    [    2.181991] bst_canfd 20016000.canfd: Driver registered: regs=0xffffffc009f54000, irq=44, clock=200000000
    [    2.182428] bst_canfd 20016800.canfd: Driver registered: regs=0xffffffc009f58800, irq=45, clock=200000000
    [    2.182935] bst_canfd 20017000.canfd: Driver registered: regs=0xffffffc009f5c000, irq=46, clock=200000000
    [    2.183054] CAN device driver interface
    [    2.183100] usbcore: registered new interface driver asix
    [    2.183131] usbcore: registered new interface driver ax88179_178a
    [    2.183156] usbcore: registered new interface driver cdc_ether
    [    2.183175] usbcore: registered new interface driver net1080
    [    2.183192] usbcore: registered new interface driver cdc_subset
    [    2.183208] usbcore: registered new interface driver zaurus
    [    2.183239] usbcore: registered new interface driver cdc_ncm
    [    2.183263] usbcore: registered new interface driver r8153_ecm
    [    2.183804] dwc3,usb:dwc3_set_reqinfo_len,1082
    [    2.190672] bst-dwc3 amba_apu@0:usb2: usb30 could not find power control gpio.
    [    2.190896] dwc3,usb:dwc3_set_reqinfo_len,1082
    [    2.191939] xhci-hcd xhci-hcd.0.auto: xHCI Host Controller
    [    2.191956] xhci-hcd xhci-hcd.0.auto: new USB bus registered, assigned bus number 1
    [    2.192359] xhci-hcd xhci-hcd.0.auto: hcc params 0x0220fe64 hci version 0x110 quirks 0x0000000000010010
    [    2.192392] xhci-hcd xhci-hcd.0.auto: irq 47, io mem 0x30200000
    [    2.192515] xhci-hcd xhci-hcd.0.auto: xHCI Host Controller
    [    2.192523] xhci-hcd xhci-hcd.0.auto: new USB bus registered, assigned bus number 2
    [    2.192530] xhci-hcd xhci-hcd.0.auto: Host supports USB 3.0 SuperSpeed
    [    2.192901] hub 1-0:1.0: USB hub found
    [    2.192934] hub 1-0:1.0: 1 port detected
    [    2.193097] usb usb2: We don't know the algorithms for LPM for this host, disabling LPM.
    [    2.193357] hub 2-0:1.0: USB hub found
    [    2.193381] hub 2-0:1.0: 1 port detected
    [    2.193626] usbcore: registered new interface driver uas
    [    2.193670] usbcore: registered new interface driver usb-storage
    [    2.193736] usbcore: registered new interface driver option
    [    2.193749] usbserial: USB Serial support registered for GSM modem (1-port)
    [    2.193848] gadgetfs: USB Gadget filesystem, version 24 Aug 2004
    [    2.193950] i2c_dev: i2c /dev entries driver
    [    2.194458] bst,maxim-deser-hub 2-0029: maxim_hub_parse_dt() line:1255 GMSL2
    [    2.194468] bst,maxim-deser-hub 2-0029: lane-num = 2
    [    2.194491] bst,maxim-deser-hub 2-0029: trigger-tx-gpio index0  = 8
    [    2.194496] bst,maxim-deser-hub 2-0029: camera index is 0,ser is 42,ser_alias is 60,sensor addr is 36, sensor_i2c_addr_alias is 70
    [    2.194505] bst,maxim-deser-hub 2-0029: parse_input_dt:: input device1 not found
    [    2.194515] bst,maxim-deser-hub 2-0029: parse_input_dt:: input device2 not found
    [    2.194527] bst,maxim-deser-hub 2-0029: parse_input_dt:: input device3 not found
    [    2.246055] bst,maxim-deser-hub 2-0029: read_back REG_ENABLE : 0x14
    [    2.251854] bst,maxim-deser-hub 2-0029: read_back REG_MNL : 0x10
    [    2.251864] bst,maxim-deser-hub 2-0029: maxim_hub_probe: lock gpio -2 is invalid
    [    2.359981] maxim hub probe done
    [    2.360116] bst,maxim-deser-hub 2-002a: maxim_hub_parse_dt() line:1255 GMSL2
    [    2.360136] bst,maxim-deser-hub 2-002a: trigger-tx-gpio index0  = 0
    [    2.360141] bst,maxim-deser-hub 2-002a: camera index is 0,ser is 42,ser_alias is 64,sensor addr is 36, sensor_i2c_addr_alias is 54
    [    2.360156] bst,maxim-deser-hub 2-002a: trigger-tx-gpio index1  = 0
    [    2.360161] bst,maxim-deser-hub 2-002a: camera index is 1,ser is 42,ser_alias is 65,sensor addr is 36, sensor_i2c_addr_alias is 55
    [    2.360177] bst,maxim-deser-hub 2-002a: trigger-tx-gpio index2  = 0
    [    2.360182] bst,maxim-deser-hub 2-002a: camera index is 2,ser is 42,ser_alias is 66,sensor addr is 36, sensor_i2c_addr_alias is 56
    [    2.360198] bst,maxim-deser-hub 2-002a: trigger-tx-gpio index3  = 0
    [    2.360203] bst,maxim-deser-hub 2-002a: camera index is 3,ser is 42,ser_alias is 67,sensor addr is 36, sensor_i2c_addr_alias is 57
    [    2.428511] bst,maxim-deser-hub 2-002a: read_back REG_ENABLE : 0x14
    [    2.435440] bst,maxim-deser-hub 2-002a: read_back REG_MNL : 0x10
    [    2.435449] bst,maxim-deser-hub 2-002a: maxim_hub_probe: lock gpio -2 is invalid
    [    2.543722] maxim hub probe done
    [    2.543952] bst,maxim-deser-hub 2-002e: maxim_hub_parse_dt() line:1255 GMSL2
    [    2.543960] bst,maxim-deser-hub 2-002e: lane-num = 2
    [    2.543975] bst,maxim-deser-hub 2-002e: trigger-tx-gpio index0  = 8
    [    2.543980] bst,maxim-deser-hub 2-002e: camera index is 0,ser is 42,ser_alias is 48,sensor addr is 36, sensor_i2c_addr_alias is 58
    [    2.543990] bst,maxim-deser-hub 2-002e: parse_input_dt:: input device1 not found
    [    2.543995] bst,maxim-deser-hub 2-002e: parse_input_dt:: input port2 not found
    [    2.543995]
    [    2.544000] bst,maxim-deser-hub 2-002e: parse_input_dt:: input port3 not found
    [    2.544000]
    [    2.596048] i2c_transfer error, slave = 0x2e, reg = 0x17, ret = -121
    [    2.617712] i2c_transfer error, slave = 0x2e, reg = 0x17, ret = -121
    [    2.628477] i2c_transfer error, slave = 0x2e, reg = 0x17, ret = -121
    [    2.633481] write_reg() line:177, write 2e:[17,14]failed!
    [    2.633486] bst,maxim-deser-hub 2-002e: max96712_reg_write: write 0x17 failed
    [    2.641093] i2c_transfer error, slave = 0x2e, reg = 0x19, ret = -121
    [    2.648385] i2c_transfer error, slave = 0x2e, reg = 0x19, ret = -121
    [    2.656981] i2c_transfer error, slave = 0x2e, reg = 0x19, ret = -121
    [    2.661984] write_reg() line:177, write 2e:[19,10]failed!
    [    2.661988] bst,maxim-deser-hub 2-002e: max96712_reg_write: write 0x19 failed
    [    2.669782] i2c_transfer error, slave = 0x2e, reg = 0x17, ret = -121
    [    2.681765] i2c_transfer error, slave = 0x2e, reg = 0x17, ret = -121
    [    2.699231] i2c_transfer error, slave = 0x2e, reg = 0x17, ret = -121
    [    2.704236] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x17,0x74]failed!
    [    2.704241] bst,maxim-deser-hub 2-002e: read_back REG_ENABLE : 0x74
    [    2.706534] i2c_transfer error, slave = 0x2e, reg = 0x19, ret = -121
    [    2.721833] i2c_transfer error, slave = 0x2e, reg = 0x19, ret = -121
    [    2.730518] i2c_transfer error, slave = 0x2e, reg = 0x19, ret = -121
    [    2.735523] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x19,0x74]failed!
    [    2.735529] bst,maxim-deser-hub 2-002e: read_back REG_MNL : 0x74
    [    2.735538] bst,maxim-deser-hub 2-002e: maxim_hub_probe: lock gpio -2 is invalid
    [    2.741656] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.752880] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.761914] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.766918] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x0,0x0]failed!
    [    2.807958] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.818290] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.832877] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.837882] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x0,0x0]failed!
    [    2.878198] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.888876] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.899817] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.904822] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x0,0x0]failed!
    [    2.951156] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.965569] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.989650] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    2.994655] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x0,0x0]failed!
    [    3.035950] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    3.059508] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    3.068798] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    3.073802] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x0,0x0]failed!
    [    3.118634] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    3.131052] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    3.142345] i2c_transfer error, slave = 0x2e, reg = 0x0, ret = -121
    [    3.147350] bst,maxim-deser-hub 2-002e: max96712_reg_read() line:256, read 0x2e:[0x0,0x0]failed!
    [    3.189046] bst,maxim-deser-hub 2-002e: detect max96712 timeout
    [    3.189049] bst,maxim-deser-hub 2-002e: maxim_hub_probe: not found max96712
    [    3.189058] bst,maxim-deser-hub: probe of 2-002e failed with error -22
    [    3.189676] a1000-csi2 csi@0: a1000_csi_probe
    [    3.189682] a1000-csi2 csi@0: a1000_csi_probe
    [    3.189708] mipi chn 0 connected
    [    3.189712] mipi chn 1 connected
    [    3.189716] mipi chn 2 connected
    [    3.189719] mipi chn 3 connected
    [    3.189835] a1000-csi2 csi@1: a1000_csi_probe
    [    3.189840] a1000-csi2 csi@1: a1000_csi_probe
    [    3.189861] mipi chn 0 connected
    [    3.189865] mipi chn 1 connected
    [    3.189869] mipi chn 2 connected
    [    3.189873] mipi chn 3 connected
    [    3.189971] a1000-csi2 csi@3: a1000_csi_probe
    [    3.189976] a1000-csi2 csi@3: a1000_csi_probe
    [    3.189993] mipi chn 0 connected
    [    3.189997] mipi chn 1 connected
    [    3.190000] mipi chn 2 not connected
    [    3.190003] mipi chn 3 not connected
    [    3.190172] bst_wdt 2001b000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.190935] bst_wdt 2001c000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.191072] bst_wdt 2001d000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.191202] bst_wdt 32009000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.191405] bst_wdt 3200a000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.191578] bst_wdt 3200b000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.191752] bst_wdt 3200c000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.191939] bst_wdt 3200d000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.192109] bst_wdt 3200e000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.192301] bst_wdt 3200f000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.192467] bst_wdt 32010000.watchdog: wdt bst_wdt_drv_probe, 517
    [    3.192774] sdhci: Secure Digital Host Controller Interface driver
    [    3.192777] sdhci: Copyright(c) Pierre Ossman
    [    3.192778] sdhci-pltfm: SDHCI platform and OF driver helper
    [    3.192969] sdhci-dwcmshc 30400000.dwmmc0: dwcmshc_probe
    [    3.257851] mmc0: SDHCI controller on 30400000.dwmmc0 [30400000.dwmmc0] using ADMA
    [    3.258001] sdhci-dwcmshc 30500000.dwmmc1: dwcmshc_probe
    [    3.504668] mmc0: new high speed MMC card at address 0001
    [    3.505324] mmcblk0: mmc0:0001 CJUD4R 59.6 GiB
    [    3.577787]  mmcblk0: p1 p2 p3 p4 p5 p6 p7 p8 p9 p10
    [    3.579269] mmcblk0boot0: mmc0:0001 CJUD4R 31.9 MiB
    [    3.581351] mmcblk0boot1: mmc0:0001 CJUD4R 31.9 MiB
    [    3.595544] mmcblk0rpmb: mmc0:0001 CJUD4R 4.00 MiB, chardev (239:0)
    [    4.339768] i2c_designware 20005000.i2c: controller timed out
    [    4.339788] sdhci_bst_i2c_write_bytes: i2c write failed: -110
    [    5.379754] i2c_designware 20005000.i2c: controller timed out
    [    5.379760] sdhci_bst_i2c_read_bytes:  i2c read 1 bytes from client@0x8 starting at reg 0x8d failed, error: -110
    [    5.379765] sdhci_bst_i2c_voltage_sel: i2c test failed readdata: 255 send data:1
    [    5.379768] sdhci_bst_voltage_switch failed
    [    5.418222] mmc1: SDHCI controller on 30500000.dwmmc1 [30500000.dwmmc1] using ADMA
    [    5.418423] hid: raw HID events driver (C) Jiri Kosina
    [    5.418636] optee: probing for conduit method.
    [    5.418663] optee: revision 3.11 (28993363)
    [    5.419063] optee: initialized driver
    [    5.419965] netem: version 1.3
    [    5.419985] u32 classifier
    [    5.419986]     Performance counters on
    [    5.419987]     input device check on
    [    5.419988]     Actions configured
    [    5.420100] ipip: IPv4 and MPLS over IPv4 tunneling driver
    [    5.420443] gre: GRE over IPv4 demultiplexor driver
    [    5.420810] NET: Registered PF_INET6 protocol family
    [    5.434247] Segment Routing with IPv6
    [    5.434270] In-situ OAM (IOAM) with IPv6
    [    5.434305] sit: IPv6, IPv4 and MPLS over IPv4 tunneling driver
    [    5.434904] NET: Registered PF_PACKET protocol family
    [    5.434908] can: controller area network core
    [    5.434948] NET: Registered PF_CAN protocol family
    [    5.434953] can: raw protocol
    [    5.434959] 8021q: 802.1Q VLAN Support v1.8
    [    5.435003] sctp: Hash tables configured (bind 256/256)
    [    5.435180] Key type dns_resolver registered
    [    5.435268] ipc 4fec00000.ipc: assigned reserved memory node bstn_cma@8ff00000
    [    5.435621] Loading compiled-in X.509 certificates
    [    5.510900] [bst_cv]: bst_cv_probe 48: BST_CV driver initializing ...
    [    5.510916] [bst_cv]: bst_cv_probe 66: bst_sysfile_init OK
    [    5.510923] [bst_cv]: bst_cv_mem_manager_init 307: phys_to_bus_offset: 0x0
    [    5.510928] [bst_cv]: bst_cv_mem_manager_init 314: dma_set_coherent_mask OK.
    [    5.511059] bst_cv 51030000.bst_cv: assigned reserved memory node bst_cv_cma@9a000000
    [    5.511063] [bst_cv]: bst_cv_mem_manager_init 328: kern_sub_phys_offset: 0xffffffbf78000000
    [    5.511068] [bst_cv]: bst_cv_probe 74: bst_cv_mem_manager_init OK
    [    5.511111] [bst_cv]: bst_cv_probe 82: bst_cv_fw_manager_init OK
    [    5.511184] [bst_cv]: bst_cv_probe 90: bst_cv_misc_init OK, /dev/bst_cv registered
    [    5.511188] [bst_cv]: bst_cv_probe 96: bst_cv probe completed!
    [    5.511304] [bst_lwnn]: bst_lwnn_probe 74: bst_lwnn driver initializing ...
    [    5.511321] [bst_lwnn]: bst_lwnn_probe 93: bst_sysfile_init OK, /sys/kernel/bst_lwnn registered
    [    5.511327] [bst_lwnn]: bst_lwnn_mem_manager_init 346: phys_to_bus_offset: 0x0
    [    5.511331] [bst_lwnn]: bst_lwnn_mem_manager_init 353: dma_set_coherent_mask OK.
    [    5.511355] bst_lwnn 51030000.bst_lwnn: assigned reserved memory node coreip_pub_cma@0xb2000000
    [    5.511360] [bst_lwnn]: bst_lwnn_probe 101: bst_lwnn_mem_manager_init OK
    [    5.511700] [bst_lwnn]: bst_lwnn_probe 109: bst_lwnn_fw_manager_init OK
    [    5.511751] [bst_lwnn]: bst_lwnn_msg_manager_init 302: ipc_init OK
    [    5.511871] [bst_lwnn]: bst_lwnn_msg_manager_init 334: worker creation OK
    [    5.511876] [bst_lwnn]: bst_lwnn_probe 117: bst_lwnn_msg_manager_init OK
    [    5.511936] [bst_lwnn]: bst_lwnn_probe 125: bst_lwnn_misc_init OK, /dev/bst_lwnn registered
    [    5.511941] [bst_lwnn]: bst_lwnn_probe 133: bst_lwnn probe completed!
    [    5.512064] [bstn]: bstn_probe 50: BSTN driver initializing ...
    [    5.512068] [bstn]: bstn_probe 51: timeout_jiffies: 3200, timeout_ms 32000
    [    5.512077] [bstn]: bstn_mem_manager_init 310: phys_to_bus_offset: 0x0
    [    5.512083] [bstn]: bstn_mem_manager_init 324: reserved memory: base 0xb2000000 size 0x36000000
    [    5.512088] [bstn]: bstn_mem_manager_init 333: dma_set_mask OK.
    [    5.512091] [bstn]: bstn_mem_manager_init 340: dma_set_coherent_mask OK.
    [    5.512097] bstn 50020000.bstn: assigned reserved memory node coreip_pub_cma@0xb2000000
    [    5.512101] [bstn]: bstn_probe 76: bstn_mem_manager_init OK
    [    5.512119] [bstn]: bstn_fw_manager_init 300: firmware: bstn_dsp_rtos.rbf
    [    5.512165] [bstn]: bstn_fw_manager_init 320: assigned mem: 0xffffffc00a232000, 0xb2005000, size: 4096
    [    5.512172] [bstn]: bstn_probe 84: bstn_fw_manager_init OK
    [    5.512180] [bstn]: bstn_msg_manager_init 238: ipc_init OK
    [    5.512203] [bstn]: bstn_msg_manager_init 262: req_bufs @ phys:0xb2006000
    [    5.512258] [bstn]: bstn_msg_manager_init 286: bstn_msg_receiver task created 0xffffff800e434380
    [    5.512263] [bstn]: bstn_probe 92: bstn_msg_manager_init OK
    [    5.512273] [bstn]: bstn_probe 100: bstn_sysfile_init OK
    [    5.512344] [bstn]: bstn_probe 108: bstn_misc_init OK, device[bstn0] registered
    [    5.512348] [bstn]: bstn_probe 111: BSTN v2.5.3 probe completed
    [    5.537056] bst_identify_probe
    [    5.537343] vsp-ipc 9c000000.ipc_vsp: assigned reserved memory node vsp@0x9c000000
    [    5.537379] init start = 0x9c200000, initp_size = 0x20660, align size = 0x21000
    [    5.537384] cmdp start = 0x9c221000, cmdp_size = 0x2098a0, align size = 0x20a000
    [    5.537387] slab start = 0x9c500000, end = 0x9c600000, slab_size = 0x100000
    [    5.537390] total_alloc_size = 0x600000
    [    5.538507] c0.base  = (____ptrval____), c1.base  = (____ptrval____), c2.base  = (____ptrval____)
    [    5.560295] enter recv
    [    5.560318] printk: console [netcon0] enabled
    [    5.560321] netconsole: network logging started
    [    5.560934] bstgmaceth 30000000.ethernet: error -ENXIO: IRQ rx_chan4_irq not found
    [    5.561038] bstgmaceth 30000000.ethernet: error -ENXIO: IRQ tx_chan4_irq not found
    [    5.561208] printk: console [netcon0] printing thread started
    [    5.573255] bstgmaceth 30000000.ethernet: User ID: 0x10, Synopsys ID: 0x51
    [    5.573264] bstgmaceth 30000000.ethernet:    DWMAC4/5
    [    5.573270] bstgmaceth 30000000.ethernet: DMA HW capability register supported
    [    5.573273] bstgmaceth 30000000.ethernet: RX Checksum Offload Engine supported
    [    5.573276] bstgmaceth 30000000.ethernet: TX Checksum insertion supported
    [    5.573289] bstgmaceth 30000000.ethernet (unnamed net_device) (uninitialized): device MAC address 6a:78:6a:e9:b1:c2
    [    5.573298] bstgmaceth 30000000.ethernet: Enabled Flow TC (entries=2)
    [    5.574338] bstgmaceth 30100000.ethernet: error -ENXIO: IRQ rx_chan4_irq not found
    [    5.574444] bstgmaceth 30100000.ethernet: error -ENXIO: IRQ tx_chan4_irq not found
    [    5.574803] bstgmaceth 30100000.ethernet: User ID: 0x10, Synopsys ID: 0x51
    [    5.574810] bstgmaceth 30100000.ethernet:    DWMAC4/5
    [    5.574814] bstgmaceth 30100000.ethernet: DMA HW capability register supported
    [    5.574817] bstgmaceth 30100000.ethernet: RX Checksum Offload Engine supported
    [    5.574821] bstgmaceth 30100000.ethernet: TX Checksum insertion supported
    [    5.574830] bstgmaceth 30100000.ethernet (unnamed net_device) (uninitialized): device MAC address 7e:fc:7a:ea:0f:1c
    [    5.574836] bstgmaceth 30100000.ethernet: Enabled Flow TC (entries=2)
    [    6.043844] mdio_bus bstgmac-1: MDIO device at address 7 is missing.
    [    6.044017] bstgmaceth 30100000.ethernet: Cannot register the MDIO bus err -19
    [    6.044022] bstgmaceth 30100000.ethernet: bstgmac_dvr_probe: MDIO bus (id: 1) registration failed
    [    6.044409] a1000_isp isp: isp_probe
    [    6.044415] a1000_isp isp: isp_probe
    [    6.044520] a1000_isp isp: init_isp_channel_devs channel 10 not enabled, skip
    [    6.044525] a1000_isp isp: init_isp_channel_devs channel 11 not enabled, skip
    [    6.044679] deser_notify_bound(),line 1069 channel[3]
    [    6.044687] deser_notify_bound(),line 1069 channel[2]
    [    6.044693] deser_notify_bound(),line 1069 channel[1]
    [    6.044697] deser_notify_bound(),line 1069 channel[0]
    [    6.044709] deser_notify_bound(),line 1069 channel[0]
    [    6.044744] a1000_isp isp: assigned reserved memory node bst_isp@0xa1000000
    [    6.045519] Enter dphy_config
    [    6.045534] dphyTst_setCfg_lanes
    [    6.279753]
    [    6.279753] DPHY_SHUTDOWNZ(40) = 0
    [    6.279756]
    [    6.279756] DPHY lane_speed = 1600
    [    6.279808]
    [    6.279808] reg e5 value is 0x1
    [    6.279814]
    [    6.279814] reg 1ac value is 0x4b
    [    6.279820] nreg e4 value is 0x11
    [    6.279825]
    [    6.279825] reg 8 value is 0x18
    [    6.279827]
    [    6.279827] DPHY_N_LANES(4) = 3(ENABLE RX)
    [    6.279829]
    [    6.279829] force rxmode = 0x3c0030
    [    6.279831] dphyTst_release
    [    6.279832]
    [    6.279832] DPHY_SHUTDOWNZ(40) = 1
    [    6.279834]
    [    6.279834] DPHY_RSTZ(44) = 1
    [    6.345208] dphyTst_release timeout
    [    6.345211]
    [    6.345211] dphy0 enable done.
    [    6.345212] dphyTst_release_1_4lane
    [    6.345214]
    [    6.345214] DPHY_1_RSTZ = 3c003c
    [    6.347220]
    [    6.347220] dphy0 and dphy1 enter stopstate.
    [    6.347222]
    [    6.347222] release force rxmode = 0x3c
    [    6.347224] dphyTst_release_1_4lane finish
    [    6.347238] bst,maxim-deser-hub 2-002a: maxim_hub_s_power() line:986
    [    6.347244] bst,maxim-deser-hub 2-002a: maxim_hub_s_power() line:1007 GMSL2
    [    6.407974] bst,maxim-deser-hub 2-002a: max967XX_replicate_mode() line:509
    [    6.408170] bst,maxim-deser-hub 2-002a: max96712_fsync_config() line:436, tr0
    [    6.408938] bst,maxim-deser-hub 2-002a: INTERNAL TRIGGER MODE
    [    6.409702] bst,maxim-deser-hub 2-002a: trig_info.trigger_tx_gpio[0] = 0
    [    6.416644] bst,maxim-deser-hub 2-002a: modify_serdes_address() 254
    [    6.750420] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 0d
    [    6.811052] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 1d
    [    6.871687] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 2d
    [    6.932321] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 3d
    
    CTRL-A Z for help | 115200 8N1 | NOR | Minicom 2.9 | VT102 | Offline | ttyUSB0
    [    7.166303] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 0, not linked
    [    7.226936] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 1, not linked
    [    7.287571] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 2, not linked
    [    7.348212] bst,maxim-deser-hub 2-002a: is_gmsl2_video_connected() index = 3, not linked
    [    7.456521] bst,maxim-deser-hub 2-002a: Failed to request irq 0
    [    7.456526] bst,maxim-deser-hub 2-002a: maxim_hub_s_power(), line 1034, max96712 s_power success!
    [    7.456540] Enter dphy_config
    [    7.456555] dphyTst_setCfg_lanes
    [    7.689752]
    [    7.689752] DPHY_SHUTDOWNZ(40) = 0
    [    7.689755]
    [    7.689755] DPHY lane_speed = 2400
    [    7.689808]
    [    7.689808] reg e5 value is 0x1
    [    7.689813]
    [    7.689813] reg 1ac value is 0x4b
    [    7.689819] nreg e4 value is 0x11
    [    7.689825]
    [    7.689825] reg 8 value is 0x18
    [    7.689827]
    [    7.689827] DPHY_N_LANES(4) = 1(ENABLE RX)
    [    7.689829]
    [    7.689829] force rxmode = 0x3c0030
    [    7.689831] dphyTst_release
    [    7.689832]
    [    7.689832] DPHY_SHUTDOWNZ(40) = 1
    [    7.689834]
    [    7.689834] DPHY_RSTZ(44) = 1
    [    7.755201] dphyTst_release timeout
    [    7.755203]
    [    7.755203] dphy0 enable done.
    [    7.755204] dphyTst_release_1_4lane
    [    7.755206]
    [    7.755206] DPHY_1_RSTZ = 3c003c
    [    7.803760] dphyTst_release_1_4lane timeout
    [    7.803762]
    [    7.803762] dphy0 and dphy1 enter stopstate.
    [    7.803764]
    [    7.803764] release force rxmode = 0x3c
    [    7.803765] dphyTst_release_1_4lane finish
    [    7.803777] bst,maxim-deser-hub 2-0029: maxim_hub_s_power() line:986
    [    7.803782] bst,maxim-deser-hub 2-0029: maxim_hub_s_power() line:1007 GMSL2
    [    7.853309] bst,maxim-deser-hub 2-0029: max967XX_replicate_mode() line:509
    [    7.853506] bst,maxim-deser-hub 2-0029: max96712_fsync_config() line:436, trig_info->trigger_tx_gpio[0] = 8
    [    7.854272] bst,maxim-deser-hub 2-0029: INTERNAL TRIGGER MODE
    [    7.855036] bst,maxim-deser-hub 2-0029: trig_info.trigger_tx_gpio[0] = 8
    [    7.855231] bst,maxim-deser-hub 2-0029: modify_serdes_address() 254
    [    8.196057] bst,maxim-deser-hub 2-0029: is_gmsl2_video_connected() index = 0, not linked
    [    8.196062] modify_serdes_address() cam_dev [1] is NULL, break
    [    8.196064] modify_serdes_address() cam_dev [2] is NULL, break
    [    8.196066] modify_serdes_address() cam_dev [3] is NULL, break
    [    8.447360] bst,maxim-deser-hub 2-0029: is_gmsl2_video_connected() index = 0, not linked
    [    8.507997] bst,maxim-deser-hub 2-0029: is_gmsl2_video_connected() index = 1, not linked
    [    8.568632] bst,maxim-deser-hub 2-0029: is_gmsl2_video_connected() index = 2, not linked
    [    8.629276] bst,maxim-deser-hub 2-0029: is_gmsl2_video_connected() index = 3, not linked
    [    8.737584] bst,maxim-deser-hub 2-0029: Failed to request irq 0
    [    8.737588] bst,maxim-deser-hub 2-0029: maxim_hub_s_power(), line 1034, max96712 s_power success!
    [    8.737593] ox08b camera_s_power(), line 246
    [    8.737600] cfg_num 0, alg_num 0
    [    8.737607] a1000_isp isp: assigned reserved memory node coreip_pub_cma@0xb2000000
    [    8.738184] vsp vsp@1: assigned reserved memory node coreip_pub_cma@0xb2000000
    [    8.738345] [drm] plane:31 created
    [    8.738352] [drm] plane:33 created
    [    8.738718] [drm] Initialized bst-vsp 1.0.0 20200416 for vsp@1 on minor 0
    [    8.738732] bst_drm_platform_probe exit!
    [    8.738844] bst-gmwarp gmwarp@0: assigned reserved memory node coreip_pub_cma@0xb2000000
    [    8.738932] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-0 as /dev/video30
    [    8.739001] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-1 as /dev/video31
    [    8.739059] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-2 as /dev/video32
    [    8.739130] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-3 as /dev/video33
    [    8.739189] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-4 as /dev/video34
    [    8.739245] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-5 as /dev/video35
    [    8.739316] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-6 as /dev/video36
    [    8.739384] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-7 as /dev/video37
    [    8.739448] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-8 as /dev/video38
    [    8.739512] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-9 as /dev/video39
    [    8.739568] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-10 as /dev/video40
    [    8.739633] bst-gmwarp gmwarp@0: Registered bst_gmwarp_channel-11 as /dev/video41
    [    8.739637] bst-gmwarp gmwarp@0: gmwarp probe ok!
    [    8.754621] bst-encode encoder@0: assigned reserved memory node coreip_pub_cma@0xb2000000
    [    8.754701] bst-encode encoder@0: Registerd bst_encoder-0 as /dev/video50
    [    8.754761] bst-encode encoder@0: Registerd bst_encoder-1 as /dev/video51
    [    8.754817] bst-encode encoder@0: Registerd bst_encoder-2 as /dev/video52
    [    8.754882] bst-encode encoder@0: Registerd bst_encoder-3 as /dev/video53
    [    8.754946] bst-encode encoder@0: Registerd bst_encoder-4 as /dev/video54
    [    8.755003] bst-encode encoder@0: Registerd bst_encoder-5 as /dev/video55
    [    8.755065] bst-encode encoder@0: Registerd bst_encoder-6 as /dev/video56
    [    8.755141] bst-encode encoder@0: Registerd bst_encoder-7 as /dev/video57
    [    8.755190] ALSA device list:
    [    8.755195]   No soundcards found.
    [    8.972528] EXT4-fs (mmcblk0p7): recovery complete
    [    8.972949] EXT4-fs (mmcblk0p7): mounted filesystem with ordered data mode. Quota mode: none.
    [    8.972988] VFS: Mounted root (ext4 filesystem) on device 179:7.
    [    8.973612] devtmpfs: mounted
    [    8.974012] Freeing unused kernel memory: 1856K
    [    8.974282] Run /sbin/init as init process
    [    9.158743] audit: type=1404 audit(9.129:2): enforcing=1 old_enforcing=0 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=selinux res=1
    [    9.218475] SELinux:  Permission watch in class filesystem not defined in policy.
    [    9.218507] SELinux:  Permission watch in class file not defined in policy.
    [    9.218509] SELinux:  Permission watch_mount in class file not defined in policy.
    [    9.218512] SELinux:  Permission watch_sb in class file not defined in policy.
    [    9.218515] SELinux:  Permission watch_with_perm in class file not defined in policy.
    [    9.218517] SELinux:  Permission watch_reads in class file not defined in policy.
    [    9.218525] SELinux:  Permission watch in class dir not defined in policy.
    [    9.218528] SELinux:  Permission watch_mount in class dir not defined in policy.
    [    9.218530] SELinux:  Permission watch_sb in class dir not defined in policy.
    [    9.218532] SELinux:  Permission watch_with_perm in class dir not defined in policy.
    [    9.218534] SELinux:  Permission watch_reads in class dir not defined in policy.
    [    9.218545] SELinux:  Permission watch in class lnk_file not defined in policy.
    [    9.218548] SELinux:  Permission watch_mount in class lnk_file not defined in policy.
    [    9.218550] SELinux:  Permission watch_sb in class lnk_file not defined in policy.
    [    9.218553] SELinux:  Permission watch_with_perm in class lnk_file not defined in policy.
    [    9.218555] SELinux:  Permission watch_reads in class lnk_file not defined in policy.
    [    9.218562] SELinux:  Permission watch in class chr_file not defined in policy.
    [    9.218564] SELinux:  Permission watch_mount in class chr_file not defined in policy.
    [    9.218566] SELinux:  Permission watch_sb in class chr_file not defined in policy.
    [    9.218569] SELinux:  Permission watch_with_perm in class chr_file not defined in policy.
    [    9.218571] SELinux:  Permission watch_reads in class chr_file not defined in policy.
    [    9.218578] SELinux:  Permission watch in class blk_file not defined in policy.
    [    9.218580] SELinux:  Permission watch_mount in class blk_file not defined in policy.
    [    9.218582] SELinux:  Permission watch_sb in class blk_file not defined in policy.
    [    9.218584] SELinux:  Permission watch_with_perm in class blk_file not defined in policy.
    [    9.218587] SELinux:  Permission watch_reads in class blk_file not defined in policy.
    [    9.218593] SELinux:  Permission watch in class sock_file not defined in policy.
    [    9.218595] SELinux:  Permission watch_mount in class sock_file not defined in policy.
    [    9.218597] SELinux:  Permission watch_sb in class sock_file not defined in policy.
    [    9.218600] SELinux:  Permission watch_with_perm in class sock_file not defined in policy.
    [    9.218602] SELinux:  Permission watch_reads in class sock_file not defined in policy.
    [    9.218608] SELinux:  Permission watch in class fifo_file not defined in policy.
    [    9.218610] SELinux:  Permission watch_mount in class fifo_file not defined in policy.
    [    9.218613] SELinux:  Permission watch_sb in class fifo_file not defined in policy.
    [    9.218615] SELinux:  Permission watch_with_perm in class fifo_file not defined in policy.
    [    9.218618] SELinux:  Permission watch_reads in class fifo_file not defined in policy.
    [    9.218753] SELinux:  Permission perfmon in class capability2 not defined in policy.
    [    9.218756] SELinux:  Permission bpf in class capability2 not defined in policy.
    [    9.218758] SELinux:  Permission checkpoint_restore in class capability2 not defined in policy.
    [    9.218775] SELinux:  Permission perfmon in class cap2_userns not defined in policy.
    [    9.218778] SELinux:  Permission bpf in class cap2_userns not defined in policy.
    [    9.218780] SELinux:  Permission checkpoint_restore in class cap2_userns not defined in policy.
    [    9.218872] SELinux:  Class mctp_socket not defined in policy.
    [    9.218874] SELinux:  Class perf_event not defined in policy.
    [    9.218875] SELinux:  Class anon_inode not defined in policy.
    [    9.218877] SELinux:  Class io_uring not defined in policy.
    [    9.218879] SELinux:  Class user_namespace not defined in policy.
    [    9.218881] SELinux: the above unknown classes and permissions will be allowed
    [    9.241537] SELinux:  policy capability network_peer_controls=1
    [    9.241551] SELinux:  policy capability open_perms=1
    [    9.241553] SELinux:  policy capability extended_socket_class=1
    [    9.241555] SELinux:  policy capability always_check_network=0
    [    9.241558] SELinux:  policy capability cgroup_seclabel=1
    [    9.241560] SELinux:  policy capability nnp_nosuid_transition=1
    [    9.241562] SELinux:  policy capability genfs_seclabel_symlinks=0
    [    9.241564] SELinux:  policy capability ioctl_skip_cloexec=0
    [    9.337566] audit: type=1403 audit(9.309:3): auid=4294967295 ses=4294967295 lsm=selinux res=1
    [    9.347876] systemd[1]: Successfully loaded SELinux policy in 189.944ms.
    [    9.445456] systemd[1]: System time before build time, advancing clock.
    [    9.642816] systemd[1]: Relabelled /dev, /dev/shm, /run, /sys/fs/cgroup in 48.506ms.
    [    9.692606] systemd[1]: systemd 241-9-gc1f8ff8+ running in system mode. (+PAM +AUDIT +SELINUX -IMA -APPARMOR -SMACK +SYSVINIT -UTMP -LIBCRYPTSETUP -GCRYPT -GNUTLS -ACL -XZ -LZ4 -SECC)
    [    9.692902] systemd[1]: Detected architecture arm64.
    [    9.761188] systemd[1]: Set hostname to <a1000>.
    [    9.765722] systemd[1]: Failed to bump fs.file-max, ignoring: Invalid argument
    [    9.865774] systemd-fstab-generator[142]: Mount point  is not a valid path, ignoring.
    [    9.881793] systemd-fstab-generator[142]: Mount point  is not a valid path, ignoring.
    [    9.882075] systemd-fstab-generator[142]: Mount point  is not a valid path, ignoring.
    [    9.945688] systemd[1]: File /lib/systemd/system/systemd-journald.service:12 configures an IP firewall (IPAddressDeny=any), but the local system does not support BPF/cgroup based fir.
    [    9.945702] systemd[1]: Proceeding WITHOUT firewalling in effect! (This warning is only shown for the first loaded unit using IP firewalling.)
    [    9.993988] systemd[1]: Configuration file /lib/systemd/system/user-startup.service is marked executable. Please remove executable permission bits. Proceeding anyway.
    [   10.015829] systemd[1]: /lib/systemd/system/usb-gadget@.service:14: Unknown lvalue 'After' in section 'Service', ignoring
    [   10.019272] systemd[1]: Configuration file /lib/systemd/system/safety-service.service is marked executable. Please remove executable permission bits. Proceeding anyway.
    [   10.019582] systemd[1]: /lib/systemd/system/safety-service.service:8: Unknown lvalue 'StartLimitIntervalSec' in section 'Service', ignoring
    [   12.379759] random: crng init done
    [   12.475848] early application starting...
    [   12.730278] audit: type=1130 audit(1675679554.279:4): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=selinux-labeldev comm="systemd" exe="/lib/'
    [   12.730364] audit: type=1131 audit(1675679554.279:5): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=selinux-labeldev comm="systemd" exe="/lib/'
    [   12.731973] audit: type=1130 audit(1675679554.279:6): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=kmod-static-nodes comm="systemd" exe="/lib'
    [   12.761921] audit: type=1130 audit(1675679554.309:7): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-sysctl comm="systemd" exe="/lib/sy'
    [   12.860995] EXT4-fs (mmcblk0p7): re-mounted. Quota mode: none.
    [   12.865560] audit: type=1130 audit(1675679554.409:8): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-remount-fs comm="systemd" exe="/li'
    [   12.956353] audit: type=1130 audit(1675679554.499:9): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-tmpfiles-setup-dev comm="systemd" '
    [   13.098380] audit: type=1130 audit(1675679554.639:10): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-journald comm="systemd" exe="/lib'
    [   13.180022] systemd-journald[173]: Received request to flush runtime journal from PID 1
    [   13.187569] systemd-journald[173]: File /var/log/journal/c9eb360cf45a4d3ca7df8dc9a4b9d632/system.journal corrupted or uncleanly shut down, renaming and replacing.
    [   13.209076] audit: type=1130 audit(1675679554.749:11): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-udevd comm="systemd" exe="/lib/sy'
    [   13.904594] bst_noc_pmu_probe, 296
    [   13.904685] bst_nocpmu 32702000.noc_pmu: assigned reserved memory node noc_pmu@0xe8000000
    [   13.936242] bst-thermal 70039000.thermal: cooling_dev, name=pwm
    [   14.912819] EXT4-fs (mmcblk0p9): recovery complete
    [   14.912842] EXT4-fs (mmcblk0p9): mounted filesystem with ordered data mode. Quota mode: none.
    [   14.916892] ext4 filesystem being mounted at /secdata supports timestamps until 2038 (0x7fffffff)
    [   14.965123] EXT4-fs (mmcblk0p6): recovery complete
    [   14.965622] EXT4-fs (mmcblk0p6): mounted filesystem with ordered data mode. Quota mode: none.
    [   15.471416] EXT4-fs (mmcblk0p10): recovery complete
    [   15.471609] EXT4-fs (mmcblk0p10): mounted filesystem with ordered data mode. Quota mode: none.
    [   15.583524] kauditd_printk_skb: 2 callbacks suppressed
    [   15.583538] audit: type=1130 audit(1675679557.129:14): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=selinux-autorelabel comm="systemd" exe="/'
    [   15.583841] audit: type=1131 audit(1675679557.129:15): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=selinux-autorelabel comm="systemd" exe="/'
    [   15.640743] audit: type=1130 audit(1675679557.189:16): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=selinux-init comm="systemd" exe="/lib/sys'
    [   15.641029] audit: type=1131 audit(1675679557.189:17): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=selinux-init comm="systemd" exe="/lib/sys'
    [   15.745845] audit: type=1130 audit(1675679557.289:18): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-tmpfiles-setup comm="systemd" exe'
    [   18.103131] audit: type=1130 audit(1675679559.649:19): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=bstosuser comm="systemd" exe="/lib/system'
    [   18.103678] audit: type=1131 audit(1675679559.649:20): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=bstosuser comm="systemd" exe="/lib/system'
    [   18.193782] audit: type=1130 audit(1675679559.739:21): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=safety-service comm="systemd" exe="/lib/s'
    [   18.197052] audit: type=1130 audit(1675679559.739:22): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=user-startup comm="systemd" exe="/lib/sys'
    [   18.237303] audit: type=1130 audit(1675679559.779:23): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=busybox-syslog comm="systemd" exe="/lib/s'
    [   18.831535] picp: picp init start...
    [   18.831663] picp: pci not init..
    [   18.893208] picp: picp init start...
    [   18.893340] picp: pci not init..
    [   18.934665] picp: picp init start...
    [   18.934806] picp: pci not init..
    [   20.667877] file system registered
    [   20.741400] dwmac4: Master AXI performs fixed burst length
    [   20.741447] bstgmaceth 30000000.ethernet eth0: Safety Features Fix to 0.Hw feature 3
    [   20.741459] bstgmaceth 30000000.ethernet eth0: No Safety Features support found
    [   20.741492] bstgmaceth 30000000.ethernet eth0: IEEE 1588-2008 Advanced Timestamp supported
    [   20.746066] pps pps0: new PPS source ptp0
    [   20.746880] bstgmaceth 30000000.ethernet eth0: registered PTP clock
    [   20.746902] bstgmaceth 30000000.ethernet eth0: configuring for fixed/rgmii link mode
    [   20.756958] bstgmaceth 30000000.ethernet eth0: Link is Up - 1Gbps/Full - flow control off
    [   20.768570] bstgmaceth 30000000.ethernet eth0: Request Tx chan:0 irq:67.
    [   20.768589] bstgmaceth 30000000.ethernet eth0: Request Tx chan:1 irq:68.
    [   20.770788] bstgmaceth 30000000.ethernet eth0: Request Tx chan:2 irq:69.
    [   20.770818] bstgmaceth 30000000.ethernet eth0: Request Tx chan:3 irq:70.
    [   20.776672] bstgmaceth 30000000.ethernet eth0: Request Rx chan:0 irq:63.
    [   20.782635] bstgmaceth 30000000.ethernet eth0: Request Rx chan:1 irq:64.
    [   20.795483] bstgmaceth 30000000.ethernet eth0: Request Rx chan:2 irq:65.
    [   20.801242] bstgmaceth 30000000.ethernet eth0: Request Rx chan:3 irq:66.
    [   20.816356] 8021q: adding VLAN 0 to HW filter on device eth0
    [   20.880677] kauditd_printk_skb: 19 callbacks suppressed
    [   20.880692] audit: type=1130 audit(1675679562.429:43): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=NetworkManager-dispatcher comm="systemd" '
    [   20.913516] EXT4-fs (mmcblk0p5): recovery complete
    [   20.913543] EXT4-fs (mmcblk0p5): mounted filesystem with ordered data mode. Quota mode: none.
    [   21.166231] read descriptors
    [   21.166250] read descriptors
    [   21.166255] read strings
    [   21.303268] bstgmac_ethtool_get_link_ksettings: eth0: PHY is not registered
    [   21.498934] audit: type=1404 audit(1675679563.039:44): enforcing=0 old_enforcing=1 auid=4294967295 ses=4294967295 enabled=1 old-enabled=1 lsm=selinux res=1
    [   21.504209] audit: type=1300 audit(1675679563.039:44): arch=c00000b7 syscall=64 success=yes exit=1 a0=3 a1=7fe032a6d8 a2=1 a3=7fbb55ea78 items=0 ppid=371 pid=633 auid=4294967295 uid=)
    [   21.504921] audit: type=1327 audit(1675679563.039:44): proctitle=736574656E666F7263650030
    [   21.520737] audit: type=1130 audit(1675679563.069:45): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=rc-local comm="systemd" exe="/lib/systemd'
    [   21.574655] audit: type=1130 audit(1675679563.119:46): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=getty@tty1 comm="systemd" exe="/lib/syste'
    [   21.636310] audit: type=1130 audit(1675679563.179:47): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=serial-getty@ttyS0 comm="systemd" exe="/l'
    [   22.202000] audit: type=1130 audit(1675679563.749:48): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=usb-gadget@g1 comm="systemd" exe="/lib/sy'
    [   22.310234] bash (697): /proc/173/oom_adj is deprecated, please use /proc/173/oom_score_adj instead.
    
    BSTOS (Operation System by Black Sesame Technologies) 2.3.0.4 a1000 ttyS0
    
    a1000 login: [   23.484629] audit: type=1130 audit(1675679565.029:49): pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=udsservice_autostart comm="s'
    [   23.637773] audit: type=1701 audit(1675679565.179:50): auid=4294967295 uid=0 gid=0 ses=4294967295 subj=system_u:system_r:initrc_t:s0 pid=701 comm="uds_service" exe="/usr/bin/uds_serv1
    

适配问题总结

  • 目前加载guest的方式尚不健全,需要改进

  • 遇到的主要问题就是跑飞的问题,在qemu环境下实现时没有遇到,在上板子的时候则出现问题,在胡博的帮助下成功定位

    由于在进入guest时没有无效化guest内核镜像加载区域的数据缓存,在进入guest进行一些数据读写相关的指令时访问的不是正确的数据内容,导致跑飞

    通过在进入前对对应的区域进行缓存无效化后成功解决

Copyright © 2025 • Created by ArceOS Team

RK3588

目前,在黑芝麻 A1000 平台上已经对仅运行 Linux 的情况进行了验证。

ArceOS

TODO

Linux

  1. 从内核源码编译一个合适的linux镜像文件和dtb文件

  2. 设置 arceos-vmm/configs/vms/linux-rk3588-aarch64.toml文件下的相关信息,此文件为guest相关配置

    id = 1                    //vm id
    name = "linux"			  //vm 命名
    vm_type = 1				  //vm类型
    phy_cpu_sets = [0x1]      //设定每一个vcpu的亲和性
    cpu_num = 1             //cpu 数量
    entry_point = 0x1008_0000  //guest内核入口地址,约定内核从这个地址开始运行
    kernel_load_addr = 0x1008_0000   //guest内核加载地址,约定将内核加载到这个地址
    dtb_load_addr = 0x1000_0000      //guest dtb加载地址,约定将dtb加载到这个地址
    
    # The location of image: "memory" | "fs"
    # load from memory
    image_location = "memory"       //内核加载方式,rk3588目前为从内存中加载guest镜像
    kernel_path = "linux-rk3588-aarch64.bin" //guest 内核镜像所在的本地路径,推荐使用绝对地址
    dtb_path = "linux-rk3588.dtb"			 //guest dtb文件所在的本地路径,推荐使用绝对地址
    
    # ramdisk_path = ""
    # ramdisk_load_addr = 0
    # disk_path = "disk.img"
    # Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`).
    # For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`.
    memory_regions = [
        [0x0, 0x10_f000, 0x37, 1],        # rk3588使用到的一段uncached内存,需要一一映射
        [0x940_0000, 0x76c00000, 0x7, 1], # 一一映射给guest的物理内存,根据实际的物理内存按需分配即可
    ]
    
    # Emu_devices
    # Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig
    emu_devices = []
    
    # Pass-through devices
    # Name Base-Ipa Base-Pa Length Alloc-Irq
    //此处的设备地址目前主要是根据设备树来的,需要给guest linux什么设备,就同时修改此处的配置和相关的dtb,使二者一致即可
    passthrough_devices = [
        [
            "ramoops",
            0x11_0000,
            0x11_0000,					
            0xf_0000,
            0x1,
        ],
        [
            "sram",
            0x10_f000,
            0x10_f000,
            0x1000,
            0x1,
        ],
        [
            "gpu",
            0xfb00_0000,
            0xfb00_0000,
            0x200000,
            0x1,
        ],
        [
            "uart8250 UART",
            0xfd00_0000,
            0xfd00_0000,
            0x2000000,
            0x1,
        ],
        [
            "usb",
            0xfc00_0000,
            0xfc00_0000,
            0x1000000,
            0x1,
        ],
    ]
    
  3. 设置arceos-vmm/configs/platforms/aarch64-rk3588j-hv.toml文件下的相关信息,此文件为host相关配置

    # Architecture identifier.
    arch = "aarch64" # str                //host 架构
    # Platform identifier.
    platform = "aarch64-rk3588j" # str     //host 平台
    
    #
    # Platform configs
    #
    [plat]
    # Platform family.
    family = "aarch64-rk3588j"
    
    # Base address of the whole physical memory.
    phys-memory-base = 0x20_0000 # uint         //host 物理内存起始地址
    # Size of the whole physical memory.
    phys-memory-size = 0x800_0000    # uint     //host自身管理使用的内存大小,由于目前对非连续物理内存的支持不够完善,此处物理内存的大小可以小于host使用的内存,在guest vm使用一一映射的内存可以不在此段内存中
    # Base physical address of the kernel image.
    kernel-base-paddr = 0x48_0000 # uint         //host内核起始物理地址
    # Base virtual address of the kernel image.
    kernel-base-vaddr = "0x0000_0000_0048_0000"   //host内核起始虚拟地址
    # Linear mapping offset, for quick conversions between physical and virtual
    # addresses.
    phys-virt-offset = "0x0000_0000_0000_0000"
    # Kernel address space base.
    kernel-aspace-base = "0x0000_0000_0000_0000"
    # Kernel address space size.
    kernel-aspace-size = "0x0000_ffff_ffff_f000"
    
    #
    # Device specifications
    #
    [devices]
    # MMIO regions with format (`base_paddr`, `size`).
    //host物理设备的地址
    mmio-regions = [
        [0xfeb50000, 0x1000], # uart8250 UART0
        [0xfe600000, 0x10000], # gic-v3 gicd
        [0xfe680000, 0x100000], # gic-v3 gicr
        [0xa41000000, 0x400000],
        [0xa40c00000, 0x400000],
        [0xf4000000,0x1000000],
        [0xf3000000,0x1000000],
    ] # [(uint, uint)]
    # VirtIO MMIO regions with format (`base_paddr`, `size`).
    virtio-mmio-regions = []  # [(uint, uint)]
    
    # Base physical address of the PCIe ECAM space.
    pci-ecam-base = 0xf4000000  # uint
    # End PCI bus number (`bus-range` property in device tree).
    pci-bus-end = 0xff # uint
    # PCI device memory ranges (`ranges` property in device tree).
    pci-ranges = [] # [(uint, uint)]
    # UART Address
    uart-paddr = 0xfeb5_0000 # uint
    uart-irq = 0x14d # uint
    
    # GICC Address
    gicd-paddr = 0xfe600000 # uint
    # GICR Address
    gicc-paddr = 0xfe680000 # uint
    gicr-paddr = 0xfe680000 # uint
    
    # PSCI
    psci-method = "smc" # str
    
    # pl031@9010000 {
    #     clock-names = "apb_pclk";
    #     clocks = <0x8000>;
    #     interrupts = <0x00 0x02 0x04>;
    #     reg = <0x00 0x9010000 0x00 0x1000>;
    #     compatible = "arm,pl031\0arm,primecell";
    # };
    # RTC (PL031) Address
    rtc-paddr = 0x901_0000          # uint
    
  4. 使用make A=(pwd) ARCH=aarch64 VM_CONFIGS=configs/vms/linux-rk3588-aarch64.toml PLAT_NAME=aarch64-rk3588j-hv FEATURES=page-alloc-64g,hv LOG=info kernel 编译出一个可烧写的arceos-umhv内核镜像boot.img文件

  5. 使用rk3588官方提供的工具等方式将开发板原先的内核镜像文件替换为step 4编译出的文件

  6. 断电重启

ArceOS + Linux

TODO

Copyright © 2025 • Created by ArceOS Team

正在适配中。。。

Copyright © 2025 • Created by ArceOS Team

总体设计

1. 设计目标

ArceOS-Hypervisor 是基于 ArceOS unikernel 框架实现的 Hypervisor。其目标是利用 ArceOS 提供的基础操作系统功能作为基础,实现一个统一的模块化 Hypervisor。统一指使用同一套代码同时支持 x86_64、arm(aarch64) 和 RISC-V 三种架构,以最大化复用架构无关代码,简化代码开发和维护成本。模块化指 Hypervisor 的功能被分解为多个模块,每个模块实现一个特定的功能,模块之间通过标准接口进行通信,以实现功能的解耦和复用。

ArceOS 是一个基于 Rust 语言的 unikernel 框架,其设计目标是提供一个高性能、模块化、最小化的操作系统基座。通过在 ArceOS 的基础上添加不同的模块,就可以对应不同的应用场景生成不同的操作系统:在 ArceOS 上直接添加应用程序,就可以生成一个独立的应用程序 unikernel 镜像;在 ArceOS 上添加宏内核模块,就可以生成一个完整的宏内核操作系统;ArceOS-Hypervisor 则在 ArceOS 的基础上添加虚拟化相关模块,从而以最小成本实现一个 Type-1 Hypervisor。

arceos-architecture

2. 软件架构

ArceOS-Hypervisor 的软件架构如下图所示,图中每一个框都是一个独立的模块,模块之间通过标准接口进行通信。包括作为基础的 ArceOS 在内,ArceOS-Hypervisor 的软件架构分为五层:

arceos-hypervisor-architecture

AxVisor 整体架构

2.1. ArceOS

在 ArceOS-Hypervisor 中,ArceOS 作为最底层的基础存在,提供内存管理、任务调度、设备驱动、同步原语等多种基础功能。ArceOS 的模块化设计允许 ArceOS-Hypervisor 灵活选择需要的模块,这不仅缩减了编译的开销和二进制体积,也提高了系统的安全性和可靠性。

2.2. ArceOS-VMM 应用程序(App)

ArceOS-VMM 应用程序是整个 ArceOS-Hypervisor 的核心,它作为 ArceOS 上的一个 unikernel 应用程序运行。ArceOS-VMM 应用程序负责管理虚拟机的生命周期,进行创建、销毁、启动、停止等操作,维护虚拟机的配置、状态、资源等信息,同时也负责处理虚拟机之间的隔离与通信,以及虚拟机对硬件资源的申请和访问。

2.3. axvm 模块

axvm 模块位于 ArceOS-Hypervisor 的中间层,它定义了虚拟机的数据结构和操作接口,具体实现了虚拟机的创建、销毁、启动、停止等功能。同时,axvm 模块还负责虚拟机内部虚拟 CPU 的创建、销毁、启动、停止等功能,并负责管理虚拟内存、虚拟设备等资源,实现虚拟环境内操作系统和应用程序对各种虚拟资源的访问。

2.4. axvcpuaxaddrspaceaxdevice 模块

axvm 模块的下层是 axvcpuaxaddrspaceaxdevice 三个具体实现的模块,axvcpu 模块负责架构无关虚拟 CPU 的具体实现,axaddrspace 模块负责虚拟内存的具体实现,axdevice 模块负责虚拟设备的具体实现。这三个模块共同构成了虚拟机的基础设施,为虚拟机提供了 CPU、内存、设备等基本资源。

axvcpu 模块定义了虚拟 CPU 的数据结构和统一操作接口。尽管各个架构下的虚拟化技术千差万别,但是通过统一的接口,架构之间的差异在 axvcpu 模块中得到了屏蔽,从而允许 axvm 及以上层的模块不受架构的限制,实现架构无关的虚拟机管理,提高了代码的复用性和可移植性。

axaddrspace 模块定义了虚拟内存特别是嵌套页表的数据结构。通过复用 ArceOS 的页表等数据结构,实现了架构无关的虚拟内存管理。

axdevice 模块定义了虚拟设备统一访问接口,提供了虚拟设备的基本抽象和封装,允许虚拟机通过统一的接口访问不同的虚拟设备,从而实现虚拟机对硬件资源的访问。

2.5. 具体实现模块

基于 axvcpuaxdevice 模块,ArceOS-Hypervisor 实现了不同架构下的具体虚拟 CPU 和虚拟设备模块,虚拟 CPU 包括 x86_vcpuarm_vcpuriscv_vcpu,虚拟设备包括 x86_vlapicarm_gic 以及正在实现的 virtio_blkvirtio_net 等等。这些模块实现了具体的虚拟化功能,并且通过 axvcpuaxdevice 模块提供的统一接口与其它模块进行交互,这使得代码的复用性和可移植性得到了极大的提高。

3. 运行流程

3.1. 虚拟 CPU 调度

ArceOS-Hypervisor 的执行流程的核心是虚拟 CPU 的调度。在 ArceOS-Hypervisor 中,虚拟 CPU 是虚拟机的基本执行单元,每个虚拟机可以包含一个或多个虚拟 CPU。虚拟 CPU 的调度是通过复用 ArceOS 的任务调度机制实现的,每个虚拟 CPU 作为一个任务,由 ArceOS 的任务调度器进行调度:

vcpu scheduling

ArceOS-Hypervisor 还支持混合的调度策略。对于不同的虚拟 CPU,可以采用不同的调度策略,例如,对于实时任务,可以将对应的虚拟 CPU 固定在一个物理 CPU 上,独占物理 CPU 的资源,以保证实时任务的响应时间;对于普通任务,则通过调度器进行动态调度,以实现资源的高效利用:

vcpu scheduling

未来计划实现:unikernel axtask、宏内核 process 以及 AxVisor vcpu 的统一调度

3.3. 二阶段地址翻译

3.3. VMExit 处理

vmexit handling

3.4 虚拟设备实现

3.4.1 Virtio-device

AxVisor 实现 virtio-device 后端设备,具体的设备实现通过类似影子进程的设计转发给 Linux 实现

virtio

3.5. 影子进程

影子进程是一种通过将具体设备直通给虚拟机内的 Linux 等成品操作系统,让其他虚拟机通过虚拟机间通信和共享内存等方式与这个 Linux 进行通信,从而利用 Linux 中的现有驱动程序来实现虚拟设备的一种技术。影子进程技术可以大大减少虚拟机监控器的开发工作量,提高虚拟机监控器的可移植性和可扩展性。

Copyright © 2025 • Created by ArceOS Team

AxVisor

AxVisor 作为虚拟机监控器(VMM)运行,构建并作为 ArceOS 独立内核应用程序运行。

如上依赖关系图所示,它提供了一个全局视角的虚拟化资源管理,作为连接 ArceOS 核心功能组件与虚拟化相关组件的桥梁。

一方面,它直接依赖于 ArceOS 提供的 axstd 库,调用 ArceOS 的核心功能。一些直接的依赖包括:

  • axtask based vCpu management and scheduling
  • axhal for platform-specific operations and interrupt handling
  • axconfig for platform configuration

另一方面,它依赖于 axvm 来实现虚拟机管理(配置与运行时),包括:

  • ​CRUD operations for guest VMs
  • ​VM lifecycle control: setup, boot, notification and shutdown
  • Hypercall handling for communication between hypervisor and guest VMs

VM 管理

  • hypercall handler
  • GLOBAL_VM_LIST

基于 axtask 的 vCPU 调度

axvcpu 仅负责虚拟化功能支持,例如通过 vmlaunch/vmexit 进入/退出客户机。

由于 ArceOS 已经提供了 axtask 用于在单一特权级别下进行运行时控制流管理,我们可以重用其调度器并与之共同发展。

在虚拟机启动和设置过程中,axvisor 为每个 vCPU 分配 axtask,将任务的入口函数设置为 vcpu_run(),如果 vCPU 有专用的物理 CPU 集,它还会初始化 CPU 掩码。

vcpu_run()

vcpu_run() 函数是 vCPU 任务的主要例程,可以总结如下:

#![allow(unused)]
fn main() {
fn vcpu_run() {
    let curr = axtask::current();

    let vm = curr.task_ext().vm.clone();
    let vcpu = curr.task_ext().vcpu.clone();

    loop {
        match vm.run_vcpu(vcpu_id) {
            // match vcpu.run() {
            Ok(exit_reason) => match exit_reason {
                AxVCpuExitReason::Hypercall { nr, args } => {}
                }
                AxVCpuExitReason::ExternalInterrupt { vector } => {
                    // Irq injection logic
                }
                AxVCpuExitReason::Halt => {
                    wait(vm_id)
                }
                AxVCpuExitReason::Nothing => {}
                AxVCpuExitReason::CpuDown { _state } => {
                    // Sleep target axtask.
                }
                AxVCpuExitReason::CpuUp {
                    target_cpu,
                    entry_point,
                    arg,
                } => {
                    // Spawn axtask for target vCpu.
                    vcpu_on(vm.clone(), target_cpu as _, entry_point, arg as _);
                    vcpu.set_gpr(0, 0);
                }
                AxVCpuExitReason::SystemDown => {}
                _ => {
                    warn!("Unhandled VM-Exit");
                }
            },
            Err(err) => {}
        }
    }
}
}

Task 扩展

该机制允许调用者在不修改 axtask 结构体源代码的情况下自定义其扩展字段,(这是一种类似于线程局部存储(TLS)的轻量级机制)。

axtask 结构体的基本字段:

  • 任务执行所需的基本信息,包括函数调用上下文、栈指针以及其他运行时元数据。

使用场景

  • 宏内核的扩展
    • Process metadata (e.g., PID)
    • Memory management informations like page table
    • Resource management including fd table
    • ...
  • hypervisor 扩展
    • vCPU state
    • Metadata of the associated VM
    • ...

Task 扩展设计

  • task_ext_ptr 引入作为扩展字段
  • 利用基于指针的访问方式,实现与原生结构体字段相当的内存访问性能。
  • 通过 def_task_ext 在编译时确定扩展字段的大小。
  • 在堆上分配内存,将扩展字段指针 task_ext_ptr 设置为该内存块。
  • 暴露引用 API 供外部访问
  • init_task_ext 初始化
#![allow(unused)]
fn main() {
// arceos/modules/axtask/src/task_ext.rs
#[unsafe(no_mangle)]
static __AX_TASK_EXT_SIZE: usize = ::core::mem::size_of::<TaskExt>();
#[unsafe(no_mangle)]
static __AX_TASK_EXT_ALIGN: usize = ::core::mem::align_of::<TaskExt>();

pub trait TaskExtRef<T: Sized> {
    /// Get a reference to the task extended data.
    fn task_ext(&self) -> &T;
}

impl ::axtask::TaskExtRef<TaskExt> for ::axtask::TaskInner {
    fn task_ext(&self) -> &TaskExt {
        unsafe {
            let ptr = self.task_ext_ptr() as *const TaskExt;
            if !!ptr.is_null() {
                ::core::panicking::panic("assertion failed: !ptr.is_null()")
            };
            &*ptr
        }
    }
}

// arceos/modules/axtask/src/task.rs
impl TaskInner {
        /// Returns the pointer to the user-defined task extended data.
    ///
    /// # Safety
    ///
    /// The caller should not access the pointer directly, use [`TaskExtRef::task_ext`]
    /// or [`TaskExtMut::task_ext_mut`] instead.
    ///
    /// [`TaskExtRef::task_ext`]: crate::task_ext::TaskExtRef::task_ext
    /// [`TaskExtMut::task_ext_mut`]: crate::task_ext::TaskExtMut::task_ext_mut
    pub unsafe fn task_ext_ptr(&self) -> *mut u8 {
        self.task_ext.as_ptr()
    }

    /// Initialize the user-defined task extended data.
    ///
    /// Returns a reference to the task extended data if it has not been
    /// initialized yet (empty), otherwise returns [`None`].
    pub fn init_task_ext<T: Sized>(&mut self, data: T) -> Option<&T> {
        if self.task_ext.is_empty() {
            self.task_ext.write(data).map(|data| &*data)
        } else {
            None
        }
    }
}

}

TaskExt in axvisor

#![allow(unused)]
fn main() {
// axvisor/src/task.rs
use std::os::arceos::modules::axtask::def_task_ext;

use crate::vmm::{VCpuRef, VMRef};

/// Task extended data for the hypervisor.
pub struct TaskExt {
    /// The VM.
    pub vm: VMRef,
    /// The virtual memory address space.
    pub vcpu: VCpuRef,
}

impl TaskExt {
    pub const fn new(vm: VMRef, vcpu: VCpuRef) -> Self {
        Self { vm, vcpu }
    }
}

def_task_ext!(TaskExt);

// axvisor/src/vmm/vcpus.rs
fn alloc_vcpu_task(vm: VMRef, vcpu: VCpuRef) -> AxTaskRef {
    let mut vcpu_task = TaskInner::new(
        vcpu_run,
        format!("VM[{}]-VCpu[{}]", vm.id(), vcpu.id()),
        KERNEL_STACK_SIZE,
    );
    // ...
    vcpu_task.init_task_ext(TaskExt::new(vm, vcpu));
    axtask::spawn_task(vcpu_task)
}
}

irq & timer

External Interrupt

所有来自外部设备的中断都通过多层次的 VM-Exit 处理例程返回给 axvisor 进行处理。因为 只有 axvisor 拥有全局资源管理视角。

axvisor 根据中断号和虚拟机配置文件识别外部中断:

  • 如果中断是预留给 axvisor 的(例如 axvisor 自己的时钟中断),则由 axhal 提供的 ArceOS 中断处理例程来处理。

  • 如果中断属于某个客户虚拟机(例如客户虚拟机的直通磁盘中断),则该中断会直接注入到对应的虚拟机。

    • 请注意,一些架构的中断控制器可以配置为在不经过 VM-Exit 的情况下直接将外部中断注入到虚拟机中(例如 x86 提供的已发布中断)

Timer

草拟设计请参考 discussion#36

Copyright © 2025 • Created by ArceOS Team

Axvisor API:设计的思考与妥协

1. 为什么需要 Axvisor API?

在 Axvisor 的整体架构中,ArceOS 处于最底层,负责提供内存管理、任务调度、设备驱动、同步原语等多种基础功能;这些功能会被 Axvisor 的各个组件所使用。然而,从软件工程的角度上,我们不能让 Axvisor 的各个组件直接依赖于 ArceOS (的 axstd 等接口组件);这一方面是因为我们希望 ArceOS 与 Axvisor 之间的耦合度尽可能的低,这样可以提高系统的可移植性、可扩展性和可维护性,另一方面则是因为,将对 ArceOS 的依赖分散在各个组件中,会使得依赖和 feature 管理变得极度混乱,容易出现各种错误。

因此,我们需要一个统一的接口,收拢对 ArceOS 的依赖,同时提供给 Axvisor 的各个组件使用。在这里也存在着两个选择,第一是这个接口层放置在所有组件的下层,ArceOS 则被接口层直接依赖,位于最底层;第二是这个接口层同样放置在所有组件的下层,但是 ArceOS 与接口层之间并不直接依赖,接口层只提供接口的定义,实现则由最上层的 app 层来完成。显然,第二种方案在耦合度和可移植性上更有优势,因此我们选择了第二种方案。

2. Axvisor API 应该如何实现

如何实现这样的定义与实现分离的接口呢?我们有很多可行的方案。

第一种,也是目前所广泛使用的方案,是在下层定义一个 trait,上层实现这个 trait,在需要使用这个 trait 的地方,通过泛型参数来传递这个 trait。例如:

#![allow(unused)]
fn main() {
// 下层定义
trait MemoryHal {
    fn alloc() -> u64;
    fn dealloc(addr: u64);
    fn phys_to_virt(phys: u64) -> u64;
    fn virt_to_phys(virt: u64) -> u64;
}

// 中层使用
struct AxVCpu<M: MemoryHal> {
    // ...
}

impl<M: MemoryHal> AxVCpu<M> {
    fn some_func(&self) {
        let addr = M::alloc();
        // ...
        M::dealloc(addr);
    }
}

// 上层实现
struct MemoryHalImpl;

impl MemoryHal for MemoryHalImpl {
    fn alloc() -> u64 {
        // ...
    }

    fn dealloc(addr: u64) {
        // ...
    }

    fn phys_to_virt(phys: u64) -> u64 {
        // ...
    }

    fn virt_to_phys(virt: u64) -> u64 {
        // ...
    }
}
}

这种方案的优点是简单易懂,并且编译器有着非常充分的信息,可以进行很好的优化;实现时也可以很清楚的知道哪些接口是必须实现的。然而,这种方案也有着明显的缺点,那就是,具体的实现必须通过泛型参数一层一层地传递下去,一旦某一个较为下层的组件需要使用一个接口,那么这个接口就必须在所有的中间层都写一遍,这会使得代码的可读性和可维护性变得较差。

另一种方案则是贾越凯学长所实现的 crate_interface 方案。这个方案在链接时通过符号将接口的定义和实现连接起来,通过特殊定义的数个宏,消除了对泛型参数的依赖。

#![allow(unused)]
fn main() {
// 下层定义
#[def_interface]
trait MemoryHal {
    fn alloc() -> u64;
    fn dealloc(addr: u64);
    fn phys_to_virt(phys: u64) -> u64;
    fn virt_to_phys(virt: u64) -> u64;
}

// 中层使用
struct AxVCpu {
    // ...
}

impl AxVCpu {
    fn some_func(&self) {
        let addr = call_interface!(MemoryHal::alloc);
        // ...
        call_interface!(MemoryHal::dealloc, addr);
    }
}

// 上层实现
struct MemoryHalImpl;

#[impl_interface]
impl MemoryHal for MemoryHalImpl {
    fn alloc() -> u64 {
        // ...
    }

    fn dealloc(addr: u64) {
        // ...
    }

    fn phys_to_virt(phys: u64) -> u64 {
        // ...
    }

    fn virt_to_phys(virt: u64) -> u64 {
        // ...
    }
}
}

相比于通过泛型参数进行依赖注入的方案,crate_interface 方案的优点在于无需写出泛型参数,代码更加简洁;同时保留了 trait 的定义,能够明确地知道哪些接口是必须实现的。这个方案的缺点在于,调用接口时需要通过宏,这会使得代码的可读性变差;同时,由于接口的实现是通过符号链接的,因此在编译时会有一些限制,例如无法在一个 crate 中同时实现两个相同的接口,不过考虑到我们的需求(调用 ArceOS 的系统功能),这个限制并不会对我们造成太大的困扰。

3. Axvisor API 的设计与妥协

目前 axvisor_api crate 中使用了一种改良的定义接口的方式,即使用 mod 组织 API,使用标注在 mod 上的 #[api_mod]#[api_mod_impl] 宏来定义和实现接口。示例如下:

#![allow(unused)]
fn main() {
// 下层定义
#[api_mod]
mod memory {
    type PhysAddr = u64;
    type VirtAddr = u64;

    extern fn alloc() -> VirtAddr;
    extern fn dealloc(addr: VirtAddr);
    extern fn phys_to_virt(phys: PhysAddr) -> VirtAddr;
    extern fn virt_to_phys(virt: VirtAddr) -> PhysAddr;

    fn alloc_2_pages() -> (VirtAddr, VirtAddr) {
        let addr = alloc();
        let addr2 = alloc();
        (addr, addr2)
    }
}

// 中层使用
struct AxVCpu {
    // ...
}

impl AxVCpu {
    fn some_func(&self) {
        let addr = memory::alloc();
        // ...
        memory::dealloc(addr);
    }
}

// 上层实现
#[api_mod_impl(path::to::memory)]
mod memory_impl {
    extern fn alloc() -> memory::VirtAddr {
        // ...
    }

    extern fn dealloc(addr: memory::VirtAddr) {
        // ...
    }

    extern fn phys_to_virt(phys: memory::PhysAddr) -> memory::VirtAddr {
        // ...
    }

    extern fn virt_to_phys(virt: memory::VirtAddr) -> memory::PhysAddr {
        // ...
    }
}
}

这种实现方式的优点在于:

  1. 组织更加接近于普通的 Rust 模块组织,易于理解;调用接口也使用普通的函数调用语法,对调用者心智负担较小;
  2. 接口模块中可以定义一些辅助和工具内容,方便实用,例如 API 相关的类型别名,基于 API 的简单封装等;

但是这种实现方式也有一些缺点:

  1. 不容易一次性看出有哪些接口是必须实现的;

实际上,这两个宏背后使用了 crate_interface crate 作为底层实现;虽然技术上可以绕过 crate_interface,只使用 link_name 来实现接口,但这样就会完全失去使用 trait 来约束必须实现的接口的优势。而现在虽然使用了 trait,但是视觉上仍然不容易看出哪些接口是必须实现的;针对这一点,api_mod 宏现在会自动在文档中生成一个列表,列出所有的接口,方便查看。

  1. #[api_mod] 标注的 mod 中,允许所有能够出现在普通 mod 中的内容,包括 structenumconst 等,这样可能会使得接口模块变得过于臃肿,不易维护;

这一点可以通过约定来解决,例如只在接口模块中定义接口相关的内容,其他内容放在其他模块中。

目前,经过与贾越凯、胡柯洋等同学的讨论,我们认为可以先在使用现有泛型参数传递方式极为不便的情况下,使用 axvisor_api crate 来实现 API,以评估其实际表现和可用性;如果在实际使用中发现了问题,再考虑是否需要进一步改进。

Copyright © 2025 • Created by ArceOS Team

AxVM: resource management within each VM

WIP 🚧

  • address space of guest VM
  • axvcpu list
  • axdevice list
#![allow(unused)]
fn main() {
/// A Virtual Machine.
pub struct AxVM<H: AxVMHal, U: AxVCpuHal> {
    running: AtomicBool,
    inner_const: AxVMInnerConst<U>,
    inner_mut: AxVMInnerMut<H>,
}

struct AxVMInnerConst<U: AxVCpuHal> {
    id: usize,
    config: AxVMConfig,
    vcpu_list: Box<[AxVCpuRef<U>]>,
    devices: AxVmDevices,
}

struct AxVMInnerMut<H: AxVMHal> {
    // Todo: use more efficient lock.
    address_space: Mutex<AddrSpace<H::PagingHandler>>,
    _marker: core::marker::PhantomData<H>,
}
}

Copyright © 2025 • Created by ArceOS Team

AxVCpu: Virtual CPU interface and wrapper for ArceOS.

  • axvcpu 提供 CPU 虚拟化支持
    • 高度依赖于架构
    • 存储不同架构的异常上下文框架
    • 基本调度项
    • 特定架构的 vCPU 实现需要被分离到独立的 crate 中:
#![allow(unused)]
fn main() {
/// A trait for architecture-specific vcpu.
///
/// This trait is an abstraction for virtual CPUs of different architectures.
pub trait AxArchVCpu: Sized {
    /// The configuration for creating a new [`AxArchVCpu`]. Used by [`AxArchVCpu::new`].
    type CreateConfig;
    /// The configuration for setting up a created [`AxArchVCpu`]. Used by [`AxArchVCpu::setup`].
    type SetupConfig;

    /// Create a new `AxArchVCpu`.
    fn new(config: Self::CreateConfig) -> AxResult<Self>;

    /// Set the entry point of the vcpu.
    ///
    /// It's guaranteed that this function is called only once, before [`AxArchVCpu::setup`] being called.
    fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult;

    /// Set the EPT root of the vcpu.
    ///
    /// It's guaranteed that this function is called only once, before [`AxArchVCpu::setup`] being called.
    fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult;

    /// Setup the vcpu.
    ///
    /// It's guaranteed that this function is called only once, after [`AxArchVCpu::set_entry`] and [`AxArchVCpu::set_ept_root`] being called.
    fn setup(&mut self, config: Self::SetupConfig) -> AxResult;

    /// Run the vcpu until a vm-exit occurs.
    fn run(&mut self) -> AxResult<AxVCpuExitReason>;

    /// Bind the vcpu to the current physical CPU.
    fn bind(&mut self) -> AxResult;

    /// Unbind the vcpu from the current physical CPU.
    fn unbind(&mut self) -> AxResult;

    /// Set the value of a general-purpose register according to the given index.
    fn set_gpr(&mut self, reg: usize, val: usize);
}
```<style>
  .scroll-to-top {
    font-size: 2.5rem;
    width: 3.2rem;
    height: 3.2rem;
    display: none;
    align-items: center;
    justify-content: center;
    position: fixed;
    padding: 0.75rem;
    bottom: 4rem;
    right: calc(1.25rem + 90px + var(--page-padding));
    z-index: 999;
    cursor: pointer;
    border: none;
    color: var(--bg);
    background: var(--fg);
    border-radius: 50%;
  }
  .scroll-to-top.hidden {
    display: none;
  }
  .scroll-to-top i {
    transform: translateY(-2px);
  }
  @media (min-width: 1080px) {
    .scroll-to-top {
      display: flex;
    }
  }
</style>
<button type="button" aria-label="scroll-to-top" class="scroll-to-top hidden" onclick="scrollToTop()">
  <i class="fa fa-angle-up"></i>
</button>
<script>
  const scrollToTop = () => window.scroll({ top: 0, behavior: "smooth" });
  window.addEventListener("scroll", () => {
    const button = document.querySelector(".scroll-to-top");
    button.classList.toggle("hidden", window.scrollY < 200);
  });
</script>
<style>
  .announcement-banner {
    z-index: 150;
    color: var(--fg);
    position: relative;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 0;
    padding: 1rem 3.5rem;
    background: repeating-linear-gradient(
      45deg,
      var(--site-announcement-bar-stripe-color1),
      var(--site-announcement-bar-stripe-color1) 20px,
      var(--site-announcement-bar-stripe-color2) 10px,
      var(--site-announcement-bar-stripe-color2) 40px
    );
  }
  .announcement-banner {
    --site-announcement-bar-stripe-color1: #e5e7eb;
    --site-announcement-bar-stripe-color2: #d1d5db;
  }
  .announcement-banner[data-theme="ocean"] {
    --site-announcement-bar-stripe-color1: #86b2f9;
    --site-announcement-bar-stripe-color2: #7298ea;
  }
  .announcement-banner[data-theme="forest"] {
    --site-announcement-bar-stripe-color1: #97f5d6;
    --site-announcement-bar-stripe-color2: #6de0bf;
  }
  .announcement-banner[data-theme="lava"] {
    --site-announcement-bar-stripe-color1: #fea3a3;
    --site-announcement-bar-stripe-color2: #e57e7e;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner {
    --site-announcement-bar-stripe-color1: #1f2937;
    --site-announcement-bar-stripe-color2: #111827;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="ocean"] {
    --site-announcement-bar-stripe-color1: #2563eb;
    --site-announcement-bar-stripe-color2: #1d4ed8;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="forest"] {
    --site-announcement-bar-stripe-color1: #22d3a5;
    --site-announcement-bar-stripe-color2: #0fbf8f;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="lava"] {
    --site-announcement-bar-stripe-color1: #f87171;
    --site-announcement-bar-stripe-color2: #ef4444;
  }
  .announcement-banner p {
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    text-align: center;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-wrap: balance;
  }
  .announcement-banner button {
    top: 50%;
    right: 1rem;
    position: absolute;
    transform: translateY(-50%);
    width: 3rem;
    height: 3rem;
    cursor: pointer !important;
    border: none;
    font-weight: 900;
    border-radius: 50%;
    background-color: transparent;
  }
</style>
<div style="display: none" data-id="0.2.11" class="announcement-banner" data-theme="default">
  <p><em>正在逐步完善中。。。</em></p>

  <button type="button">X</button>
</div>
<script>
  (() => {
    const banner = document.querySelector(".announcement-banner");
    const id = banner.getAttribute("data-id");
    const message = banner.querySelector("p").textContent;
    const localData = JSON.parse(localStorage.getItem("mdbook-announcement-banner"));
    if (!localData || localData.id !== id || localData.hide !== true) {
      banner.style.display = "flex";
      const page = document.querySelector(".page");
      page.parentNode.insertBefore(banner, page);
      banner.querySelector("button").addEventListener("click", () => {
        banner.remove();
        localStorage.setItem("mdbook-announcement-banner", JSON.stringify({ id, hide: true, message }));
      });
    }
  })();
</script>
<style>
  .giscus {
    margin-top: 6rem;
  }
</style>
<script
  src="https://giscus.app/client.js"
  data-repo="arceos-hypervisor/doc"
  data-repo-id="R_kgDOLMHfvQ"
  data-category="Comments"
  data-category-id="DIC_kwDOLMHfvc4CoqAB"
  data-mapping="title"
  data-strict="0"
  data-reactions-enabled="1"
  data-emit-metadata="0"
  data-input-position="bottom"
  data-theme="light"
  data-lang="zh-CN"
  data-loading="eager"
  crossorigin="anonymous"
  async
></script>
<script>
  (() => {
    const giscusScript = document.querySelector("script[data-repo][data-repo-id]");
    if (giscusScript?.getAttribute("data-theme") !== "book") return;
    const mapTheme = (theme) => (theme === "light" || theme === "rust" ? "light" : "dark");
    const bookTheme = localStorage.getItem("mdbook-theme") || html.getAttribute("class");
    giscusScript.setAttribute("data-theme", mapTheme(bookTheme));
    document.querySelectorAll("button[role='menuitem'].theme").forEach((btn) => {
      btn.addEventListener("click", (event) => {
        theme = mapTheme(event.target.id);
        const iframe = document.querySelector("iframe.giscus-frame");
        if (iframe) iframe.contentWindow.postMessage({ giscus: { setConfig: { theme } } }, "*");
      });
    });
  })();
</script>
<style>
  footer {
    text-align: center;
    text-wrap: balance;
    margin-top: 5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
  footer p {
    margin: 0;
  }
</style>
<footer><p>Copyright © 2025 • Created by ArceOS Team</p>
</footer>
}

x86_vcpu

介绍

定义 x86_64 架构的 vCPU 结构和虚拟化相关接口支持。

crate 用户必须使用 crate_interface::impl_interface 实现 PhysFrameIf trait,以提供 PhysFrame 分配和释放的低级实现,相关实现可参考 ArceOS

Example

use x86_vcpu::PhysFrameIf;

struct PhysFrameIfImpl;

#[crate_interface::impl_interface]
impl axvm::PhysFrameIf for PhysFrameIfImpl {
    fn alloc_frame() -> Option<PhysAddr> {
        // Your implementation here
    }
    fn dealloc_frame(paddr: PhysAddr) {
        // Your implementation here
    }
    fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
        // Your implementation here
    }
}

系统架构

模块组织

x86_vcpu/
    ├── src/
    │   ├── msr.rs          - 模型特定寄存器操作
    │   ├── regs.rs         - 通用寄存器管理
    │   ├── ept.rs          - 扩展页表支持
    │   ├── frame.rs        - 物理内存帧管理
    │   ├── vmx/            - Intel VT-x 相关实现
    │   │   ├── definitions.rs  - 常量和类型定义
    │   │   ├── instructions.rs - VMX 指令封装
    │   │   ├── percpu.rs       - 每 CPU 状态管理
    │   │   ├── structs.rs      - VMX 数据结构
    │   │   ├── vcpu.rs         - 虚拟 CPU 实现
    │   │   ├── vmcs.rs         - VMCS 字段操作
    │   │   └── mod.rs          - 模块入口
    │   └── lib.rs          - 库入口点

核心组件

  • VmxVcpu: 虚拟 CPU 实现,管理客户机状态和执行
  • VmxPerCpuState: 每物理 CPU 的 VMX 状态
  • VMCS 管理: 虚拟机控制结构字段的读写操作
  • EPT 控制: 扩展页表配置和违规处理
  • 物理内存管理: 通过 PhysFrame 抽象管理物理内存

关键数据结构

GeneralRegisters

用于存储和操作 X86_64 通用寄存器状态:

#![allow(unused)]
fn main() {
#[repr(C)]
pub struct GeneralRegisters {
    pub rax: u64,
    pub rcx: u64,
    // ... 其他寄存器
}
}

提供按索引访问和修改寄存器值的方法:

  • get_reg_of_index(index: u8) -> u64
  • set_reg_of_index(index: u8, value: u64)

VmxVcpu

虚拟 CPU 的核心实现:

pub struct VmxVcpu<H: AxVCpuHal> {
    guest_regs: GeneralRegisters,
    host_stack_top: u64,
    launched: bool,
    vmcs: VmxRegion<H>,
    io_bitmap: IOBitmap<H>,
    msr_bitmap: MsrBitmap<H>,
    // ... 其他字段
}

GuestPageWalkInfo

存储客户机页表遍历所需的信息:

pub struct GuestPageWalkInfo {
    pub top_entry: usize,
    pub level: usize,
    pub width: u32,
    // ... 权限和控制位
}

PhysFrame

物理内存页面的安全抽象:

pub struct PhysFrame<H: AxVCpuHal> {
    start_paddr: Option<HostPhysAddr>,
    _marker: PhantomData<H>,
}

核心功能实现

VCPU 生命周期管理

// 创建新的虚拟 CPU
pub fn new() -> AxResult<Self> { ... }

// 配置虚拟 CPU
pub fn setup(&mut self, ept_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { ... }

// 绑定到当前物理 CPU
pub fn bind_to_current_processor(&self) -> AxResult { ... }

// 执行客户机代码
pub fn inner_run(&mut self) -> Option<VmxExitInfo> { ... }

VMCS 设置

fn setup_vmcs_guest(&mut self, entry: GuestPhysAddr) -> AxResult { ... }

fn setup_vmcs_host(&self) -> AxResult { ... }

fn setup_vmcs_control(&mut self, ept_root: HostPhysAddr, is_guest: bool) -> AxResult { ... }

VM 进入/退出处理

#[naked]
unsafe extern "C" fn vmx_launch(&mut self) -> usize { ... }

#[naked]
unsafe extern "C" fn vmx_resume(&mut self) -> usize { ... }

fn builtin_vmexit_handler(&mut self, exit_info: &VmxExitInfo) -> Option<AxResult> { ... }

事件注入

/// Add a virtual interrupt or exception to the pending events list,
/// and try to inject it before later VM entries.
pub fn queue_event(&mut self, vector: u8, err_code: Option<u32>) { ... }

/// Try to inject a pending event before next VM entry.
fn inject_pending_events(&mut self) -> AxResult { ... }

I/O 和 MSR 拦截

/// Set I/O intercept by modifying I/O bitmap.
pub fn set_io_intercept_of_range(&mut self, port_base: u32, count: u32, intercept: bool) { ... }

/// Set msr intercept by modifying msr bitmap.
pub fn set_msr_intercept_of_range(&mut self, msr: u32, intercept: bool) { ... }

关键技术

VMX 操作模式

实现了完整的 VMX 操作模式切换:

  • 通过 VMXON 指令进入 VMX 操作模式
  • 通过 VMLAUNCHVMRESUME 指令执行客户机代码
  • 通过 VM 退出处理程序响应客户机事件

嵌套分页 (EPT)

使用扩展页表实现高效内存虚拟化:

  • 配置 EPT 指针 (EPTP)
  • 处理 EPT 违规事件
  • 支持内存访问权限控制

寄存器状态切换

通过X86汇编代码实现状态切换:

  • save_regs_to_stack! 宏保存寄存器状态到堆栈
  • restore_regs_from_stack! 宏从堆栈恢复寄存器状态
  • 特殊处理栈指针 (RSP) 以确保正确的状态切换

指令模拟

为特定指令提供模拟实现:

  • CPUID 指令模拟,提供自定义处理器信息
  • XSETBV 指令处理,管理扩展状态
  • CR 寄存器访问处理

内存管理

物理内存分配

通过 PhysFrame 抽象提供安全的物理内存管理:

  • 自动释放不再使用的物理页面
  • 支持零填充和自定义初始化
  • 提供物理地址到虚拟地址的转换

EPT 页表管理

实现二级地址转换机制:

  • 创建和管理 EPT 页表结构
  • 支持不同的页面粒度 (4KB, 2MB, 1GB)
  • 处理页面权限和访问控制

Copyright © 2025 • Created by ArceOS Team

arm_vcpu

ARM 虚拟化简介

ARM 的硬件辅助虚拟化技术叫做 ARM-V (Virtualization) 技术,从 ARMv8 开始比较好的支持这种硬件辅助虚拟化技术。类似 X86 根/非根模式,ARM 引入多个异常级来控制资源访问权限。宿主机上 VMM 运行在 EL2 层,客户机操作系统运行在 EL1 层,应用程序运行在 EL0 层。

ARMv8 的异常级分为 4 级(EL0~EL3),权限也是从低到高。同时也分了两个 CPU 运行状态(Non-secure 和 Secure),顾名思义为了安全和非安全的运行态,此处不扩展。每一级运行的内容如下图所示:

运行在不同异常级最大的区别就是能够访问的寄存器组不同,同时也影响了某些硬件行为(比如对页表的 lookup)。运行在高异常级时可以访问低异常级的寄存器组,反之不行。 为了切换到更高的异常级(主动触发某个异常级的异常),需要主动执行特殊的指令: • Supervisor Call (SVC)。一般由 EL0 切换到 EL1 的指令,会进入 EL1 的异常向量表。 • Hypervisor Call (HVC) 。Non-secure EL1 切换到 EL2 的指令,会进入到 EL2 的异常向量表。 • Secure monitor Call (SMC) 。切换到 EL3 的指令,只有在 EL1、EL2 执行有效。

项目概述

arm_vcpu 是 axvisor 项目的重要组成部分,特别为 ARM 架构设计的虚拟 CPU 实现。该项目提供了在 ARM64 架构上实现虚拟机(VM)的底层组件,使操作系统或 hypervisor 能够创建和管理虚拟 CPU,进而运行客户操作系统。

核心组件结构

主要模块

项目包含以下核心模块:

  • context_frame: 定义了 ARM64 CPU 上下文帧结构
  • exception: 处理异常和 VM 退出
  • exception_utils: 提供异常处理的工具函数
  • pcpu: 物理 CPU 相关功能的实现
  • smc: 安全监视器调用实现
  • vcpu: 虚拟 CPU 的核心实现

核心数据结构

Aarch64ContextFrame (src/context_frame.rs)

#![allow(unused)]
fn main() {
pub struct Aarch64ContextFrame {
    pub gpr: [u64; 31],      // 通用寄存器
    pub sp_el0: u64,         // EL0 栈指针
    pub elr: u64,            // 异常链接寄存器
    pub spsr: u64,           // 保存的程序状态寄存器
}
}

这个结构体代表了一个完整的 ARM64 CPU 上下文,包含了 CPU 的通用寄存器、栈指针、返回地址和状态标志。当发生 VM 切换时,这些寄存器需要保存和恢复。

GuestSystemRegisters (src/context_frame.rs)

#![allow(unused)]
fn main() {
pub struct GuestSystemRegisters {
    // 通用定时器相关寄存器
    pub cntvoff_el2: u64,
    cntp_cval_el0: u64,
    ...
    // 虚拟ID寄存器
    vpidr_el2: u32,
    pub vmpidr_el2: u64,

    // EL1/EL0 寄存器
    pub sp_el0: u64,
    sp_el1: u64,
    ...

    // Hypervisor上下文
    pub hcr_el2: u64,
    pub vttbr_el2: u64,
    ...
}
}

这个结构体包含了客户虚拟机的系统寄存器状态,包括定时器寄存器、CPU ID、异常控制、内存管理等。这些寄存器在 VM 进入/退出时需要保存和恢复。

Aarch64VCpu (src/vcpu.rs)

#![allow(unused)]
fn main() {
pub struct Aarch64VCpu<H: AxVCpuHal> {
    ctx: TrapFrame,
    host_stack_top: u64,
    guest_system_regs: GuestSystemRegisters,
    mpidr: u64,
    _phantom: PhantomData<H>,
}
}

Aarch64VCpu 是实现虚拟 CPU 的核心结构体,包含了虚拟 CPU 的完整状态:

  • ctx: 保存客户 VM 的 CPU 上下文
  • host_stack_top: 主机栈顶指针,用于 VM 退出时恢复主机上下文
  • guest_system_regs: 客户系统寄存器状态
  • mpidr: 多处理器 ID 寄存器值

关键功能实现

VCPU 初始化和运行

VCPU 的初始化在 Aarch64VCpu::new() 方法中实现,设置初始化 CPU 寄存器和系统状态:

#![allow(unused)]
fn main() {
fn new(config: Self::CreateConfig) -> AxResult<Self> {
    let mut ctx = TrapFrame::default();
    ctx.set_argument(config.dtb_addr);

    Ok(Self {
        ctx,
        host_stack_top: 0,
        guest_system_regs: GuestSystemRegisters::default(),
        mpidr: config.mpidr_el1,
        _phantom: PhantomData,
    })
}
}

VCPU 的运行在 run() 方法中实现:

#![allow(unused)]
fn main() {
fn run(&mut self) -> AxResult<AxVCpuExitReason> {
    let exit_reson = unsafe {
        // 保存主机 SP_EL0 到上下文中
        save_host_sp_el0();
        self.restore_vm_system_regs();
        self.run_guest()
    };

    let trap_kind = TrapKind::try_from(exit_reson as u8).expect("Invalid TrapKind");
    self.vmexit_handler(trap_kind)
}
}

异常处理机制

异常处理是通过异常向量表和处理函数实现的。异常向量表在 exception.S 中定义:

.section .text
.p2align 11
.global exception_vector_base_vcpu
exception_vector_base_vcpu:
    // current EL, with SP_EL0
    INVALID_EXCP_EL2 0 0
    ...
    // lower EL, aarch64
    HANDLE_LOWER_SYNC_VCPU
    HANDLE_LOWER_IRQ_VCPU
    ...

同步异常处理在 handle_exception_sync 函数中实现:

#![allow(unused)]
fn main() {
pub fn handle_exception_sync(ctx: &mut TrapFrame) -> AxResult<AxVCpuExitReason> {
    match exception_class() {
        Some(ESR_EL2::EC::Value::DataAbortLowerEL) => {
            let elr = ctx.exception_pc();
            let val = elr + exception_next_instruction_step();
            ctx.set_exception_pc(val);
            handle_data_abort(ctx)
        }
        Some(ESR_EL2::EC::Value::HVC64) => {
            // 处理超级调用
            ...
        }
        ...
    }
}
}

VM 进入/退出流程

VM 进入是通过 run_guest() 函数实现的:

#![allow(unused)]
fn main() {
unsafe fn run_guest(&mut self) -> usize {
    unsafe {
        core::arch::asm!(
            // 保存主机上下文
            save_regs_to_stack!(),
            "mov x9, sp",
            "mov x10, x11",
            // 保存当前主机栈顶到 Aarch64VCpu 结构中
            "str x9, [x10]",
            "mov x0, x11",
            "b context_vm_entry",
            in("x11") &self.host_stack_top as *const _ as usize,
            options(nostack)
        );
    }

    // 返回值,实际返回值是在 x0 中,当 return_run_guest 返回时
    0
}
}

VM 退出是通过 vmexit_handler() 函数处理的:

#![allow(unused)]
fn main() {
fn vmexit_handler(&mut self, exit_reason: TrapKind) -> AxResult<AxVCpuExitReason> {
    unsafe {
        // 保存客户系统寄存器
        self.guest_system_regs.store();

        // 保存客户 SP_EL0 到 Aarch64VCpu 结构中
        self.ctx.sp_el0 = self.guest_system_regs.sp_el0;

        // 恢复主机的 SP_EL0
        restore_host_sp_el0();
    }

    match exit_reason {
        TrapKind::Synchronous => handle_exception_sync(&mut self.ctx),
        TrapKind::Irq => Ok(AxVCpuExitReason::ExternalInterrupt {
            vector: H::irq_fetch() as _,
        }),
        _ => panic!("Unhandled exception {:?}", exit_reason),
    }
}
}

物理 CPU 初始化

物理 CPU 初始化在 Aarch64PerCpu::new()hardware_enable() 方法中实现:

#![allow(unused)]
fn main() {
fn hardware_enable(&mut self) -> AxResult {
    // 首先保存原始异常向量表基址
    unsafe { ORI_EXCEPTION_VECTOR_BASE.write_current_raw(VBAR_EL2.get() as usize) }

    // 设置当前 VBAR_EL2 为本 crate 中定义的 exception_vector_base_vcpu
    VBAR_EL2.set(exception_vector_base_vcpu as usize as _);

    // 启用虚拟化功能
    HCR_EL2.modify(
        HCR_EL2::VM::Enable
            + HCR_EL2::RW::EL1IsAarch64
            + HCR_EL2::IMO::EnableVirtualIRQ
            + HCR_EL2::FMO::EnableVirtualFIQ
            + HCR_EL2::TSC::EnableTrapEl1SmcToEl2,
    );

    Ok(())
}
}

关键机制解析

上下文切换机制

上下文切换是虚拟化的核心机制,包括以下步骤:

  1. VM 进入时:

    • 保存主机上下文(通用寄存器、栈指针)
    • 加载客户 VM 上下文和系统寄存器
    • 通过 eret 指令切换到 VM 执行
  2. VM 退出时:

    • 通过异常向量捕获退出事件
    • 保存客户 VM 上下文和系统寄存器
    • 恢复主机上下文
    • 返回 VM 退出原因

内存管理

虚拟 CPU 的内存管理主要通过 EPT (Extended Page Table) 或 ARM 中的第二阶段地址转换实现:

#![allow(unused)]
fn main() {
fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult {
    debug!("set vcpu ept root:{:#x}", ept_root);
    self.guest_system_regs.vttbr_el2 = ept_root.as_usize() as u64;
    Ok(())
}
}

内存访问异常由 handle_data_abort 处理:

#![allow(unused)]
fn main() {
fn handle_data_abort(context_frame: &mut TrapFrame) -> AxResult<AxVCpuExitReason> {
    let addr = exception_fault_addr()?;
    let access_width = exception_data_abort_access_width();
    let is_write = exception_data_abort_access_is_write();
    let reg = exception_data_abort_access_reg();
    let reg_width = exception_data_abort_access_reg_width();

    // ...

    if is_write {
        return Ok(AxVCpuExitReason::MmioWrite {
            addr,
            width,
            data: context_frame.gpr(reg) as u64,
        });
    }
    Ok(AxVCpuExitReason::MmioRead {
        addr,
        width,
        reg,
        reg_width,
    })
}
}

中断和异常处理

中断和异常处理通过异常向量表和对应的处理函数实现。系统提供了以下主要异常处理路径:

  1. 同步异常处理:处理客户 VM 的指令执行异常
  2. 中断处理:处理物理中断并将其路由到适当的目标(主机或客户 VM)
  3. SMC 调用处理:安全监控器调用的处理

附 1:参考资料

ARM 官方虚拟化手册:

Copyright © 2025 • Created by ArceOS Team

概述

本节介绍在RISC-V体系结构中,与CPU虚拟化相关的基本知识、原理和设计。

体系结构的虚拟化扩展 'H'

与x86等体系架构类似,RISC-V也采取了硬件辅助虚拟化的方式,以提升虚拟化效率,这种方式表现为对体系结构的一个扩展,代号'H'。

image-20250314151758837
图1:RISC-V扩展的官方示意图

上图来自RISC-V官方文档,可以看出,虚拟化扩展'H' 在原有的非虚拟化世界基础上创造了一个平行的虚拟化世界

虚拟化世界是一个虚拟的计算机环境,它包含了两个特权级,正好可以满足各类主流的客户操作系统(Guest OS)设计与机制上对特权级保护的需要,其中VS用于运行Guest OS的内核,VU用于运行Guest OS支撑的用户态应用。只要不触及敏感指令,整个Guest OS及其之上的应用都只会在这个虚拟世界中运行,如此就保证了较高的执行效率;只有执行到敏感指令,虚拟世界无法提供虚拟化支持时,才会跳回到非虚拟化世界,此时Hypervisor接管系统执行权,处理虚拟世界模式中无法处理的情况后,再返回到虚拟世界中继续运行。

特权级HS是让两个世界相互切换机制得以实现的关键,Hypervisor就运行在这个特权级。该特权级在原有S特权级的基础上增加了一些特性与机制,针对虚拟化,新增了一些寄存器,扩展了某些寄存器的属性位,例如,在一个特殊的寄存器标志位的控制下,HS特权级可以决定是向U还是向VS返回。(后面专门说明该寄存器标志位)

下面为'H'扩展的相关概念和机制给出更准确的描述。

  • 非虚拟化世界 - 基本模式,或简称H-Mode。(在x86体系结构中,称为根模式)

  • 虚拟化世界 - 虚拟模式,或简称V-Mode。(在x86体系结构中,称为非根模式)

按照'H'扩展的设计,模式与特权级之间是正交的关系。图1虽然直观,但未能很好的体现这种正交的关系,它应该是受到了模拟器层次示意图的影响。但Hypervisor与模拟器还是有明确的区别。下面是更准确的图示:

image-20250315113238902
图2:模式与特权级的正交关系

虚拟机是对物理机的“高效”复制,高效主要体现在虚拟机与物理机的“同质性”上,核心是CPU指令集的兼容,让虚拟机需要执行的大多数指令都可以不须翻译过程,而直接运行在物理CPU上。

Guest所在的虚拟机运行在V-Mode模式中,一般的CPU指令直接由物理CPU执行,并不需要Hypervisor这一层次的中介干预,这是主流;只是在触及敏感指令才会切换到H-Mode,由Hypervisor处理。由于触及敏感指令是相对少数的情况,所以上面的正交关系示意图应该是更为合理的表述。

本节只是涉及CPU虚拟化,事实上,对于内存和设备虚拟化来说,如果Hypervisor已经完成了特定物理页帧与Guest的分配关联,如果对特定外设采取了Passthrough的方案,那么Guest在运行中同样是直通物理硬件,而不需要Hypervisor作为中介干预的,Hypervisor在此类情况下,只是提前为Guest做了setup的工作。

CPU虚拟化涉及的关键对象及相互关系

在CPU虚拟化方面涉及的关键对象主要包括vCPU、虚拟机VM、物理CPU、任务Task和运行队列RUN_QUEUE,其中任务Task与运行队列由ArceOS提供,Hypervisor基于对它们的扩展和使用来实现所需的功能,它们的关系如下:

image-20250315195416778

以vCPU为起点进行分析,它是虚拟化世界中逻辑执行的主体。

每个虚拟机VM至少有一个vCPU,称为Primary vCPU,也是Boot vCPU。在此基础上,虚拟机VM可以包含更多的Secondary vCPU。这些从属于同一个虚拟机的vCPU在执行上是相互独立的,它们可以在不同的物理CPU上被同时调度执行。

vCPU实现的基础是ArceOS中的Task,它们可以看作是同一个对象在不同层面各自的表现形式,即在虚拟世界中表现为vCPU,而在ArceOS Hypervisor中的运行形式就是Task。

每个物理CPU有自己的RUN_QUEUE,如果想指定vCPU在某个或某组物理CPU上执行,只要把vCPU对应Task调度到相应的RUN_QUEUE即可。

虚拟机首次启动前的准备

虚拟机VM首次启动前,Hypervisor需要为其检查和准备必要的条件。与CPU相关的核心工作:

  • 检查确认硬件平台支持‘H'扩展

    底层平台必须支持RISC-V的’H'扩展,首先OpenSBI固件在启动时会检查misa寄存器的第7个状态位,如果是1表示支持虚拟化扩展。

    image-20250315232321423

    Hypervisor可以通过查看OpenSBI的输出来确定是否支持。但目前Hypervisor采取的方式是:直接尝试读hgatp,如果读失败,则说明底层平台不支持‘H’扩展。

    OpenSBI可以通过把misa的第7位设置为0,来从固件层面关闭对虚拟化的支持。

  • 设置寄存器hstatus状态,为切换到虚拟化模式做准备

    特权级HS是U特权级切换和VS特权级切换的汇聚点,执行sret进行特权级返回时,通过hstatus的SPV这一位来确定返回的方向。

    SPV这一位的作用是保存进入HS特权级之前的原始模式,如果是0,表示之前是非虚拟化模式,即从U特权级切换而来;如果是1,表示之前是在虚拟化模式中运行,即从VS特权级切换而来。

    image-20250315234025806

    Hypervisor在首次进入虚拟机之前,把hstatus的SPV设置为1,这样将来在执行sret时就能够确保进入到虚拟机中运行。

  • 设置Guest寄存器sepc和sstatus,指定Guest OS 内核最初启动的指令地址和状态

    预先为Guest伪造现场,当虚拟器首次启动时,将从Guest OS内核的入口开始执行。

    image-20250316074320672

准备工作完成后,Hypervisor通过VM-Entry过程首次启动虚拟机(实际是启动Primary vCPU),由此进入一个循环:

image-20250315225843014

虚拟机启动后,Guest OS的内核与用户态应用在虚拟环境中运行,如同在物理机器中运行;触及敏感指令时将触发VM-Exit过程,退出虚拟化模式,控制权返回到Hypervisor,Hypervisor根据退出原因进行相应的处理,然后再次经由VM-Entry过程进入虚拟机,Guest将从上次执行的断点处恢复执行,如同从未被打断执行一样。如此循环往复,直至虚拟机关闭。

VM-Entry: 从Host到Guest

VM-Entry是从Host环境进入Guest环境的过程,在体系结构的层面看,物理CPU需要完成从非虚拟化模式到虚拟化模式的切换。在切换过程中,一些跨模式共享的寄存器就可能遭到破坏。因此,Hypervisor需要在物理层执行切换前,预先保存部分寄存器组的状态到Host上下文中。此外,物理层面的模式切换完成后,会立即基于当时的寄存器状态继续工作,因此还需要在模式切换前,从Guest上下文中恢复相关寄存器到上次离开Guest环境到状态。vCPU代表了虚拟环境中独立的执行流,因此Host和Guest上下文同一由vCPU来维护。

image-20250316074551411

具体到RISC-V体系结构,对于VM-Entry,保存/恢复上下文的工作在_run_guest中完成,最后执行sret指令完成物理CPU的模式切换。过程_run_guest定义在riscv_vcpu/src/trap.S文件中,关键的数据结构和过程如下:

  • RISCV_VCPU保存寄存器上下文的结构,参见riscv_cpu/src/vcpu.rs和riscv_cpu/src/regs.rs

    #![allow(unused)]
    fn main() {
    pub struct RISCVVCpu<H: AxVCpuHal> {
        regs: VmCpuRegisters,
    	... ...
    }
    
    pub struct VmCpuRegisters {
        // CPU state that's shared between our's and the guest's execution environment. Saved/restored
        // when entering/exiting a VM.
        pub hyp_regs: HypervisorCpuState,
        pub guest_regs: GuestCpuState,
    	... ...
    }

    每个RISCV_VCPU实例中通过regs成员保存寄存器状态,具体又分为Host上下文hyp_regs和Guest上下文guest_regs。

  • 向_run_guest传入上下文数据区的起始地址,参见riscv_vcpu/src/vcpu.rs

    RISCV_VCPU的主运行方法run在调用_run_guest时,会传入其regs成员的地址作为参数。

    #![allow(unused)]
    fn main() {
    fn run(&mut self) -> AxResult<AxVCpuExitReason> {
    	... ...
        // Safe to run the guest as it only touches memory assigned to it by being owned
        // by its page table
        _run_guest(&mut self.regs);
    	... ...
    }

    RISCV_VCPU成员regs的地址作为第一个参数,在汇编层面保存在a0寄存器中。

  • 过程_run_guest关于保存Host上下文的实现,参见riscv_vcpu/src/trap.S

    接上面,_run_guest中的a0保存的是RISCV_VCPU的成员regs的开始地址,加上偏移就可以访问到Host/Guest两组上下文中存放各个寄存器的具体位置。

    _run_guest:
        /* Save hypervisor state */
    
        /* Save hypervisor GPRs (except T0-T6 and a0, which is GuestInfo and stashed in sscratch) */
        sd   ra, ({hyp_ra})(a0)
        sd   gp, ({hyp_gp})(a0)
        sd   tp, ({hyp_tp})(a0)
    	... ...
    
  • 过程_run_guest关于恢复Guest上下文的实现

    #![allow(unused)]
    fn main() {
        /* Restore the gprs from this GuestInfo */
        ld   ra, ({guest_ra})(a0)
        ld   gp, ({guest_gp})(a0)
        ld   tp, ({guest_tp})(a0)
    	... ...
    }

    为Guest运行提前准备,把涉及的寄存器状态从上下文中恢复出来。

  • 执行模式切换,进入到虚拟机执行Guest OS

    在前面已经保存和恢复上下文的基础上,通过指令sret完成物理CPU模式的切换,进入到Guest环境中执行。

VM-Exit:从Gust返回Host

VM-Exit是虚拟机无法满足虚拟化条件时,从体系结构硬件发起的、由特殊trap响应函数配合的自动过程。从执行逻辑上看,它基本上是GuestVM-Entry的逆向过程。

image-20250316093606353
  • VM-Exit过程的触发

    VM-Exit过程的触发是物理硬件的基本机制,相当于宏内核模式下,低特权级程序在执行某些特权操作时所触发的trap过程。软件层面Hypervisor需要做的工作就是在上次执行_run_guest时,提前注册响应函数_guest_exit。

        /* Set stvec so that hypervisor resumes after the sret when the guest exits. */
        la    t1, _guest_exit
        csrrw t1, stvec, t1
    	...
    

    对于RISC-V,寄存器stvec保存trap响应函数入口表的起始地址,对于虚拟化导致的trap同样适用。

  • VM-Exit的响应过程

    响应过程主体就是_guest_exit的实现,参见riscv_vcpu/src/trap.S

    .align 2
    _guest_exit:
        /* Pull GuestInfo out of sscratch, swapping with guest's a0 */
        csrrw a0, sscratch, a0
    
        /* Save guest GPRs. */
        sd   ra, ({guest_ra})(a0)
        sd   gp, ({guest_gp})(a0)
        sd   tp, ({guest_tp})(a0)
    	... ...
    	ret
    

    由于逻辑上基本是_run_guest的逆过程,不再赘述。但是需要特别注意该过程的最后一行是ret指令,是普通的函数返回指令,原理是从寄存器ra取出返回地址后跳转。回顾VM-Entry中切换模式进入虚拟机时,执行的是_run_guest,如下:

    #![allow(unused)]
    fn main() {
    fn run(&mut self) -> AxResult<AxVCpuExitReason> {
    	... ...
        // Safe to run the guest as it only touches memory assigned to it by being owned
        // by its page table
        _run_guest(&mut self.regs);
    	... ...	// <- Reg 'ra' points to this line.
    }

    当时寄存器ra保存的就是_run_guest函数的下一行指令代码地址,所以此时执行ret的效果就是返回到该处继续执行。

    从Hypervisor的角度看,它执行_run_guest的效果就如同执行了一次普通的函数调用,但是内部已经经历了一次从进入虚拟机到退出虚拟机的完整周期。

附1:参考资料

RISC-V官方指令手册中,关于‘H’扩展的章节。

riscv-docs/riscv-privileged-20211203.pdf at main · konpoe/riscv-docs

附2:vCPU与物理CPU绑定

如正文所述,Hypervisor基于对ArceOS的扩展实现。vCPU与ArceOS的Task是一体的,因此vCPU与物理CPU的绑定关系是通过Task与物理CPU的绑定关系来实现的。

ArceOS提供了要给cpumask的功能,即通过位图的形式指定Task可以被哪些RUN_QUEUE所执行,RUN_QUEUE与物理CPU是一一对应的关系,由此可以实现vCPU到物理CPU的绑定。

Copyright © 2025 • Created by ArceOS Team

🚧 Coming soon.

Copyright © 2025 • Created by ArceOS Team

axaddrspace

介绍

内存虚拟化由axaddrspace实现,该模块与架构无关,负责管理和映射客户虚拟机的二级地址空间(GPA -> HPA)

系统架构

模块组织

axaddrspace/
    ├── src/
    │   ├── address_space/   
    |   |   ├── backend
    |   |   |    ├── alloc.rs   - 动态分配映射实现
    |   |   |    ├── linear.rs  - 线性映射实现
    |   |   └── ├── mod.rs     - 模块入口
    |   └── ├── mod.rs     - 客户机物理地址空间管理模块
    │   ├── npt/
    │   |   ├── arch/
    │   |   |   ├── aarch64.rs   - ARMv8 架构下嵌套页表管理
    │   |   |   ├── mod.rs       - 模块入口
    │   │   |   ├── riscv.rs     - riscv页表管理
    │   │   └── ├── x86_64.rs    - x86_64 架构扩展页表管理
    │   └── ├── mod.rs       - 模块入口
    │   ├── addr.rs      - 地址类型定义
    │   └── lib.rs       - 库入口点

关键数据结构

客户机物理地址空间结构体

#![allow(unused)]
fn main() {
pub struct AddrSpace<H: PagingHandler> {
  va_range: GuestPhysAddrRange,  //地址空间范围
  areas: MemorySet<Backend<H>>,  //内存区域集合
  pt: PageTable<H>,              //嵌套页表实例
}
}

该结构体使用了一个泛型类型参数,该参数受page_table_multiarch模块中PagingHandler trait的约束。

核心功能实现

功能代码

定义一个统一内存映射后端枚举类型 Backend<H>,用于抽象不同策略的物理内存管理方式。通过泛型参数 H: PagingHandler 实现对不同页表操作接口的兼容,允许后端适配不同架构的页表实现(如x86_64的EPT、ARM的Stage-2页表)。

#![allow(unused)]
fn main() {
pub enum Backend<H: PagingHandler> {
  /// Linear mapping backend.
  Linear {
    /// `vaddr - paddr`.
    pa_va_offset: usize,
  },
  /// Allocation mapping backend.
  Alloc {
    populate: bool,
    _phantom: core::marker::PhantomData<H>,
  },
}
}

当前支持两种内存映射后端策略:

  1. Linear(线性映射)物理地址 = 虚拟地址 - pa_va_offset

  2. Alloc(动态分配映射):通过全局分配器动态获取物理帧,支持两种模式:

    • 预分配模式 (populate=true): 立即分配所有物理帧,无缺页开销
    • 按需分配模式 (populate=false): 延迟分配,通过缺页异常触发分配

文件alloc.rs实现了动态分配映射:

方法功能描述
new_alloc创建后端实例,指定是否预分配
map_alloc建立客户机虚拟地址到动态分配物理帧的映射
unmap_alloc解除映射并释放关联的物理帧
handle_page_fault处理缺页异常,为未映射的地址分配物理帧

文件linear.rs实现了线性映射,功能有:

impl<H: PagingHandler> Backend<H> {
    // 创建线性映射后端实例
    pub const fn new_linear(pa_va_offset: usize) -> Self
    // 执行线性映射
    pub(crate) fn map_linear(...) -> bool 
    // 解除线性映射
    pub(crate) fn unmap_linear(...) -> bool

}

axaddrspace通过使用page_table_multiarch的泛型页表框架和page_table_entry的标准接口定义,实现了支持x86、ARM、RISC-V的嵌套页表管理的多架构兼容。

依赖注入

AddrSpace结构体表示虚拟机的内存区域和二级地址映射,依赖于泛型类型PagingHandler处理页表相关操作。axaddrspaceaxvmAxVM结构体拥有和管理,而AxVM依赖于定义在axvmhal.rs中的AxVMHal trait。

实际上,PagingHandlerAxVMHal trait的一个关联类型:

// 底层软件(内核或虚拟机监控程序)必须实现的接口
pub trait AxVMHal: Sized {
    type PagingHandler: page_table_multiarch::PagingHandler;
    // 将虚拟地址转换为对应的物理地址
    fn virt_to_phys(vaddr: HostVirtAddr) -> HostPhysAddr;
    // 获取当前时间(纳秒)
    fn current_time_nanos() -> u64;
	// ...
}

AxVMHalvmm-app中的AxVMHalImpl实现,其关联类型PagingHandler依赖于ArceOS的axhal模块提供的PagingHandlerImpl

pub struct AxVMHalImpl;

impl AxVMHal for AxVMHalImpl {
    type PagingHandler = axhal::paging::PagingHandlerImpl;
    fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
        axhal::mem::virt_to_phys(vaddr)
    }
    fn current_time_nanos() -> u64 {
        axhal::time::monotonic_time_nanos()
    }
	// ...
}

当前设计通过Rust的泛型类型(Trait)及其关联类型机制实现了依赖注入。

Copyright © 2025 • Created by ArceOS Team

虚拟中断控制器

各架构控制器

  • x86_64: local Apic (xAPIC and x2APIC) and IO Apic
  • aarch64: GIC (v2,v3,v4)
  • riscv64: PLIC or APLIC

实现方式

虚拟控制器

为相应架构的中断控制器设计虚拟控制器模型,为每个 vcpu 创建一个内部中断模拟设备,用于管理 systime 中断等,为每个客户机创建一个外部中断模拟设备,用于管理 io 中断。

MMIO 区域注册

在客户机进行中断控制器的寄存器读写时,会通过 data abort 陷入到虚拟机,到达 axvcpu 的如下代码段:

#![allow(unused)]
fn main() {
match &exit_reason {
    AxVCpuExitReason::MmioRead {
        addr,
        width,
        reg,
        reg_width: _,
    } => {
        let val = self
            .get_devices()
            .handle_mmio_read(*addr, (*width).into())?;
        vcpu.set_gpr(*reg, val);
        true
    }
    AxVCpuExitReason::MmioWrite { addr, width, data } => {
        self.get_devices()
            .handle_mmio_write(*addr, (*width).into(), *data as usize);
        true
    }
    AxVCpuExitReason::IoRead { port: _, width: _ } => true,
    AxVCpuExitReason::IoWrite {
        port: _,
        width: _,
        data: _,
    } => true,
    AxVCpuExitReason::NestedPageFault { addr, access_flags } => self
        .inner_mut
        .address_space
        .lock()
        .handle_page_fault(*addr, *access_flags),
    _ => false,
};
}

通过 handle_mmio_readhandle_mmio_write 实现相应 mmio 范围内的地址访问会路由到相应虚拟设备。

通过对相应虚拟寄存器的读写逻辑,实现对客户机中断设置的权限控制。

中断透传

虚拟中断控制器通过配置表,判断客户机是否有权限控制中断号,若有权限,则将客户机中断号相应操作透传到物理中断控制器。

虚拟设备到 Guest OS 的通知

绝大多数情况下,虚拟设备通知 Guest OS 的方式是虚拟中断。但虚拟中断并不完全来自虚拟设备,也可能来自直通设备的物理中断(由 Hypervisor 转发)或者来自某个 VCpu 的虚拟 IPI(同样由 Hypervisor 转发)。因此,需要一个统一的虚拟中断注入接口,用以向指定的 VCpu 注入中断。

这个接口应该放置在 AxVM 中,签名类似于 inject_interrupt_to_vcpu(target: Option, vector: usize) -> AxResult。其中 target 可以控制中断注入的目标 VCpu,是任意一个 VCpu,指定一个 VCpu,指定一组 VCpu,或者所有 VCpu;vector 是中断向量。放置在 AxVM 中的原因是,中断注入的操作可能需要访问 VGIC 等设备。

为了设备不直接依赖于 AxVM 或者 AxVCpu,虚拟设备结构体不能直接调用 inject_interrupt_to_vcpu,而是应当通过提供给设备的一个闭包来实现中断注入。

系统时钟和虚拟设备等中断通过全虚拟化方式实现,每个 vcpu 都有一个中断向量表,用于记录客户机中断号对应的中断状态。

当虚拟设备触发中断时,向物理中断控制器发送软中断,由物理中断控制器将中断请求转发到 vcpu

inject_interrupt_to_vcpu 的实现

为了保持 AxVM 的架构无关性,AxVCpu 和 AxArchVCpu 仍然应该提供一个 inject_interrupt 方法,用以向当前 VCpu 注入中断。AxVM 的 inject_interrupt_to_vcpu 方法应该根据 target 参数,调用对应 AxVCpu 的 inject_interrupt 方法。在 aarch64 和 riscv64 平台上,AxArchVCpu 在 setup 时,应该通过 SetupConfig 得到一个实际完成中断注入的闭包;而在 x86 平台上,AxArchVCpu 本身具有中断注入的能力,因此无需进一步的配置。

当被注入中断时,如果 VCpu 正在当前核心上运行,可以直接通过各个架构的虚拟化机制注入中断;如果 VCpu 处于当前核心就绪队列中,则应该记录中断,等 VCpu 下次运行时再注入;如果 VCpu 在非当前核心上运行,可以通过 IPI 通知目标核心的 Hypervisor,由 Hypervisor 负责注入中断。

参考资料

Copyright © 2025 • Created by ArceOS Team

Virtual GIC v2 设计文档

一、GICv2介绍

通过上图可以确定,GIC 主要包含 3 部分:Distributor、CPU interfaces 和 Virtual CPU interfaces。Virtual CPU interfaces 包含 Virtual interface control 和 Virtual CPU interface。

  • 中断进入 distributor,然后分发到 CPU interface

  • 某个 CPU 触发中断后,读 GICC_IAR 拿到中断信息,处理完后写 GICC_EOIR 和 GICC_DIR(如果 GICC_CTLR.EOImodeNS 是 0,则 EOI 的同时也会 DI)

  • GICD、GICC 寄存器都是 MMIO 的,device tree 中会给出物理地址

中断类型

  • 1. 软件生成中断(Software Generated Interrupts, SGI)

    • 中断号范围:0 到 15(共 16 个中断号)
    • 用途:用于处理器间通信(IPI),允许一个处理器核心向另一个处理器核心发送中断信号。
    • 特点:每个核心都可以生成和接收这些中断,通常用于任务调度、同步等操作。

    2. 私有外设中断(Private Peripheral Interrupts, PPI)

    • 中断号范围:16 到 31(共 16 个中断号)
    • 用途:用于处理与特定处理器核心直接相关的硬件事件,例如计时器中断、性能监控中断、调试中断等。
    • 特点:这些中断是每个核心私有的,只有对应的核心会处理这些中断。

    3. 共享外设中断(Shared Peripheral Interrupts, SPI)

    • 中断号范围:32 到 1019(共 988 个中断号)

    • 用途:用于处理系统中共享的外设中断,例如来自外部设备、网络接口、存储设备等的中断。

    • 特点:这些中断是所有核心共享的,可以由任何一个核心处理,通常通过中断亲和性(affinity)来决定哪个核心处理该中断。

    • SPI默认发送vcpu 0上,同样将中断信号放到vcpu的ap_list字段排队,等待vcpu处理。

Distributor 作用

Distributor 主要作用为检测中断源、控制中断源行为和将中断源分发到指定 CPU 接口上(针对每个 CPU 将优先级最高的中断转发到该接口上)。

Distributor 对中断的控制包括:

  • 全局启用中断转发到 CPU 接口

  • 开启或关闭每一个中断

  • 为每个中断设置优先级

  • 为每个中断设置目标处理器列表

  • 设置每个外设中断触发方式(电平触发、边缘触发)

  • 为每个中断设置组

  • 将 SGI 转发到一个或多个处理器

  • 每个中断状态可见

  • 提供软件设置或清除外设中断的挂起状态的一种机制

中断 ID

使用 ID 对中断源进行标识。每个 CPU 接口最多可以有 1020 个中断。SPI 和 PPI 中断为每个接口特定的,SPI 为为所有接口共用,因此多处理器系统中实际中断数大于 1020 个。

CPU Interface

CPU 接口提供一个处理器连接到 GIC 的接口。每一个 CPU 接口都提供一个编程接口:

  • 允许向处理器发送中断请求信号
  • 确认中断
  • 指示中断处理完成
  • 为处理器设置中断优先级掩码
  • 为处理器定义抢占策略
  • 选择最高优先级挂起中断

二、中断处理状态机

GIC 为每个 CPU 接口上每个受支持的中断维护一个状态机。下图显示了此状态机的实例,以及可能的状态转换。

  • Inactive:该中断源处于未激活状态
  • Pending:中断源触发状态,GIC感知到,但还未被分发到PE
  • Active:中断已经被某个PE认领确认
  • Active and pending:这个中断源的一次触发已经被PE确认,同时这个中断源的另一次触发正在pending状态

添加挂起状态(A1、A2)

  • 对于一个 SGI,发生以下 2 种情况的 1 种:

    • 软件写 GICD_SGIR 寄存器,指定目标处理器
    • 目标处理器上软件写 GICD_SPENDSGIRn 寄存器
  • 对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:

    • 外设发出一个中断请求信号

    • 软件写 GICD_ISPENDRn 寄存器

删除挂起状态(B1、B2)

  • 对于 SGI
    • 目标处理器写 GICD_CPENDSGIRn 寄存器
  • 对于一个 SPI 或 PPI,发生以下 2 种情况的 1 种:
    • 电平触发类型中断,信号取消
    • 边沿触发类型中断,软件写 GICD_ICPENDRn 寄存器

挂起到激活(C)

  • 如果中断使能,并且高优先级,软件从 GICC_IAR 寄存器读取时发生状态改变。

挂起到激活和挂起(D)

  • 对于 SGI,这种转变发生在以下任一情况下:

    • 将 SGI 状态设置为挂起的写入操作与读取 GICC_IAR 几乎同时发生
    • 当多个挂起的 SGI 具有相同 ID 时,并且它们来自同一个源处理器并指向同一个处理器。其中一个 SGI 状态变为激活(C),其他 SGI 状态变为激活和挂起(D)
  • 对于 SPI 或 PPI,满足以下所有条件,则发生这种转换

    • 中断开启
    • 软件读取 GICC_IAR,读操作将激活状态添加到中断中。
    • 此外,还应满足以下条件之一:
      • 对于电平触发中断,中断信号保持。通常都是这样,因为外设直到处理器处理完中断后才会取消触发信号。
      • 对于边沿触发中断,是否发生此转换取决于读取 GICC_IAR 的时间(中断再次触发,上一次未处理),读取 GICC_IAR 可能会转换到 C,后面可能会转换到 A2。

删除激活状态(E1、E2)

  • 软件写入 GICC_EOIR 或 GICC_DIR 来停用中断,

三、中断虚拟化设计

中断虚拟化概要

  • HCR_EL2.IMO 设置为 1 后,所有 IRQ 都会 trap 到 Hypervisor
  • Hypervisor 判断该 IRQ 是否需要插入到 vCPU
  • 插入 vIRQ 之后,在切换到 VM 之前需要 EOI 物理 IRQ,即 priority drop,降低运行优先级,使之后 VM 运行时能够再次触发该中断
  • 回到 VM 后,GIC 在 EL1 触发 vIRQ,这时候 EOI 和 DI 会把 vIRQ 和物理 IRQ 都 deactivate,因此不需要再 trap 到 Hypervisor ,不过如果是 SGI 的话并不会 deactivate,需要 Hypervisor 自己处理。

Hypervisor interface (GICH)

  • GICH base 物理地址在 device tree 中给出
  • 控制寄存器:GICH_HCR、GICH_VMCR 等
  • List 寄存器:GICH_LRn
  • KVM 中,这些寄存器保存在 struct vgic_cpuvgic_v2 字段,struct vgic_cpu 本身放在 struct kvm_vcpu_arch,每个 vCPU 一份
  • vCPU switch 的时候,需要切换这些寄存器(KVM 在 vgic-v2-switch.S 中定义相关切换函数)
  • VM 无法访问 GICH 寄存器,因为根本没有映射

vCPU interface (GICV, GICC in VM's view)

  • GICV 也是物理 GIC 上存在的,base 物理地址同样在 device tree 中给出
  • KVM 在系统全局的一个结构体(struct vgic_params vgic_v2_params)保存了这个物理地址
  • 创建 VM 时 Hypervisor 把一个特定的 GPA(KVM 中通过 ioctl 设置该地址)映射到 GICV base 物理地址,然后把这个 GPA 作为 GICC base 在 device tree 中传给 VM
  • VM 以为自己在访问 GICC,实际上它在访问 GICV
  • 目前理解这些 GICV 寄存器在 vCPU switch 的时候是不需要保存的(KVM 里没有保存 GICV 相关的代码),因为它其实在硬件里访问的是 GICH 配置的那些寄存器,比如 LR

Virtual distributor (GICD in VM's view)

  • 实际是内核里的一个结构体(struct vgic_dist
  • 在 device tree 中给 VM 一个 GICD base,但实际上没有映射
  • VM 访问 GICD 时,trap & emulate,直接返回或设置 struct vgic_dist 里的字段(在 vgic-v2-emul.c 文件中)
  • 每个 VM 一个,而不是每个 vCPU 一个,所以 struct vgic_dist 放在 struct kvm_arch

VM's view

img

  • 从 device tree 获得 GICD、GICC base 物理地址(实际是 Hypervisor 伪造的地址)
  • 配置 GICD 寄存器(实际上 trap 到 Hypervisor ,模拟地读写了内核某 struct 里的数据)
  • 执行直到发生中断(中断先到 Hypervisor ,Hypervisor 在 LR 中配置了一个物理 IRQ 到 vIRQ 的映射,并且设置为 pending,回到 VM 之后 GIC 在 VM 的 EL1 触发中断)
  • 读 GICC_IAR(经过 stage 2 页表翻译,实际上读了 GICV_IAR,GIC 根据 LR 返回 vIRQ 的信息,vIRQ 状态从 pending 转为 active)
  • 写 GICC_EOIR、GICC_DIR(经过 stage 2 页表翻译,实际上写了 GICV_EOIR、GICV_DIR,GIC EOI 并 deactivate 对应的 vIRQ,并 deactivate vIRQ 对应的物理 IRQ)

VGIC设计

主要以以下4中case进行讨论,其中case4涉及vCPU调度,其他情况不涉及调度:

VGIC Distributor设计

#![allow(unused)]
fn main() {
struct VgicDist {
    ...
    nr_spis: usize,           // num of SPIs
    spis: Vec<VgicIrq>,       // store SPI
    dist_iodev: VgicIoDevice, // Distributor I/O设备描述符
    ....
}

impl VgicDist {
    pub fn new() {
        let nr_spis = 256; 
        let mut spis = Vec::new(); 

        // init SPI interrupt
        for _ in 0..nr_spis {
            spis.push(VgicIrq {
                ap_list: RefCell::new(LinkedList::new()),
                vcpu: Some(Box::new(Vcpu {})),
                target_vcpu: Some(Box::new(Vcpu {})),
                intid: 0,
                line_level: false,
                active: false,
                enabled: true,
            });
        }

        let dist_iodev = VgicIoDevice {  };
        VgicDist {
            nr_spis,
            spis,
            dist_iodev,
        }
    }
}

}

VGIC Distributor 主要模拟 nr_spis 个 spis 中断

#![allow(unused)]
fn main() {
struct VgicIrq {
    /// A linked list header for managing interrupts.
    /// This is used for managing the list of interrupts associated with a VCPU.
    ap_list: RefCell<LinkedList<Box<VgicIrq>>>,
    
    /// For SGIs and PPIs: The VCPU that generated the interrupt.
    /// For SPIs: The VCPU whose `ap_list` this is queued on.
    vcpu: Option<Box<dyn VcpuTrait>>,
    
    /// The VCPU that this interrupt should be sent to, based on the targets register (v2)
    target_vcpu: Option<Box<dyn VcpuTrait>>,
    
    /// The guest-visible interrupt ID.
    intid: u32,
    
    /// Indicates if the interrupt is level-triggered only.
    line_level: bool,
    
    /// Not used for LPIs.
    active: bool,
    
    /// Indicates if the interrupt is enabled.
    enabled: bool,
}
}
  • GIC所以只处理SPI类型的中断,原因是其它两类中断的输入就是针对特定一个CPU的,不需要Distributor控制其中断信号的deliver行为;而SPI的目标CPU,是可以用户配置的,因此需要模拟一个Distributor来控制中断deliver的目标,并将Distributor的控制接口暴露给用户。
  • target_vcpu结构用来存放用户设置的GIC中断路由信息,如果用户没有设置,那target_vcpu就使用默认的CPU0,后续GIC可能会根据负载均衡策略将中断分发到其它目标CPU上。换句话说,target_vcpu可能不是中断最终投递的CPU,只是一个初始值,而vcpu才是中断最终投递的CPU

VGIC初始化

  1. vgicd-ctrl 寄存器:

​ - 对于case 1、2 和 3,不需要 IPI 通信。case 4 需要 IPI 通信。

  1. vgicd-iid 寄存器,vgicd-type 寄存器:

​ - 这些寄存器保存 GIC 的一些属性和处理元素(PE)的数量。

- 虚拟化提供的 vgicd 应根据 VPE 的数量进行配置。
  1. vgicd-isenable 寄存器:

​ - getenable:直接从结构中读取内容。

​ - setenable:根据 vtop 和 ptov 设置配置 GIC。对于情况 1、2 和 3,不需要 IPI 通信。情况 4 需要 IPI 通信。

  1. 其他 emu 寄存器:

​ - 其他 vgicd-emu 寄存器与 isenabler 类似。

SGI软件生成中断

SGI是一种特殊的中断,由软件生成,通常用于在多核系统中实现CPU间通信。SGI的目标CPU由发送者指定,并且SGI可以被路由到一个或多个核上。

在虚拟化环境下,由于多个vCPU可能共享同一个物理CPU,hypervisor需要对SGI进行虚拟化,以确保VM之间的隔离性和透明性。

Hypervisor对SGI的拦截

在虚拟化环境中,当VM试图发送SGI时,通常通过修改guest的GIC相关寄存器来触发。VM本身无法直接访问物理的GIC Distributor(GICD)寄存器,因此这些写操作会被hypervisor拦截。

  • vCPU到vCPU的SGI:VM发送SGI给自己的vCPU或同一个VM中的其他vCPU。

SGI的处理与路由

在SGI虚拟化中,hypervisor负责以下操作:

  • 拦截和解析SGI:当VM写入GICD_SGIR寄存器(用于触发SGI),hypervisor会拦截该写操作。它解析出目标vCPU以及SGI的ID。
  • SGI的重定向:hypervisor根据解析出的SGI信息,将SGI重新路由到目标vCPU,这里应该有有一个接口能够根据vcpu_id注入指定的中断。
    • vCPU活跃:直接发送到指定的vCPU
    • vCPU休眠:唤醒vCPU后,再发送到指定vCPU
  • 中断优先级和状态管理:hypervisor需要维护虚拟中断的优先级和状态(如等待、激活等),以确保VM感知到的中断行为与物理硬件一致。

虚拟GIC的支持

为了让VM能够像使用物理GIC一样处理中断,hypervisor会提供虚拟的GIC接口(vGIC)。vGIC负责模拟GICD和GICC(CPU接口)的寄存器操作,并将这些寄存器映射到VM的地址空间。

虚拟GIC支持VM的SGI管理,包括:

  • 虚拟GICD_SGIR寄存器:VM通过这个寄存器触发SGI,hypervisor在接收到写操作后进行中断重定向。
  • 虚拟中断路由表:hypervisor维护一个虚拟的中断路由表List Registers,用来追踪SGI应该被路由到哪个vCPU。

PPI 私有外设中断

PPI通常用于管理特定于处理器的外设中断。在GICv2中,每个核心都有其专属的PPI,通常包括定时器中断和其他本地外设中断。在虚拟化环境中,hypervisor需要虚拟化这些中断,以便每个VM能够透明地访问和使用它们。

VM发起PPI请求

当VM中的vCPU需要处理PPI时,通常是通过对GIC的寄存器进行操作。例如,vCPU可能会读取或清除某个PPI的状态,这一操作需要经过hypervisor的拦截。

Hypervisor拦截请求
  • 拦截操作:Hypervisor监测对PPI相关寄存器的访问请求,特别是GIC的PPI寄存器。
  • 解析请求:Hypervisor解析该请求以确定哪个vCPU或VM正在进行操作,并根据需要处理这些请求。
PPI的路由和分发
  • 管理PPI状态:Hypervisor需要维护每个VM的PPI状态,确保在VM运行时,PPI的状态能够正确反映在对应的vCPU上。
  • 中断注入:当PPI中断发生时,hypervisor负责将中断注入到对应的vCPU中。对于PPI,hypervisor可以直接向目标vCPU发送中断请求,而不需要重定向。
目标vCPU处理中断
  • vCPU处理PPI:一旦PPI被触发,目标vCPU会进入中断处理程序,执行相应的处理逻辑。
  • 状态恢复:处理完成后,vCPU会清除PPI中断状态,并恢复执行状态。
Hypervisor的清理工作
  • 状态管理:Hypervisor在处理完PPI中断后,负责清理中断状态和相关的寄存器,确保后续中断请求的正确性。

SPI 共享外设中断

在虚拟化环境中,SPI(Shared Peripheral Interrupt,共享外设中断)是一种用于处理多个处理器核心共享外设的中断。与SGI和PPI不同,SPI是针对共享设备的中断,允许多个CPU响应同一外设生成的中断。hypervisor在虚拟化SPI时需要确保VM之间的隔离,同时提供对共享外设的正确中断管理。

SPI通常用于系统中那些可以被多个处理器访问的外设,例如网络适配器、存储控制器等。在GICv2中,SPI由GIC的Distributor(GICD)管理,允许多个处理器核接收来自同一外设的中断。在虚拟化环境中,hypervisor需要将SPI虚拟化为适合多个VM使用的形式。

VM发起SPI请求

当外设生成中断时,它将通过物理GIC将SPI传递给相应的处理器核心。在虚拟化环境中,物理中断首先会传递到hypervisor。

Hypervisor的拦截和管理
  • 拦截中断:Hypervisor拦截来自外设的SPI请求,并识别该中断的目标VM(如果已映射)。
  • 中断映射:Hypervisor维护一个中断映射表,将物理中断与VM中的虚拟中断进行关联。
SPI的路由和重定向
  • 目标VM识别:Hypervisor通过中断映射表确定SPI应该路由到哪个VM的vCPU。
  • 中断注入:Hypervisor将SPI注入到目标VM的对应vCPU中,以触发中断处理。
目标vCPU处理中断
  • 处理SPI中断:目标vCPU接收到中断请求后,执行相应的中断处理程序。此过程包括保存上下文、执行处理逻辑以及清除中断状态。
  • 状态恢复:处理完成后,vCPU需要恢复到正常执行状态,并清除中断标志。
Hypervisor的清理工作
  • 状态更新:Hypervisor在处理完SPI中断后,更新中断的状态,以反映当前的处理中断情况。

List Register

img

对于有虚拟化扩展的 GIC,Hypervisor使用 List Registers 来维护高优先级虚拟中断的一些上下文信息。

struct gich_lr {
    uint32_t vid : 10;  // virq 中断号
    uint32_t pid : 10;  // 此 field 根据 hw 值不同而不同
                        // hw=1,表示此虚拟中断关联了一个物理中断,此 pid 为实际的 physical irq 中断号
                        // hw=0,bit19表示是否 signal eoi,给 maintenance interrupt 使用,不做讨论
                                 //bit12-10,如果这是一个 sgi 中断,即 virtual interrupt id < 15,那么此位域表示 requesting cpu id

    uint32_t resv : 3;  // 保留
    uint32_t pr : 5;    // 该virtual integrrupt 的优先级
    uint32_t state : 2; // 指示该中断的状态,invalid、pending、active、pending and active
    uint32_t grp1 : 1;  // 表示该 virtual integrrupt 是否是 group 1 virtual integrrupt
                        // 0 表示这是一个 group 0 virtual interrupt,表示安全虚拟中断,可配置是按照 virq 还是 vfiq 发送给 vcpu
                        // 1 表示这是一个 group 1 virtual interrupt,表示非安全虚拟中断,该中断以 virq 的形式触发,而不是 vfiq

    uint32_t hw : 1;    // 该虚拟中断是否关联了一个硬件物理中断
                        // 0 表示否,这是 triggered in software,当 deactivated 的时候不会通知 distributor
                        // 1 表示是,那么 deactivate 这个虚拟中断也会向对应的物理中断也执行 deactivate 操作
                        // 而具体的 deactivate 操作,如果 gicv_ctlr.eoimode=0,写 gicv_eoir 寄存器表示 drop priority 和 deactive 操作同时进行 
                        // 如果 gicv_ctlr.eoimode=1,写 gicv_eoir 寄存器表示 drop priority,写 GICV_DIR 表示 deactive
};

VGIC 寄存器虚拟化

有的寄存器是单个32位长,有的寄存器是多个32位长,所以使用宏来定义各个寄存器,提供直观的访问方式

#![allow(unused)]
fn main() {
// Macro to define GIC register enums
macro_rules! generate_gic_registers {
    (
        // Single register definitions
        singles {
            $(
                $single_name:ident = $single_offset:expr // Single register name and offset
            ),* $(,)?
        }
        // Range register definitions
        ranges {
            $(
                $range_name:ident = {
                    offset: $range_offset:expr, // Range register base offset
                    size: $range_size:expr // Number of registers in the range
                }
            ),* $(,)?
        }
    ) => {
        #[derive(Debug, Clone, Copy, PartialEq)]
        pub enum GicRegister {
            // Generate single register variants
            $(
                $single_name, // Single register variant
            )*
            // Generate range register variants (with index)
            $(
                $range_name(u32), // Range register variant with index
            )*
        }

        impl GicRegister {

            // Convert address to register enum
            pub fn from_addr(addr: u32) -> Option<Self> {
                match addr {
                    // Match single registers
                    $(
                        addr if addr == $single_offset => Some(Self::$single_name), // Single register match
                    )*
                    // Match range registers
                    $(
                        addr if addr >= $range_offset && addr < $range_offset + ($range_size * 4) => {
                            let idx = (addr - $range_offset) / 4; // Calculate index
                            if idx < $range_size {
                                Some(Self::$range_name(idx)) // Range register match
                            } else {
                                None
                            }
                        },
                    )*
                    _ => None, // No match
                }
            }
        }
    };
}

// Use the macro to generate specific register definitions
generate_gic_registers! {
    singles {
        // Distributor Control Register
        GicdCtlr = 0x0000,
        // Distributor Type Register
        GicdTyper = 0x0004,
        // Distributor Implementer Identification Register
        GicdIidr = 0x0008,
        // Distributor Status Register
        GicdStatusr = 0x0010,
    }
    ranges {
        // Interrupt Group Register
        GicdIgroupr = {
            offset: 0x0080,
            size: 32
        },
        // Interrupt Enable Set Register
        GicdIsenabler = {
            offset: 0x0100,
            size: 32
        },
        // Interrupt Enable Clear Register
        GicdIcenabler = {
            offset: 0x0180,
            size: 32
        },
        // Interrupt Pending Set Register
        GicdIspendr = {
            offset: 0x0200,
            size: 32
        },
        GicdIcpendr = {
            offset: 0x0280,
            size: 32
        },
        // Interrupt Active Set Register
        GicdIsactiver = {
            offset: 0x0300,
            size: 32
        },
        // Interrupt Active Clear Register
        GicdIcactiver = {
            offset: 0x0380,
            size: 32
        },
        // Interrupt Priority Register
        GicdIpriorityr = {
            offset: 0x0400,
            size: 256
        },
        // Interrupt Target Register
        GicdItargetsr = {
            offset: 0x0800,
            size: 256
        },
        // Interrupt Configuration Register
        GicdIcfgr = {
            offset: 0x0c00,
            size: 64
        },
        // PPI Status Register
        GicdPpisr = {
            offset: 0x0d00,
            size: 32
        },
        // SPI Status Register
        GicdSpisr = {
            offset: 0x0d04,
            size: 32
        },
        // Non-Secure Access Control Register
        GicdNsacr = {
            offset: 0x0e00,
            size: 32
        },
        // Software Generated Interrupt Register
        GicdSgir = {
            offset: 0x0f00,
            size: 32
        },
        // Pending Software Generated Interrupt Register
        GicdCpendsgir = {
            offset: 0x0f10,
            size: 32
        },
        // Software Generated Interrupt Pending Register
        GicdSpendsgir = {
            offset: 0x0f20,
            size: 32
        },
    }
}

// 访问寄存器的方法
match GicRegister::from_addr(addr as u32) {
    Some(reg) => match reg {
        GicRegister::GicdCtlr => Ok(self.vgicd.lock().ctrlr as usize),
        GicRegister::GicdTyper => Ok(self.vgicd.lock().typer as usize),
        GicRegister::GicdIidr => Ok(self.vgicd.lock().iidr as usize),
        GicRegister::GicdStatusr => self.read_statusr(),
        GicRegister::GicdIgroupr(idx) => self.read_igroupr(idx),
        GicRegister::GicdIsenabler(idx) => Ok(self.vgicd.lock().vgicd_isenabler_read(idx)),
        GicRegister::GicdIcenabler(idx) => self.read_icenabler(idx),
        GicRegister::GicdIspendr(idx) => self.read_ispendr(idx),
        _ => {
            error!("Read register address: {addr:#x}");
        }
    },
    None => {
        error!("Invalid read register address: {addr:#x}");
    }
}

}

KVM关于VGIC的设计

kvm_vm_ioctl
	kvm_vm_ioctl_irq_line
	    irq_type = (irq >> KVM_ARM_IRQ_TYPE_SHIFT) & KVM_ARM_IRQ_TYPE_MASK;	/* SPI 类型 */
    	vcpu_idx = (irq >> KVM_ARM_IRQ_VCPU_SHIFT) & KVM_ARM_IRQ_VCPU_MASK;	/* vcpu_idx: 0*/
    	irq_num = (irq >> KVM_ARM_IRQ_NUM_SHIFT) & KVM_ARM_IRQ_NUM_MASK;		/* 中断号:32+7 = 39 */
    	
   	case KVM_ARM_IRQ_TYPE_CPU:	/* 发往特定CPU上的快速中断 */
        if (irqchip_in_kernel(kvm))
            return -ENXIO;

        if (vcpu_idx >= nrcpus)
            return -EINVAL;

        vcpu = kvm_get_vcpu(kvm, vcpu_idx);	/* 根据cpuid取出vcpu结构体*/
        if (!vcpu)
            return -EINVAL;

        if (irq_num > KVM_ARM_IRQ_CPU_FIQ)
            return -EINVAL;
		/* 立即投递到cpu的中断状态字段,然后kick cpu进行处理
		 * 由于是快速中断,KVM直接更新的irq_lines字段,没有将中断信号放到vgic_cpu的ap_list上排队
		 */
        return vcpu_interrupt_line(vcpu, irq_num, level);		
    case KVM_ARM_IRQ_TYPE_PPI:	/* CPU私有类型的中断 */
        if (!irqchip_in_kernel(kvm))
            return -ENXIO;

        if (vcpu_idx >= nrcpus)
            return -EINVAL;

        vcpu = kvm_get_vcpu(kvm, vcpu_idx);	/* 根据cpuid取出vcpu结构体 */
        if (!vcpu)
            return -EINVAL;

        if (irq_num < VGIC_NR_SGIS || irq_num >= VGIC_NR_PRIVATE_IRQS)
            return -EINVAL;
		/* 非快速中断,取出目的vcpu后,将中断信号放到vcpu的ap_list字段排队,等待vcpu处理 */
        return kvm_vgic_inject_irq(kvm, vcpu->vcpu_id, irq_num, level, NULL);	
    case KVM_ARM_IRQ_TYPE_SPI:
        if (!irqchip_in_kernel(kvm))
            return -ENXIO;

        if (irq_num < VGIC_NR_PRIVATE_IRQS)
            return -EINVAL;
		/* 非快速中断,SPI默认发送vcpu 0上,同样将中断信号放到vcpu的ap_list字段排队,等待vcpu处理 */
        return kvm_vgic_inject_irq(kvm, 0, irq_num, level, NULL);
    }

```<style>
  .scroll-to-top {
    font-size: 2.5rem;
    width: 3.2rem;
    height: 3.2rem;
    display: none;
    align-items: center;
    justify-content: center;
    position: fixed;
    padding: 0.75rem;
    bottom: 4rem;
    right: calc(1.25rem + 90px + var(--page-padding));
    z-index: 999;
    cursor: pointer;
    border: none;
    color: var(--bg);
    background: var(--fg);
    border-radius: 50%;
  }
  .scroll-to-top.hidden {
    display: none;
  }
  .scroll-to-top i {
    transform: translateY(-2px);
  }
  @media (min-width: 1080px) {
    .scroll-to-top {
      display: flex;
    }
  }
</style>
<button type="button" aria-label="scroll-to-top" class="scroll-to-top hidden" onclick="scrollToTop()">
  <i class="fa fa-angle-up"></i>
</button>
<script>
  const scrollToTop = () => window.scroll({ top: 0, behavior: "smooth" });
  window.addEventListener("scroll", () => {
    const button = document.querySelector(".scroll-to-top");
    button.classList.toggle("hidden", window.scrollY < 200);
  });
</script>
<style>
  .announcement-banner {
    z-index: 150;
    color: var(--fg);
    position: relative;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 0;
    padding: 1rem 3.5rem;
    background: repeating-linear-gradient(
      45deg,
      var(--site-announcement-bar-stripe-color1),
      var(--site-announcement-bar-stripe-color1) 20px,
      var(--site-announcement-bar-stripe-color2) 10px,
      var(--site-announcement-bar-stripe-color2) 40px
    );
  }
  .announcement-banner {
    --site-announcement-bar-stripe-color1: #e5e7eb;
    --site-announcement-bar-stripe-color2: #d1d5db;
  }
  .announcement-banner[data-theme="ocean"] {
    --site-announcement-bar-stripe-color1: #86b2f9;
    --site-announcement-bar-stripe-color2: #7298ea;
  }
  .announcement-banner[data-theme="forest"] {
    --site-announcement-bar-stripe-color1: #97f5d6;
    --site-announcement-bar-stripe-color2: #6de0bf;
  }
  .announcement-banner[data-theme="lava"] {
    --site-announcement-bar-stripe-color1: #fea3a3;
    --site-announcement-bar-stripe-color2: #e57e7e;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner {
    --site-announcement-bar-stripe-color1: #1f2937;
    --site-announcement-bar-stripe-color2: #111827;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="ocean"] {
    --site-announcement-bar-stripe-color1: #2563eb;
    --site-announcement-bar-stripe-color2: #1d4ed8;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="forest"] {
    --site-announcement-bar-stripe-color1: #22d3a5;
    --site-announcement-bar-stripe-color2: #0fbf8f;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="lava"] {
    --site-announcement-bar-stripe-color1: #f87171;
    --site-announcement-bar-stripe-color2: #ef4444;
  }
  .announcement-banner p {
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    text-align: center;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-wrap: balance;
  }
  .announcement-banner button {
    top: 50%;
    right: 1rem;
    position: absolute;
    transform: translateY(-50%);
    width: 3rem;
    height: 3rem;
    cursor: pointer !important;
    border: none;
    font-weight: 900;
    border-radius: 50%;
    background-color: transparent;
  }
</style>
<div style="display: none" data-id="0.2.11" class="announcement-banner" data-theme="default">
  <p><em>正在逐步完善中。。。</em></p>

  <button type="button">X</button>
</div>
<script>
  (() => {
    const banner = document.querySelector(".announcement-banner");
    const id = banner.getAttribute("data-id");
    const message = banner.querySelector("p").textContent;
    const localData = JSON.parse(localStorage.getItem("mdbook-announcement-banner"));
    if (!localData || localData.id !== id || localData.hide !== true) {
      banner.style.display = "flex";
      const page = document.querySelector(".page");
      page.parentNode.insertBefore(banner, page);
      banner.querySelector("button").addEventListener("click", () => {
        banner.remove();
        localStorage.setItem("mdbook-announcement-banner", JSON.stringify({ id, hide: true, message }));
      });
    }
  })();
</script>
<style>
  .giscus {
    margin-top: 6rem;
  }
</style>
<script
  src="https://giscus.app/client.js"
  data-repo="arceos-hypervisor/doc"
  data-repo-id="R_kgDOLMHfvQ"
  data-category="Comments"
  data-category-id="DIC_kwDOLMHfvc4CoqAB"
  data-mapping="title"
  data-strict="0"
  data-reactions-enabled="1"
  data-emit-metadata="0"
  data-input-position="bottom"
  data-theme="light"
  data-lang="zh-CN"
  data-loading="eager"
  crossorigin="anonymous"
  async
></script>
<script>
  (() => {
    const giscusScript = document.querySelector("script[data-repo][data-repo-id]");
    if (giscusScript?.getAttribute("data-theme") !== "book") return;
    const mapTheme = (theme) => (theme === "light" || theme === "rust" ? "light" : "dark");
    const bookTheme = localStorage.getItem("mdbook-theme") || html.getAttribute("class");
    giscusScript.setAttribute("data-theme", mapTheme(bookTheme));
    document.querySelectorAll("button[role='menuitem'].theme").forEach((btn) => {
      btn.addEventListener("click", (event) => {
        theme = mapTheme(event.target.id);
        const iframe = document.querySelector("iframe.giscus-frame");
        if (iframe) iframe.contentWindow.postMessage({ giscus: { setConfig: { theme } } }, "*");
      });
    });
  })();
</script>
<style>
  footer {
    text-align: center;
    text-wrap: balance;
    margin-top: 5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
  footer p {
    margin: 0;
  }
</style>
<footer><p>Copyright © 2025 • Created by ArceOS Team</p>
</footer>

虚拟 Local APIC

本节描述了虚拟 Local APIC 的实现。

全虚拟化

寄存器虚拟化:

Local APIC 的寄存器通过内存映射(MMIO)访问。虚拟机对 APIC 寄存器的读写会触发 VM-exit,由虚拟化层(如 VMM/Hypervisor)模拟这些操作,维护每个虚拟 CPU(vCPU)的虚拟寄存器状态。

中断注入:

当物理中断需要传递给虚拟机时,虚拟化层将其转换为虚拟中断(如虚拟 IRQ),并通过修改虚拟 APIC 的状态(如 IRR/ISR 寄存器)或直接注入中断(如 Intel 的 vmcs VM_ENTRY_INTR_INFO)通知虚拟机。

定时器虚拟化:

虚拟 APIC 定时器需根据虚拟机的配置(如周期和计数)模拟中断。Hypervisor 可能使用物理定时器(如 host 的 hrtimer)或时间偏移技术来触发虚拟中断。

硬件辅助虚拟化

现代 CPU(如 Intel VT-x 和 AMD-V)提供了硬件加速特性,显著优化性能:

APICv(Intel) / AVIC(AMD):

硬件直接支持虚拟 APIC 状态维护,减少 VM-exit。例如:

  • Virtual APIC Page:在 VMCS 中维护虚拟 APIC 的寄存器,允许虚拟机直接访问,无需陷入。

  • 中断投递优化:硬件自动将中断路由到目标 vCPU 的虚拟 APIC。

  • 自动处理 EOI:某些中断的确认(EOI)由硬件处理,避免 VM-exit。

Posted Interrupts(Intel):

  • 物理中断可直接“投递”到虚拟机的虚拟 APIC,绕过 Hypervisor 干预,极大降低延迟。

具体实现

代码位于 x86-vlapic

EmulatedLocalApic 实现了虚拟中断的基本方法,通过 handle_read handle_write 实现读写虚拟中断寄存器的功能。

VirtualApicRegs 包含了 APIC 所有寄存器,保存客户机虚拟中断的寄存器状态

Local APIC 寄存器

本地APIC寄存器被内存映射到MP/MADT表中可找到的地址。若使用分页,请确保将这些寄存器映射到虚拟内存。每个寄存器均为32位长,并期望以32位整数形式进行读写。尽管每个寄存器占用4个字节,但它们都按16字节边界对齐。 本地APIC寄存器列表(待办事项:为所有寄存器添加描述):

#![allow(unused)]
fn main() {
register_structs! {
    #[allow(non_snake_case)]
    pub LocalAPICRegs {
        (0x00 => _reserved0),
        /// Local APIC ID register (VID): the 32-bit field located at offset 000H on the virtual-APIC page.
        (0x20 => pub ID: ReadWrite<u32>),
        (0x24 => _reserved1),
        /// Local APIC Version register (VVER): the 32-bit field located at offset 030H on the virtual-APIC page.
        (0x30 => pub VERSION: ReadOnly<u32>),
        (0x34 => _reserved2),
        /// Virtual task-priority register (VTPR): the 32-bit field located at offset 080H on the virtual-APIC page.
        (0x80 => pub TPR: ReadWrite<u32>),
        (0x84 => _reserved3),
        /// Virtual APIC-priority register (VAPR): the 32-bit field located at offset 090H on the virtual-APIC page.
        (0x90 => pub APR: ReadOnly<u32>),
        (0x94 => _reserved4),
        /// Virtual processor-priority register (VPPR): the 32-bit field located at offset 0A0H on the virtual-APIC page.
        (0xA0 => pub PPR: ReadOnly<u32>),
        (0xA4 => _reserved5),
        /// Virtual end-of-interrupt register (VEOI): the 32-bit field located at offset 0B0H on the virtual-APIC page.
        (0xB0 => pub EOI: WriteOnly<u32>),
        (0xB4 => _reserved6),
        /// Virtual Remote Read Register (RRD): the 32-bit field located at offset 0C0H on the virtual-APIC page.
        (0xC0 => pub RRD: ReadOnly<u32>),
        (0xC4 => _reserved7),
        /// Virtual Logical Destination Register (LDR): the 32-bit field located at offset 0D0H on the virtual-APIC page.
        (0xD0 => pub LDR: ReadWrite<u32>),
        (0xD4 => _reserved8),
        /// Virtual Destination Format Register (DFR): the 32-bit field located at offset 0E0H on the virtual-APIC page.
        (0xE0 => pub DFR: ReadWrite<u32>),
        (0xE4 => _reserved9),
        /// Virtual Spurious Interrupt Vector Register (SVR): the 32-bit field located at offset 0F0H on the virtual-APIC page.
        (0xF0 => pub SVR: SpuriousInterruptVectorRegisterMmio),
        (0xF4 => _reserved10),
        /// Virtual interrupt-service register (VISR):
        /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets
        /// 100H, 110H, 120H, 130H, 140H, 150H, 160H, and 170H on the virtual-APIC page.
        (0x100 => pub ISR: [ReadOnly<u128>; 8]),
        /// Virtual trigger-mode register (VTMR):
        /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets
        /// 180H, 190H, 1A0H, 1B0H, 1C0H, 1D0H, 1E0H, and 1F0H on the virtual-APIC page.
        (0x180 => pub TMR: [ReadOnly<u128>; 8]),
        /// Virtual interrupt-request register (VIRR):
        /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets
        /// 200H, 210H, 220H, 230H, 240H, 250H, 260H, and 270H on the virtual-APIC page.
        /// Bit x of the VIRR is at bit position (x & 1FH) at offset (200H | ((x & E0H) » 1)).
        /// The processor uses only the low 4 bytes of each of the 16-Byte fields at offsets 200H, 210H, 220H, 230H, 240H, 250H, 260H, and 270H.
        (0x200 => pub IRR: [ReadOnly<u128>; 8]),
        /// Virtual error-status register (VESR): the 32-bit field located at offset 280H on the virtual-APIC page.
        (0x280 => pub ESR: ReadWrite<u32>),
        (0x284 => _reserved11),
        /// Virtual LVT Corrected Machine Check Interrupt (CMCI) Register
        (0x2F0 => pub LVT_CMCI: LvtCmciRegisterMmio),
        (0x2F4 => _reserved12),
        /// Virtual Interrupt Command Register (ICR): the 64-bit field located at offset 300H on the virtual-APIC page.
        (0x300 => pub ICR_LO: ReadWrite<u32>),
        (0x304 => _reserved13),
        (0x310 => pub ICR_HI: ReadWrite<u32>),
        (0x314 => _reserved14),
        /// Virtual LVT Timer Register: the 32-bit field located at offset 320H on the virtual-APIC page.
        (0x320 => pub LVT_TIMER: LvtTimerRegisterMmio),
        (0x324 => _reserved15),
        /// Virtual LVT Thermal Sensor register: the 32-bit field located at offset 330H on the virtual-APIC page.
        (0x330 => pub LVT_THERMAL: LvtThermalMonitorRegisterMmio),
        (0x334 => _reserved16),
        /// Virtual LVT Performance Monitoring Counters register: the 32-bit field located at offset 340H on the virtual-APIC page.
        (0x340 => pub LVT_PMI: LvtPerformanceCounterRegisterMmio),
        (0x344 => _reserved17),
        /// Virtual LVT LINT0 register: the 32-bit field located at offset 350H on the virtual-APIC page.
        (0x350 => pub LVT_LINT0: LvtLint0RegisterMmio),
        (0x354 => _reserved18),
        /// Virtual LVT LINT1 register: the 32-bit field located at offset 360H on the virtual-APIC page.
        (0x360 => pub LVT_LINT1: LvtLint1RegisterMmio),
        (0x364 => _reserved19),
        /// Virtual LVT Error register: the 32-bit field located at offset 370H on the virtual-APIC page.
        (0x370 => pub LVT_ERROR: LvtErrorRegisterMmio),
        (0x374 => _reserved20),
        /// Virtual Initial Count Register (for Timer): the 32-bit field located at offset 380H on the virtual-APIC page.
        (0x380 => pub ICR_TIMER: ReadWrite<u32>),
        (0x384 => _reserved21),
        /// Virtual Current Count Register (for Timer): the 32-bit field located at offset 390H on the virtual-APIC page.
        (0x390 => pub CCR_TIMER: ReadOnly<u32>),
        (0x394 => _reserved22),
        /// Virtual Divide Configuration Register (for Timer): the 32-bit field located at offset 3E0H on the virtual-APIC page.
        (0x3E0 => pub DCR_TIMER: ReadWrite<u32>),
        (0x3E4 => _reserved23),
        /// Virtual SELF IPI Register: the 32-bit field located at offset 3F0H on the virtual-APIC page.
        (0x3F0 => pub SELF_IPI: WriteOnly<u32>),
        (0x3F4 => _reserved24),
        (0x1000 => @END),
    }
}
}

EOI 寄存器

使用值0向偏移量为0xB0的寄存器写入,以信号通知中断结束。使用非零值可能会导致通用保护故障。

Local Vector Table 寄存器

处理器和LAPIC自身可以生成一些特殊的中断。虽然外部中断是在I/O APIC中配置的,但这些中断必须使用LAPIC中的寄存器进行配置。最有趣的寄存器包括:0x320 = LAPIC定时器,0x350 = LINT0,0x360 = LINT1。更多详情请参见 Intel SDM vol 3

寄存器格式:

位范围描述
0-7向量编号
8-10(定时器保留)如果是NMI则为100b
11保留
12如果中断挂起则设置
13(定时器保留)极性,设置为低电平触发
14(定时器保留)远程IRR
15(定时器保留)触发模式,设置为电平触发
16设置以屏蔽
17-31保留

Spurious Interrupt Vector 寄存器

偏移量是0xF0。低字节包含伪中断的编号。如上所述,您应该将此设置为0xFF。要启用APIC,请设置此寄存器的第8位(或0x100)。如果设置了第12位,则EOI消息不会被广播。其余的所有位目前都是保留的。

Interrupt Command 寄存器 (ICR)

中断命令寄存器由两个32位寄存器组成;一个位于0x300,另一个位于0x310。它用于向不同的处理器发送中断。中断是在0x300被写入时发出的,而不是在0x310被写入时发出的。因此,要发送中断命令,应该首先写入0x310,然后写入0x300。在0x310处有一个位于位24-27的字段,它是目标处理器的本地APIC ID(针对物理目的地模式)。这里是0x300的结构:

位范围描述
0-7向量编号,或SIPI的起始页号
8-10交付模式。0表示正常,1表示最低优先级,2表示SMI,4表示NMI,5可以是INIT或INIT级别解除,6表示SIPI
11目的地模式。清除表示物理目的地,或者设置表示逻辑目的地。如果该位被清除,则0x310中的目的地字段被视为正常处理
12交付状态。当中断被目标接受时清除。通常应在发送中断后等待此位清除
13保留
14清除表示INIT级别解除,否则设置
15设置表示INIT级别解除,否则清除
18-19目的地类型。如果>0,则忽略0x310中的目的地字段。1总是发送中断给自己,2发送给所有处理器,3发送给除当前处理器外的所有处理器。最好避免使用模式1、2和3,并坚持使用0
20-31保留

Copyright © 2025 • Created by ArceOS Team

设备直通(Device Passthrough)

设备直通是一种高级虚拟化技术,允许虚拟机(VM)直接访问和控制物理硬件设备。它本质上是一种特殊的设备模拟方式,其特点是将物理设备完全隔离并独占分配给特定虚拟机使用。这种技术绕过了虚拟化层的常规设备模拟过程,提供了接近原生硬件的性能表现。

核心优势

  • 接近原生性能:直通设备几乎能提供与裸机系统相同的性能表现,尤其适用于I/O密集型设备
  • 低延迟响应:最小化了虚拟化层引入的额外延迟,关键应用场景如高性能计算、实时数据处理等尤为重要
  • 功能完整性:虚拟机可访问设备的全部功能集,不受虚拟化层功能模拟的限制
  • 硬件加速:允许使用特定硬件的加速功能(如GPU计算、网卡卸载等)

技术实现机制

设备直通技术主要依赖于以下关键技术:

  • IOMMU(I/O内存管理单元):负责虚拟机I/O地址到物理地址的转换,保障安全隔离
  • 中断重映射:将设备中断正确路由到对应的虚拟机
  • DMA重映射:确保设备的直接内存访问操作被正确限制在虚拟机的内存空间内

axvisor设备直通配置解析

以下是Axvisor在ARM平台上基于QEMU架构实现的设备直通配置示例:

Pass-through devices.
# Name Base-Ipa Base-Pa Length Alloc-Irq.
passthrough_devices = [
    ["intc@8000000", 0x800_0000, 0x800_0000, 0x50_000, 0x1],
    ["pl011@9000000", 0x900_0000, 0x904_0000, 0x1000, 0x1],
    ["pl031@9010000", 0x901_0000, 0x901_0000, 0x1000, 0x1],
    ["pl061@9030000", 0x903_0000, 0x903_0000, 0x1000, 0x1],
    ["virtio_mmio", 0xa00_0000, 0xa00_0000, 0x4000, 0x1],
]

配置参数详解

每个设备条目包含5个关键参数:

  • 设备标识符(Name):唯一标识直通设备,通常包含设备类型和基址信息
  • 中间物理地址(Base-Ipa):虚拟机内部看到的设备基址,用于VM内部的内存映射
  • 物理地址(Base-Pa):设备在宿主机上的实际物理内存地址
  • 映射长度(Length):设备内存映射区域的大小,以字节为单位
  • 中断分配标志(Alloc-Irq):控制是否为该设备分配中断资源(1=启用,0=禁用)

示例中的直通设备详情

设备名称功能描述技术特点
intc@8000000中断控制器管理系统中断,允许VM直接处理硬件中断事件
pl011@9000000PL011 UART串口控制器提供串行通信能力,常用于调试和控制台访问
pl031@9010000PL031实时时钟(RTC)提供精确的时间功能,确保VM时间准确性
pl061@9030000PL061 GPIO控制器管理通用输入/输出接口,用于控制外部设备
virtio_mmioVirtIO内存映射I/O设备提供高效的半虚拟化I/O接口,平衡性能与兼容性

设备直通是构建高性能虚拟化环境的关键组件,可以实现在保持虚拟化灵活性的同时,提供接近于物理机的性能。

Copyright © 2025 • Created by ArceOS Team

  • axdevice 是 ArceOS 的一个模块,提供设备仿真支持
    • 部分架构独立
    • 不同的仿真设备实现需要被分离到独立的 crate 中

Copyright © 2025 • Created by ArceOS Team

PCI

PCI(外设组件互连)是一种广泛使用的设备总线标准,用于连接计算机的外设(如网卡、显卡等)与系统通信。CPU可以通过load/store指令来访问PCI设备,PCI设备有如下三种不同内存:

  • MMIO
  • PCI IO space
  • PCI configuration space

配置一个PCI设备通常需要对其BAR(Base Address Register)进行配置并启用设备,遍历每个BAR,检查BAR的内存类型并分配地址,root.bar_info(bdf, bar)调用返回一个关于指定设备和BAR的详细信息。从分配器申请地址,根据BAR类型(32位或64位)调用set_bar_32set_bar_64

#![allow(unused)]
fn main() {
let info = root.bar_info(bdf, bar).unwrap(); // 获取BAR元数据
if let BarInfo::Memory { address_type, address, size, .. } = info {
    if size > 0 && address == 0 { // 需要分配的未初始化BAR
        let new_addr = allocator
            .as_mut()
            .expect("No memory ranges available for PCI BARs!")
            .alloc(size as _)
            .ok_or(DevError::NoMemory)?;

        if address_type == MemoryBarType::Width32 {
            root.set_bar_32(bdf, bar, new_addr as _);
        } else if address_type == MemoryBarType::Width64 {
            root.set_bar_64(bdf, bar, new_addr);
        }
    }
}
}

检查当前BAR是否占用两个条目,若当前BAR是64位类型,则需递增bar两次,跳过下一个索引。

#![allow(unused)]
fn main() {
bar += 1;
if info.takes_two_entries() {
    bar += 1;
}
}

启用设备的IO/内存访问及总线主控

#![allow(unused)]
fn main() {
let (_status, cmd) = root.get_status_command(bdf);
  root.set_command(
    bdf,
    cmd | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER,
  );
}

命令寄存器设置

  • IO_SPACE: 允许设备响应IO端口访问。
  • MEMORY_SPACE: 允许设备响应内存映射访问。
  • BUS_MASTER: 启用设备作为总线主设备。

Emulated PCI

设备的虚拟化是通过模拟(Emulation)和直通(Passthrough)技术来实现的。

Emulated PCI正是通过软件模拟PCI设备实现的,这些虚拟设备与真实的硬件PCI设备类似,但并非直接依赖Hardware Devices的支持,而是通过Hypervisor层来模拟和管理,用于在虚拟化环境中为VM提供对物理PCI设备的访问能力。

Guest VM层

  • 虚拟PCI设备驱动,Guest VM认为存在真实PCI设备,通过标准PCI驱动发起I/O请求。

Hypervisor层

  • 虚拟PCI总线(Virtual PCI Bus)
    • 模拟PCI总线拓扑结构,管理虚拟设备的配置空间。
    • 实现PCI枚举过程,向Guest OS暴露虚拟设备列表。
  • PCI设备模型(Device Model)
    • 寄存器模拟:对设备寄存器的读写操作进行拦截和模拟。
    • DMA模拟:通过虚拟地址转换(GPA→HPA)处理Guest发起的DMA操作。
    • 中断模拟:将虚拟设备中断映射为虚拟中断。

Host OS & Hardware层

  • 物理资源交互,对于纯软件模拟设备,由Host用户态程序处理I/O请求。

核心机制

  • 配置空间模拟

    PCI设备通过配置空间定义其资源需求(如I/O端口、内存映射地址等),虚拟化时,Axvisor模拟PCI总线的拓扑关系和设备的配置空间,例如,虚拟设备的BAR(基址寄存器) 由Axvisor动态分配,客户机操作系统通过写入BAR来请求资源,Axvisor则映射到宿主机物理地址或虚拟资源。

  • 设备发现与枚举

    GuestOS启动时,会像物理机一样枚举PCI总线以发现设备。Axvisor模拟BIOS/UEFI固件的行为,向GuestOS呈现虚拟PCI总线及挂接的设备。

  • I/O访问与中断处理

    GuestOS对虚拟设备的I/O端口(PMIO)或内存映射(MMIO)访问会被VMM截获。中断则通过虚拟APIC或注入虚拟中断信号通知GuestOS。

Copyright © 2025 • Created by ArceOS Team

Virtio-Device

virtio设备的基本组成结构

virtio设备代表了一类I/O通用设备,为了让设备驱动能够管理和使用设备。在程序员的眼里,I/O设备基本组成结构包括如下恩利:

  • 呈现模式:设备一般通过寄存器、内存或特定I/O指令等方式让设备驱动能看到和访问到设备;
  • 特征描述:让设备驱动能够了解设备的静态特性(可通过软件修改),从而决定是否或如何使用该设备;
  • 状态表示:让设备驱动能够了解设备的当前动态状态,从而确定如何进行设备管理或I/O数据传输;
  • 交互机制:交互包括事件通知和数据传输;对于事件通知,让设备驱动及时获知设备的状态变化的机制(可基于中断等机制),以及让设备及时获得设备驱动发出的I/O请求(可基于寄存器读写等机制);对于数据传输,让设备驱动能处理设备给出的数据,以及让设备能处理设备驱动给出的数据,如(可基于DMA或virtqueue等机制)。 virtio设备具体定义了设备驱动和设备之间的接口,包括设备呈现模式、设备状态域、特征位、通知、设备配置空间、虚拟队列等,覆盖了上述的基本接口描述。

virtio设备的基本组成要素

virtio设备的基本组成要素如下:

  • 设备状态域(Device status field)
  • 特征位(Feature bits)
  • 通知(Notifications)
  • 设备配置空间(Device Configuration space)
  • 一个或多个虚拟队列(virtqueue) 其中的设备特征位和设备配置空间属于virtio设备的特征描述;设备状态域属于virtio设备初始化时的状态表示;通知和虚拟队列属于virtio设备的交互机制,也包含virtio设备运行时的状态表示。

virtio设备的呈现模式

virtio设备的基本组成要素如下:

  • 设备状态域(Device status field)
  • 特征位(Feature bits)
  • 通知(Notifications)
  • 设备配置空间(Device Configuration space)
  • 一个或多个虚拟队列(virtqueue) 其中的设备特征位和设备配置空间属于virtio设备的特征描述;设备状态域属于virtio设备初始化时的状态表示;通知和虚拟队列属于virtio设备的交互机制,也包含virtio设备运行时的状态表示。

virtio设备的特征描述

virtio设备特征描述包括设备特征位和设备配置空间。
特征位
特征位用于表示VirtIO设备具有的各种特性和功能。其中bit0 – 23是特定设备可以使用的feature bits, bit24 – 37预给队列和feature协商机制,bit38以上保留给未来其他用途。驱动程序与设备对设备特性进行协商,形成一致的共识,这样才能正确的管理设备。
设备配置空间
设备配置空间通常用于配置不常变动的设备参数(属性),或者初始化阶段需要设置的设备参数。设备的特征位中包含表示配置空间是否存在的bit位,并可通过在特征位的末尾添加新的bit位来扩展配置空间。 设备驱动程序在初始化virtio设备时,需要根据virtio设备的特征位和配置空间来了解设备的特征,并对设备进行初始化。

virtio设备状态表示

virtio设备状态表示包括在设备初始化过程中用到的设备状态域,以及在设备进行I/O传输过程中用到的I/O数据访问状态信息和I/O完成情况等。
设备状态域 设备状态域包含对设备初始化过程中virtio设备的6种状态:

  • ACKNOWLEDGE(1):驱动程序发现了这个设备,并且认为这是一个有效的virtio设备;
  • DRIVER (2) : 驱动程序知道该如何驱动这个设备;
  • FAILED (128) : 由于某种错误原因,驱动程序无法正常驱动这个设备;
  • FEATURES_OK (8) : 驱动程序认识设备的特征,并且与设备就设备特征协商达成一致;
  • DRIVER_OK (4) : 驱动程序加载完成,设备可以正常工作了;
  • DEVICE_NEEDS_RESET (64) :设备触发了错误,需要重置才能继续工作。 在设备驱动程序对virtio设备初始化的过程中,需要经历一系列的初始化阶段,这些阶段对应着设备状态域的不同状态。
    I/O传输状态
    设备驱动程序控制virtio设备进行I/O传输过程中,会经历一系列过程和执行状态,包括 I/O请求状态、 I/O处理状态、 I/O完成状态、I/O错误状态、 I/O后续处理状态等。设备驱动程序在执行过程中,需要对上述状态进行不同的处理。 virtio设备进行I/O传输过程中,设备驱动会指出 I/O请求队列的当前位置状态信息,这样设备能查到I/O请求的信息,并根据 I/O请求进行I/O传输;而设备会指出 I/O完成队列的当前位置状态信息,这样设备驱动通过读取 I/O完成数据结构中的状态信息,就知道设备是否完成I/O请求的相应操作,并进行后续事务处理。 比如,virtio_blk设备驱动发出一个读设备块的I/O请求,并在某确定位置给出这个I/O请求的地址,然后给设备发出’kick’通知(读或写相关I/O寄存器映射的内存地址),此时处于I/O请求状态;设备在得到通知后,此时处于 I/O处理状态,它解析这个I/O请求,完成这个I/O请求的处理,即把磁盘块内容读入到内存中,并给出读出的块数据的内存地址,再通过中断通知设备驱动,此时处于 I/O完成状态;如果磁盘块读取发生错误,此时处于 I/O错误状态;设备驱动通过中断处理例程,此时处于 I/O后续处理状态,设备驱动知道设备已经完成读磁盘块操作,会根据磁盘块数据所在内存地址,把数据传递给文件系统进行进一步处理;如果设备驱动发现磁盘块读错误,则会进行错误恢复相关的后续处理。

virtio设备交互机制

virtio设备交互机制包括基于Notifications的事件通知和基于virtqueue虚拟队列的数据传输。事件通知是指设备和驱动程序必须通知对方,它们有数据需要对方处理。数据传输是指设备和驱动程序之间进行I/O数据(如磁盘块数据、网络包)传输。
Notification通知
驱动程序和设备在交互过程中需要相互通知对方:驱动程序组织好相关命令/信息要通知设备去处理I/O事务,设备处理完I/O事务后,要通知驱动程序进行后续事务,如回收内存,向用户进程反馈I/O事务的处理结果等。 驱动程序通知设备可用门铃 doorbell机制,即采用PIO或MMIO方式访问设备特定寄存器,QEMU进行拦截再通知其模拟的设备。设备通知驱动程序一般用中断机制,即在QEMU中进行中断注入,让CPU响应并执行中断处理例程,来完成对I/O执行结果的处理。
virtqueue虚拟队列
在virtio设备上进行批量数据传输的机制被称为虚拟队列(virtqueue),virtio设备的虚拟队列(virtqueue)可以由各种数据结构(如数组、环形队列等)来具体实现。每个virtio设备可以拥有零个或多个virtqueue,每个virtqueue占用多个物理页,可用于设备驱动程序给设备发I/O请求命令和相关数据(如磁盘块读写请求和读写缓冲区),也可用于设备给设备驱动程序发I/O数据(如接收的网络包)。

virtqueue虚拟队列

virtio协议中一个关键部分是virtqueue,在virtio规范中,virtqueue是virtio设备上进行批量数据传输的机制和抽象表示。在设备驱动实现和Qemu中virtio设备的模拟实现中,virtqueue是一种数据结构,用于设备和驱动程序中执行各种数据传输操作。 操作系统在Qemu上运行时,virtqueue是 virtio 驱动程序和 virtio 设备访问的同一块内存区域。 当涉及到 virtqueue 的描述时,有很多不一致的地方。有将其与vring(virtio-rings或VRings)等同表示,也有将二者分别单独描述为不同的对象。我们将在这里单独描述它们,因为vring是virtqueues的主要组成部分,是达成virtio设备和驱动程序之间数据传输的数据结构, vring本质是virtio设备和驱动程序之间的共享内存,但 virtqueue 不仅仅只有vring。 virtqueue由三部分组成(如下图所示):

  • 描述符表 Descriptor Table:描述符表是描述符为组成元素的数组,每个描述符描述了一个内存buffer 的address/length。而内存buffer中包含I/O请求的命令/数据(由virtio设备驱动填写),也可包含I/O完成的返回结果(由virtio设备填写)等。
  • 可用环 Available Ring:一种vring,记录了virtio设备驱动程序发出的I/O请求索引,即被virtio设备驱动程序更新的描述符索引的集合,需要virtio设备进行读取并完成相关I/O操作;
  • 已用环 Used Ring:另一种vring,记录了virtio设备发出的I/O完成索引,即被virtio设备更新的描述符索引的集合,需要vrtio设备驱动程序进行读取并对I/O操作结果进行进一步处理。 描述符表 Descriptor Table
    描述符表用来指向virtio设备I/O传输请求的缓冲区(buffer)信息,由 Queue Size 个Descriptor(描述符)组成。描述符中包括buffer的物理地址 – addr字段,buffer的长度 – len字段,可以链接到 next Descriptor 的next指针(用于把多个描述符链接成描述符链)。buffer所在物理地址空间需要设备驱动程序在初始化时分配好,并在后续由设备驱动程序在其中填写IO传输相关的命令/数据,或者是设备返回I/O操作的结果。多个描述符(I/O操作命令,I/O操作数据块,I/O操作的返回结果)形成的描述符链可以表示一个完整的I/O操作请求。
    可用环 Available Ring
    可用环在结构上是一个环形队列,其中的条目(item)仅由驱动程序写入,并由设备读出。可用环中的条目包含了一个描述符链的头部描述符的索引值。可用环用头指针(idx)和尾指针(last_avail_idx)表示其可用条目范围。virtio设备通过读取可用环中的条目可获取驱动程序发出的I/O操作请求对应的描述符链,然后virtio设备就可以进行进一步的I/O处理了。描述符指向的缓冲区具有可读写属性,可读的缓冲区用于Driver发送数据,可写的缓冲区用于接收数据。 比如,对于virtio-blk设备驱动发出的一个读I/O操作请求包含了三部分内容,由三个buffer承载,需要用到三个描述符 :(1) “读磁盘块”,(2)I/O操作数据块 – “数据缓冲区”,(3)I/O操作的返回结果 –“结果缓冲区”)。这三个描述符形成的一个完成的I/O请求链,virtio-blk从设备可通过读取第一个描述符指向的缓冲区了解到是“读磁盘块”操作,这样就可把磁盘块数据通过DMA操作放到第二个描述符指向的“数据缓冲区”中,然后把“OK”写入到第三个描述符指向的“结果缓冲区”中。
    已用环 Used Ring
    已用环在结构上是一个环形队列,其中的的条目仅由virtio设备写入,并由驱动程序读出。已用环中的条目也一个是描述符链的头部描述符的索引值。已用环也有头指针(idx)和尾指针(last_avail_idx)表示其已用条目的范围。 比如,对于virtio-blk设备驱动发出的一个读I/O操作请求(由三个描述符形成的请求链)后,virtio设备完成相应I/O处理,即把磁盘块数据写入第二个描述符指向的“数据缓冲区”中,可用环中对应的I/O请求条目“I/O操作的返回结果”的描述符索引值移入到已用环中,把“OK”写入到第三个描述符指向的“结果缓冲区”中,再在已用环中添加一个已用条目,即I/O操作完成信息;然后virtio设备通过中断机制来通知virtio驱动程序,并让virtio驱动程序读取已用环中的描述符,获得I/O操作完成信息,即磁盘块内容。 上面主要说明了virqueue中的各个部分的作用。对如何基于virtqueue进行I/O操作的过程还缺乏一个比较完整的描述。我们把上述基于virtqueue进行I/O操作的过程小结一下,大致需要如下步骤:
  1. 初始化过程:(驱动程序执行)
    1.1 virtio设备驱动在对设备进行初始化时,会申请virtqueue(包括描述符表、可用环、已用环)的内存空间;
    1.2 并把virtqueue中的描述符、可用环、已用环三部分的物理地址分别写入到virtio设备中对应的控制寄存器(即设备绑定的特定内存地址)中。至此,设备驱动和设备就共享了整个virtqueue的内存空间。
  2. I/O请求过程:(驱动程序执行)
    2.1 设备驱动在发出I/O请求时,首先把I/O请求的命令/数据等放到一个或多个buffer中;
    2.2 然后在描述符表中分配新的描述符(或描述符链)来指向这些buffer;
    2.3 再把描述符(或描述符链的首描述符)的索引值写入到可用环中,更新可用环的idx指针;
    2.4 驱动程序通过kick机制(即写virtio设备中特定的通知控制寄存器)来通知设备有新请求;
  3. I/O完成过程:(设备执行)
    3.1 virtio设备通过kick机制(知道有新的I/O请求,通过访问可用环的idx指针,解析出I/O请求;
    3.2 根据I/O请求内容完成I/O请求,并把I/O操作的结果放到I/O请求中相应的buffer中;
    3.3 再把描述符(或描述符链的首描述符)的索引值写入到已用环中,更新已用环的idx指针;
    3.4 设备通过再通过中断机制来通知设备驱动程序有I/O操作完成;
  4. I/O后处理过程:(驱动程序执行)
    4.1 设备驱动程序读取已用环的idx信息,读取已用环中的描述符索引,获得I/O操作完成信息。

Copyright © 2025 • Created by ArceOS Team

多层VM-Exit处理机制

众所周知,VM-Exit 对于获取客户虚拟机的运行状态以及与客户虚拟机进行交互至关重要。

VM-Exit 用于设备仿真和 vCPU 调度。

在 x86_64、aarch64 和 riscv64 架构中,VM-Exit 遵循相同的设计逻辑,但实现方式略有不同。

Inner-VCpu处理

在 x86_64 架构下,某些 VM-Exit 项目是特定于架构的(例如 VmxExitReason::CR_ACCESSVmxExitReason::CPUID)。在我们当前的设计中,这些 VM-Exit 由 [VmxVcpu] 本身通过 builtin_vmexit_handler 处理,而其他 VM-Exit 类型则由 vcpu.run() 返回,并由调用 vcpu.run() 的程序来处理。

#![allow(unused)]
fn main() {
impl<H: AxVMHal> VmxVcpu<H> {
    /// Handle vm-exits than can and should be handled by [`VmxVcpu`] itself.
    ///
    /// Return the result or None if the vm-exit was not handled.
    fn builtin_vmexit_handler(&mut self, exit_info: &VmxExitInfo) -> Option<AxResult> {
        // Following vm-exits are handled here:
        // - interrupt window: turn off interrupt window;
        // - xsetbv: set guest xcr;
        // - cr access: just panic;
        match exit_info.exit_reason {
            VmxExitReason::INTERRUPT_WINDOW => Some(self.set_interrupt_window(false)),
            VmxExitReason::PREEMPTION_TIMER => Some(self.handle_vmx_preemption_timer()),
            VmxExitReason::XSETBV => Some(self.handle_xsetbv()),
            VmxExitReason::CR_ACCESS => Some(self.handle_cr()),
            VmxExitReason::CPUID => Some(self.handle_cpuid()),
            _ => None,
        }
    }
}
}

此外,VmxExitReason::IoRead/IoWriteVmxExitReason::MsrRead/MsrWrite 也是 x86_64 特有的,但这些 VM-Exit 与端口 I/O 或 Msr 设备仿真相关,因此更适合在 vcpu.run() 之外处理。

Inner-VM处理

由于 axvm 中的虚拟机结构负责虚拟机的资源管理,例如模拟设备和地址空间(axaddrspace),所以更倾向于将与设备模拟相关的以及与页面错误相关的(数据中止)虚拟机退出保留在 axvm 内部。

也就是说,在虚拟机结构中提供一个 run_vcpu() 函数,并将与设备模拟相关的 VM 退出处理整合到 vm.run_vcpu()

#![allow(unused)]
fn main() {
impl<H: AxVMHal> AxVM<H> {
    pub fn run_vcpu(&self, vcpu_id: usize) -> AxResult<AxVCpuExitReason> {
        let vcpu = self
            .vcpu(vcpu_id)
            .ok_or_else(|| ax_err_type!(InvalidInput, "Invalid vcpu_id"))?;

        vcpu.bind()?;

        let exit_reason = loop {
            let exit_reason = vcpu.run()?;

            trace!("{exit_reason:#x?}");
            let handled = match &exit_reason {
                AxVCpuExitReason::MmioRead { addr: _, width: _ } => true,
                AxVCpuExitReason::MmioWrite {
                    addr: _,
                    width: _,
                    data: _,
                } => true,
                AxVCpuExitReason::IoRead { port: _, width: _ } => true,
                AxVCpuExitReason::IoWrite {
                    port: _,
                    width: _,
                    data: _,
                } => true,
                AxVCpuExitReason::NestedPageFault { addr, access_flags } => self
                    .inner_mut
                    .address_space
                    .lock()
                    .handle_page_fault(*addr, *access_flags),
                _ => false,
            };
            if !handled {
                break exit_reason;
            }
        };

        vcpu.unbind()?;
        Ok(exit_reason)
    }
}
}

因此,将设备模拟操作整合到 axvm 模块中,这样 vmm-app 只需要传入配置文件就可以,然后根据需要创建模拟设备实例,而不必关心模拟设备的特定运行时行为以及地址空间。

当然,这是在这些 VM-exit 不触发 vCPU 调度的条件下。

(Outer-VM) vmm-app处理

我们重用 task 来实现 vcpu 的运行时管理和调度。

这个逻辑是在 vmm-app 中实现的,因为 VMM 自然需要关注 vCPU 调度,并且它在 vmm-app 中整合了对 ArceOS 的 axtask 的依赖。

对于前两层没有处理的 VM-Exit,它们将从 vcpu::run() 的返回值中获取,并在这里进行处理,包括处理 hypercalls(在 VMM 中处理这个似乎也相当合理)和任何需要 vcpu 调度或 vcpu 退出的 VM-Exit 类型。

#![allow(unused)]
fn main() {
        let mut task = TaskInner::new(
            || {
                let curr = axtask::current();

                let vm = curr.task_ext().vm.clone();
                let vcpu = curr.task_ext().vcpu.clone();
                let vm_id = vm.id();
                let vcpu_id = vcpu.id();

                info!("VM[{}] Vcpu[{}] waiting for running", vm.id(), vcpu.id());
                wait_for(vm_id, || vm.running());

                info!("VM[{}] Vcpu[{}] running...", vm.id(), vcpu.id());

                loop {
                    match vm.run_vcpu(vcpu_id) {
                        // match vcpu.run() {
                        Ok(exit_reason) => match exit_reason {
                            AxVCpuExitReason::Hypercall { nr, args } => {
                                debug!("Hypercall [{}] args {:x?}", nr, args);
                            }
                            AxVCpuExitReason::FailEntry {
                                hardware_entry_failure_reason,
                            } => {
                                warn!(
                                    "VM[{}] VCpu[{}] run failed with exit code {}",
                                    vm_id, vcpu_id, hardware_entry_failure_reason
                                );
                            }
                            AxVCpuExitReason::ExternalInterrupt { vector } => {
                                debug!("VM[{}] run VCpu[{}] get irq {}", vm_id, vcpu_id, vector);
                            }
                            AxVCpuExitReason::Halt => {
                                debug!("VM[{}] run VCpu[{}] Halt", vm_id, vcpu_id);
                                wait(vm_id)
                            }
                            AxVCpuExitReason::Nothing => {}
                            _ => {
                                warn!("Unhandled VM-Exit");
                            }
                        },
                        Err(err) => {
                            warn!("VM[{}] run VCpu[{}] get error {:?}", vm_id, vcpu_id, err);
                            wait(vm_id)
                        }
                    }
                }
            },
            format!("VCpu[{}]", vcpu.id()),
            KERNEL_STACK_SIZE,
        );
```<style>
  .scroll-to-top {
    font-size: 2.5rem;
    width: 3.2rem;
    height: 3.2rem;
    display: none;
    align-items: center;
    justify-content: center;
    position: fixed;
    padding: 0.75rem;
    bottom: 4rem;
    right: calc(1.25rem + 90px + var(--page-padding));
    z-index: 999;
    cursor: pointer;
    border: none;
    color: var(--bg);
    background: var(--fg);
    border-radius: 50%;
  }
  .scroll-to-top.hidden {
    display: none;
  }
  .scroll-to-top i {
    transform: translateY(-2px);
  }
  @media (min-width: 1080px) {
    .scroll-to-top {
      display: flex;
    }
  }
</style>
<button type="button" aria-label="scroll-to-top" class="scroll-to-top hidden" onclick="scrollToTop()">
  <i class="fa fa-angle-up"></i>
</button>
<script>
  const scrollToTop = () => window.scroll({ top: 0, behavior: "smooth" });
  window.addEventListener("scroll", () => {
    const button = document.querySelector(".scroll-to-top");
    button.classList.toggle("hidden", window.scrollY < 200);
  });
</script>
<style>
  .announcement-banner {
    z-index: 150;
    color: var(--fg);
    position: relative;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 0;
    padding: 1rem 3.5rem;
    background: repeating-linear-gradient(
      45deg,
      var(--site-announcement-bar-stripe-color1),
      var(--site-announcement-bar-stripe-color1) 20px,
      var(--site-announcement-bar-stripe-color2) 10px,
      var(--site-announcement-bar-stripe-color2) 40px
    );
  }
  .announcement-banner {
    --site-announcement-bar-stripe-color1: #e5e7eb;
    --site-announcement-bar-stripe-color2: #d1d5db;
  }
  .announcement-banner[data-theme="ocean"] {
    --site-announcement-bar-stripe-color1: #86b2f9;
    --site-announcement-bar-stripe-color2: #7298ea;
  }
  .announcement-banner[data-theme="forest"] {
    --site-announcement-bar-stripe-color1: #97f5d6;
    --site-announcement-bar-stripe-color2: #6de0bf;
  }
  .announcement-banner[data-theme="lava"] {
    --site-announcement-bar-stripe-color1: #fea3a3;
    --site-announcement-bar-stripe-color2: #e57e7e;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner {
    --site-announcement-bar-stripe-color1: #1f2937;
    --site-announcement-bar-stripe-color2: #111827;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="ocean"] {
    --site-announcement-bar-stripe-color1: #2563eb;
    --site-announcement-bar-stripe-color2: #1d4ed8;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="forest"] {
    --site-announcement-bar-stripe-color1: #22d3a5;
    --site-announcement-bar-stripe-color2: #0fbf8f;
  }
  html:is(.navy, .coal, .ayu) .announcement-banner[data-theme="lava"] {
    --site-announcement-bar-stripe-color1: #f87171;
    --site-announcement-bar-stripe-color2: #ef4444;
  }
  .announcement-banner p {
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    text-align: center;
    white-space: nowrap;
    text-overflow: ellipsis;
    text-wrap: balance;
  }
  .announcement-banner button {
    top: 50%;
    right: 1rem;
    position: absolute;
    transform: translateY(-50%);
    width: 3rem;
    height: 3rem;
    cursor: pointer !important;
    border: none;
    font-weight: 900;
    border-radius: 50%;
    background-color: transparent;
  }
</style>
<div style="display: none" data-id="0.2.11" class="announcement-banner" data-theme="default">
  <p><em>正在逐步完善中。。。</em></p>

  <button type="button">X</button>
</div>
<script>
  (() => {
    const banner = document.querySelector(".announcement-banner");
    const id = banner.getAttribute("data-id");
    const message = banner.querySelector("p").textContent;
    const localData = JSON.parse(localStorage.getItem("mdbook-announcement-banner"));
    if (!localData || localData.id !== id || localData.hide !== true) {
      banner.style.display = "flex";
      const page = document.querySelector(".page");
      page.parentNode.insertBefore(banner, page);
      banner.querySelector("button").addEventListener("click", () => {
        banner.remove();
        localStorage.setItem("mdbook-announcement-banner", JSON.stringify({ id, hide: true, message }));
      });
    }
  })();
</script>
<style>
  .giscus {
    margin-top: 6rem;
  }
</style>
<script
  src="https://giscus.app/client.js"
  data-repo="arceos-hypervisor/doc"
  data-repo-id="R_kgDOLMHfvQ"
  data-category="Comments"
  data-category-id="DIC_kwDOLMHfvc4CoqAB"
  data-mapping="title"
  data-strict="0"
  data-reactions-enabled="1"
  data-emit-metadata="0"
  data-input-position="bottom"
  data-theme="light"
  data-lang="zh-CN"
  data-loading="eager"
  crossorigin="anonymous"
  async
></script>
<script>
  (() => {
    const giscusScript = document.querySelector("script[data-repo][data-repo-id]");
    if (giscusScript?.getAttribute("data-theme") !== "book") return;
    const mapTheme = (theme) => (theme === "light" || theme === "rust" ? "light" : "dark");
    const bookTheme = localStorage.getItem("mdbook-theme") || html.getAttribute("class");
    giscusScript.setAttribute("data-theme", mapTheme(bookTheme));
    document.querySelectorAll("button[role='menuitem'].theme").forEach((btn) => {
      btn.addEventListener("click", (event) => {
        theme = mapTheme(event.target.id);
        const iframe = document.querySelector("iframe.giscus-frame");
        if (iframe) iframe.contentWindow.postMessage({ giscus: { setConfig: { theme } } }, "*");
      });
    });
  })();
</script>
<style>
  footer {
    text-align: center;
    text-wrap: balance;
    margin-top: 5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
  footer p {
    margin: 0;
  }
</style>
<footer><p>Copyright © 2025 • Created by ArceOS Team</p>
</footer>
}

待添加。。。

Copyright © 2025 • Created by ArceOS Team

构建

Copyright © 2025 • Created by ArceOS Team

Copyright © 2025 • Created by ArceOS Team

客户机适配

Copyright © 2025 • Created by ArceOS Team

在 QEMU-ARM 平台上启动两个nimbos,并分别注入 timer 中断

Clone代码

新建创建项目的clone.sh,然后bash clone.sh自动创建项目

#!/bin/bash

BRANCH="-b debin/2vm_timer"

mkdir -p crates

git clone $BRANCH https://github.com/arceos-hypervisor/arceos-umhv.git 

cd arceos-umhv

# 克隆arceos主仓库
git clone $BRANCH https://github.com/arceos-hypervisor/arceos.git ../arceos

# 克隆其他仓库到crates目录
REPOS=(
    "axvm"
    "axvcpu"
    "axaddrspace"
    "arm_vcpu"
    "axdevice"
    "arm_vgic"
    "arm_gicv2"
    "axdevice_crates"
)

for repo in "${REPOS[@]}"; do
    git clone $BRANCH "https://github.com/arceos-hypervisor/${repo}.git" "../crates/${repo}"
done

echo "所有仓库克隆完成!"

# 创建临时文件
temp_file=$(mktemp)

# 要添加的新内容
cat > "$temp_file" << 'EOF'
[patch."https://github.com/arceos-hypervisor/arceos.git".axstd]
path = "../arceos/ulib/axstd"
[patch."https://github.com/arceos-hypervisor/arceos.git".axhal]
path = "../arceos/modules/axhal"
[patch."https://github.com/arceos-hypervisor/axvm.git".axvm]
path = "../crates/axvm"
[patch."https://github.com/arceos-hypervisor/axvcpu.git".axvcpu]
path = "../crates/axvcpu"
[patch."https://github.com/arceos-hypervisor/axaddrspace.git".axaddrspace]
path = "../crates/axaddrspace"
[patch."https://github.com/arceos-hypervisor/arm_vcpu.git".arm_vcpu]
path = "../crates/arm_vcpu"
[patch."https://github.com/arceos-hypervisor/axdevice.git".axdevice]
path = "../crates/axdevice"
[patch."https://github.com/arceos-hypervisor/arm_vgic.git".arm_vgic]
path = "../crates/arm_vgic"
[patch."https://github.com/arceos-hypervisor/axdevice_crates.git".axdevice_base]
path = "../crates/axdevice_crates/axdevice_base"
[patch."https://github.com/arceos-hypervisor/arm_gicv2.git".arm_gicv2]
path = "../crates/arm_gicv2"

EOF

# 将原文件内容追加到临时文件
cat Cargo.toml >> "$temp_file"

# 将临时文件移回原文件
mv "$temp_file" Cargo.toml

echo "成功更新 Cargo.toml"

cd .. && mkdir .vscode

cat > .vscode/settings.json << 'EOF'
{
    "rust-analyzer.cargo.target": "aarch64-unknown-none-softfloat",
    "rust-analyzer.check.allTargets": false,
    "rust-analyzer.cargo.features": ["irq", "hv"],
    "rust-analyzer.cargo.extraEnv": {
        "RUSTFLAGS": "--cfg platform_family=\"aarch64-qemu-virt\""
    }
}
EOF

编译nimbos

因为加载到任意地址的功能还没有实现,所以只能通过硬配置来做,得单独编译两个nimbos

nimbos(VM1)

git clone https://github.com/arceos-hypervisor/nimbos.git

nimbos(VM2)

git clone -b debin/0x800 https://github.com/arceos-hypervisor/nimbos.git

创建disk.img文件

生成一个disk.img,然后将编译好的nimbos.bin重命名并放入里面

cd arceos-umhv/arceos-vmm/
mkdir mnt
make disk_img
sudo mount disk_img mnt
cp nimbos_0x408_0000.bin ./mnt
cp nimbos_0x808_0000.bin ./mnt
cd .. && sudo umount mnt

启动VMMS

cd arceos-umhv/arceos-vmm/
bash run.sh

# 在qemu启动后,打开第二个终端使用telnet连接串口2
telnet localhost 4321

就可以正常注入timer了

VM1

NN   NN  iii               bb        OOOOO    SSSSS
NNN  NN       mm mm mmmm   bb       OO   OO  SS
NN N NN  iii  mmm  mm  mm  bbbbbb   OO   OO   SSSSS
NN  NNN  iii  mmm  mm  mm  bb   bb  OO   OO       SS
NN   NN  iii  mmm  mm  mm  bbbbbb    OOOO0    SSSSS
              ___    ____    ___    ___
             |__ \  / __ \  |__ \  |__ \
             __/ / / / / /  __/ /  __/ /
            / __/ / /_/ /  / __/  / __/
           /____/ \____/  /____/ /____/

arch = aarch64
platform = qemu-virt-arm
build_mode = release
log_level = info

Initializing kernel heap at: [0xffff0000401200e0, 0xffff0000405200e0)
[INFO  nimbos] Logging is enabled.
Initializing frame allocator at: [PA:0x40521000, PA:0x48000000)
Mapping .text: [0xffff000040080000, 0xffff000040094000)
Mapping .rodata: [0xffff000040094000, 0xffff00004009b000)
Mapping .data: [0xffff00004009b000, 0xffff00004011a000)
Mapping .bss: [0xffff00004011e000, 0xffff000040521000)
Mapping boot stack: [0xffff00004011a000, 0xffff00004011e000)
Mapping physical memory: [0xffff000040521000, 0xffff000048000000)
[  0.280129 1:9 arceos_vmm::vmm::vcpus:243] VM[2] Vcpu[0] waiting for running
[  0.280591 1:9 arceos_vmm::vmm::vcpus:246] VM[2] Vcpu[0] running...
Mapping MMIO: [0xffff000009000000, 0xffff000009001000)
Mapping MMIO: [0xffff000008000000, 0xffff000008020000)
Initializing drivers...
[  0.291414 0:8 arm_vgic::interrupt:76] Setting interrupt 30 enable to true
Initializing task manager...
/**** APPS ****
cyclictest
exit
fantastic_text
forktest
forktest2
forktest_simple
forktest_simple_c
forktree
hello_c
hello_world
matrix
poweroff
sleep
sleep_simple
stack_overflow
thread_simple
user_shell
usertests
yield
**************/
Running tasks...
test kernel task: pid = TaskId(2), arg = 0xdead
[  0.294993 INFO  nimbos::task::structs][0:2] task exit with code 0
test kernel task: pid = TaskId(3), arg = 0xbeef
[  0.296126 INFO  nimbos::task::structs][0:3] task exit with code 0
[  0.296457 INFO  nimbos::arch::aarch64::context][0:4] user task start: entry=0x211cfc, ustack=0xfffffffff000, kstack=0xffff000040138000
Rust user shell
>> [  0.298106 1:9 arm_vgic::interrupt:76] Setting interrupt 30 enable to true
[  3.349047 0:8 axhal::irq:23] Unhandled IRQ 33
s[  3.583202 0:8 axhal::irq:23] Unhandled IRQ 33
l[  3.898013 0:8 axhal::irq:23] Unhandled IRQ 33
e[  4.067715 0:8 axhal::irq:23] Unhandled IRQ 33
e[  4.214772 0:8 axhal::irq:23] Unhandled IRQ 33
p[  4.829631 0:8 axhal::irq:23] Unhandled IRQ 33

[  4.830637 INFO  nimbos::arch::aarch64::context][0:5] user task start: entry=0x211d28, ustack=0xffffffffee10, kstack=0xffff000040134000
[  4.832135 INFO  nimbos::arch::aarch64::context][0:6] user task start: entry=0x210744, ustack=0xffffffffef40, kstack=0xffff000040130000
sleep 1 x 1 seconds.
sleep 2 x 1 seconds.
sleep 3 x 1 seconds.
sleep 4 x 1 seconds.
sleep 5 x 1 seconds.
[  9.879593 INFO  nimbos::task::structs][0:6] task exit with code 0
use 5048222 usecs.
sleep passed!
[  9.880587 INFO  nimbos::task::structs][0:5] task exit with code 0
Shell: Process 5 exited with code 0
>> QEMU: Terminated

VM2

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
a
NN   NN  iii               bb        OOOOO    SSSSS
NNN  NN       mm mm mmmm   bb       OO   OO  SS
NN N NN  iii  mmm  mm  mm  bbbbbb   OO   OO   SSSSS
NN  NNN  iii  mmm  mm  mm  bb   bb  OO   OO       SS
NN   NN  iii  mmm  mm  mm  bbbbbb    OOOO0    SSSSS
              ___    ____    ___    ___
             |__ \  / __ \  |__ \  |__ \
             __/ / / / / /  __/ /  __/ /
            / __/ / /_/ /  / __/  / __/
           /____/ \____/  /____/ /____/

arch = aarch64
platform = qemu-virt-arm
build_mode = release
log_level = info

Initializing kernel heap at: [0xffff0000801200e0, 0xffff0000805200e0)
[INFO  nimbos] Logging is enabled.
Initializing frame allocator at: [PA:0x80521000, PA:0x88000000)
Mapping .text: [0xffff000080080000, 0xffff000080094000)
Mapping .rodata: [0xffff000080094000, 0xffff00008009b000)
Mapping .data: [0xffff00008009b000, 0xffff00008011a000)
Mapping .bss: [0xffff00008011e000, 0xffff000080521000)
Mapping boot stack: [0xffff00008011a000, 0xffff00008011e000)
Mapping physical memory: [0xffff000080521000, 0xffff000088000000)
Mapping MMIO: [0xffff000009000000, 0xffff000009001000)
Mapping MMIO: [0xffff000008000000, 0xffff000008020000)
Initializing drivers...
Initializing task manager...
/**** APPS ****
cyclictest
exit
fantastic_text
forktest
forktest2
forktest_simple
forktest_simple_c
forktree
hello_c
hello_world
matrix
poweroff
sleep
sleep_simple
stack_overflow
thread_simple
user_shell
usertests
yield
**************/
Running tasks...
test kernel task: pid = TaskId(2), arg = 0xdead
[  0.301555 INFO  nimbos::task::structs][0:2] task exit with code 0
test kernel task: pid = TaskId(3), arg = 0xbeef
[  0.302448 INFO  nimbos::task::structs][0:3] task exit with code 0
[  0.302702 INFO  nimbos::arch::aarch64::context][0:4] user task start: entry=0x211cfc, ustack=0xfffffffff000, kstack=0xffff000080138000
Rust user shell
>> sleep
[ 13.410960 INFO  nimbos::arch::aarch64::context][0:5] user task start: entry=0x211d28, ustack=0xffffffffee10, kstack=0xffff000080134000
[ 13.412543 INFO  nimbos::arch::aarch64::context][0:6] user task start: entry=0x210744, ustack=0xffffffffef40, kstack=0xffff000080130000
sleep 1 x 1 seconds.
sleep 2 x 1 seconds.
sleep 3 x 1 seconds.
sleep 4 x 1 seconds.
sleep 5 x 1 seconds.
[ 18.460517 INFO  nimbos::task::structs][0:6] task exit with code 0
use 5048755 usecs.
sleep passed!
[ 18.461516 INFO  nimbos::task::structs][0:5] task exit with code 0
Shell: Process 5 exited with code 0
>> Connection closed by foreign host.

多虚拟串口支持

如果不能正常启动,考虑使用修改版的qemu

git clone https://github.com/luodeb/qemu.git
./configure --enable-slirp --target-list=aarch64-softmmu,aarch64-linux-user --prefix=/path/to/qemu-9.2.0
make -j
make install
串口地址中断号offset
UART00x090000001
UART10x090400008
UART20x0910000021
UART30x0914000022

Copyright © 2025 • Created by ArceOS Team

简介

AxVisor 的开发以 Github 的 Project 和 Milestones 作为开发任务和目标的管理及跟踪工具。Github 的组织结构框图如下图所示。

Github

GitHub Project

GitHub Project 是一个看板(Kanban)风格的任务管理工具,与 Issues、Pull Requests (PR)、仓库深度集成。Project 属于个人或者组织,不同的仓库可以共用同一个,它可以将不同仓库的 Issues 组织在一起提供管理。

Milestones

GitHub 的 Milestone 主要用于指定仓库的 Issues 和 Pull Requests (PRs) 的版本或阶段管理。每个仓库的 Milestones 是独立的,它只能组织当前仓库里的各个 Issues 来提供管理。

Issues

Issues 是 Project 和 Milestones 管理的基本单元,其分为了不同的类型,可以是 Task 也可以是 BUG。GitHub Project 和 Milestone 通过组织管理各个 Issues 的方式来提供开发任务和目标的管理及跟踪。

AxVisor 开发

AxVisor 的开发目前主要涉及 axvisordoc 这两个仓库,他们共用 ArceOS Hypervisor Tasks 这个 Project 来组织所有的开发任务。而 axvisordoc 中各有自己的 Milestone 来追踪阶段目标。

axvisor

开发任务

两个仓库中的每个 Issue 都是一个开发的任务(以任务类型区分),其会有一个或多个人员负责开发跟进。

task_new

  1. Issues 的内容描述基本开发情况

  2. 以在 Issues 中评论的形式记录遇到问题

任务完成

每当完成一个 Issue 之后,要求在 Issue 中以评论的形式列出相关 Commit 的 HASH 作为自己任务的记录,然后关闭 Issue 即可

task_finish

Copyright © 2025 • Created by ArceOS Team

简介

AxVisor Book 是使用 mdbook 搭建的统一模块化虚拟机管理程序 AxVisor 的在线文档。mdbook 是 Rust 官方开发的一个用于创建、维护和部署静态网站的网站生成工具。

开发

开发环境

默认安装 Rust 后并不会同步安装 mdbook 工具,我们需要使用 cargo install mdbook mdbook-pagetoc mdbook-embedify mdbook-i18n-helpers i18n-report 手动进行安装它及本文档使用的相关插件,mdbook 可执行文件会被放到 .cargo/bin 目录中。

编写文档

mdbook 是一个将 Markdown 文档作为源文件的文档系统,因此,我们只需要以 Markdown 语法编写源文件即可。

源码中的 ./src/SUMMARY.md 是文档的目录,当新增了源文件之后,需要在其中添加上对应的文件路径

构建

mdbook 是一个命令行工具,使用命令 mdbook build 就可以自动生成对应的静态网页。使用命令 mdbook serve 可以在本地启动一个 HTTP 服务端,然后我们就可以在浏览器 http://localhost:3000 中预览文档。其他参数如下:

$ mdbook -h
Creates a book from markdown files

Usage: mdbook [COMMAND]

Commands:
  init         Creates the boilerplate structure and files for a new book
  build        Builds a book from its markdown files
  test         Tests that a book's Rust code samples compile
  clean        Deletes a built book
  completions  Generate shell completions for your shell to stdout
  watch        Watches a book's files and rebuilds it on changes
  serve        Serves a book at http://localhost:3000, and rebuilds it on changes
  help         Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

For more information about a specific command, try `mdbook <command> --help`
The source code for mdBook is available at: https://github.com/rust-lang/mdBook

部署

目前,AxVisor 的文档网站托管在了 GitHub Pages 上:https://arceos-hypervisor.github.io/doc/ ,仓库默认配置为通过 GitHub Action 进行部署(Github 本身支持 Actions 和 Branch 两种部署方式,当前使用 Branch 方式),当把源码提交到仓库的 main 分支之后将自动触发 GitHub Action 进行部署。

如何贡献

欢迎 FORK 本仓库,然后提交 PR。

许可协议

AxVisor Book 使用如下开源协议:

  • Apache-2.0
  • MulanPubL-2.0
  • MulanPSL2
  • GPL-3.0-or-later

Copyright © 2025 • Created by ArceOS Team

简介

本文档使用了 Google 提供的 mdbook-i18n-helpers 来提供国际化支持。mdbook-i18n-helpers 中主要包含了 mdbook-xgettext 和 mdbook-gettext 这两个可执行程序,其中,mdbook-xgettext 是一个 mdbook 渲染器,帮助我们提取需要翻译的源文本,而 mdbook-gettext 是一个 mdbook 预处理器,负责将翻译后的文本重新注入到 Markdown 文件中。

翻译

mdbook-i18n-helpers 使用的是 GNU 的 Gettext 系统进行翻译。GNU gettext 是一组实用程序,它提供了一个框架,在这个框架中我们可以方便的进行多种语言的翻译。该系统广泛用于开源软件的翻译,并且对于文档的翻译也相当有效。

.po 文件

在 GNU gettext 中,每种语言都对应一个以 ISO 639 中定义的语言名为文件名的 .po 文件,例如,en.po,其中记录了所有原始需要翻译的内容以及对应的翻译后的内容。因此,我们的翻译工作无需复制原始的 Markdown 文件并手动跟踪更改,而只需要修改.po 文件即可。

新增翻译

当我们的原始文档编写完成之后,首先需要创建所需要的语言对应的 .po 文件,以实现国际化支持。mdbook-xgettext 可以帮助我们提取原始文档中所有需要翻译的内容,并生成一个 .pot 文件(一个 PO 模板),我们以此为基础创建出不同语言的 .po 文件即可。

  1. 执行 MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' mdbook build -d po 自动提取原始文档中所有需要翻译的内容并生成 messages.pot 文件。命令执行之后就会生成默认的 po/messages.pot 文件

    如果在 book.toml 文件中添加了 output.xgettext.pot-file = messages.pot 配置项,则每次执行 mdbook build 时都会自动输出 book/xgettext/messages.pot

    不要编辑 po/messages.pot 文件,也不要将它签入你的存储库,因为它是完全从源 Markdown 文件生成。

  2. 执行 msginit -i po/messages.pot -l xx -o po/xx.po 生成对应语言的 .po 文件。其中,xx 为 ISO 639 中定义的语言名,例如,en、zh-CN 等

    可以直接将 po/messages.pot 修改为 po/xx.po,但是必须手动将标题(第一个带有 msgid "" 条目)更新为正确的语言

  3. 手动翻译 po/xx.po 文件中各条 msgstr 对应的内容即可。其中,xx 为 ISO 639 中定义的语言名,例如,en、zh-CN 等

    不要编辑 po/xx.po 文件中的 msgid 条目

更新翻译

随着文档内容的逐步增加或者减少,对应的翻译内容也需要进行对应变动。此时,我们需要使用 mdbook-xgettext 重新提取文档中所有需要翻译的内容生成 .pot 文件,然后根据 .pot 文件更新各种语言对应的 .po 文件,而不是简单的直接编辑原始文档和.po 文件。

  1. 执行 MDBOOK_OUTPUT='{"xgettext": {"pot-file": "messages.pot"}}' mdbook build -d po 自动提取原始文档中所有需要翻译的内容并生成 messages.pot 文件。命令执行之后就会生成默认的 po/messages.pot 文件

    如果在 book.toml 文件中添加了 output.xgettext.pot-file = messages.pot 配置项,则每次执行 mdbook build 时都会自动输出 book/xgettext/messages.pot

    不要编辑 po/messages.pot 文件,也不要将它签入你的存储库,因为它是完全从源 Markdown 文件生成。

  2. 执行 msgmerge --update po/xx.po po/messages.pot 更新对应语言的 .po 文件。其中,xx 为 ISO 639 中定义的语言名,例如,en、zh-CN 等

    未更改的内容将保持原样,删除的讯息会被标记为旧内容,更新的讯息则标注为“fuzzy”。“fuzzy” 条目会沿用之前的翻译,我们需要检查并根据需要更新它,再移除 “fuzzy” 标记。

  3. 手动翻译 po/xx.po 文件中有变动的各条 msgstr 对应的内容即可。其中,xx 为 ISO 639 中定义的语言名,例如,en、zh-CN 等

    不要编辑 po/xx.po 文件中的 msgid 条目

应用翻译

应用我们翻译好的内容是通过 mdbook-gettext 预处理器完成的。

配置

首先,需要将如下配置项添加到我们的 book.toml 文件中,以便在项目中启用它。这将使能在执行完诸如 {{ #include }} 等操作后对源码运行 mdbook-gettext,从而使得翻译包含的源代码成为可能。

[preprocessor.gettext]
after = ["links"]

如果要在每次更改 po/xx.po 文件时自动重新加载我们的文档,则需要将如下配置项添加到项目的 book.toml 文件中:

[build]
extra-watch-dirs = ["po"]

构建

增加了以上配置项之后,每当我们执行 mdbook build 构建时,将自动根据 book.toml 文件中的 book.language 指定的语言选择并应用对应的 po/xx.po 并在 book 目录下生成对应语言的文档。

如果没有设置语言或者找不到与该语言对应的 .po 文件,那么将自动使用未翻译的内容

如果要单独构建指定语言的文档,则可以使用 MDBOOK_BOOK__LANGUAGE=xx mdbook build -d book/xx 命令在 book/xx 目录中生成 xx 语言的文档 。其中,xx 为 ISO 639 中定义的语言名,例如,en、zh-CN 等

每种语言都对应一个独立的完整文档

在线预览

由于构建是根据 book.toml 文件中的 book.language 指定的语言生成的对应语言的文档,因此,使用 mdbook serve 名在本地在线查看的也是对应语言的文档。

如果要单独查看指定语言的文档,则可以使用 MDBOOK_BOOK__LANGUAGE=xx mdbook serve -d book/xx 命令来在线查看。其中,xx 为 ISO 639 中定义的语言名,例如,en、zh-CN 等

动态切换

多语言的支持需要动态切换,而不是分别独立部署。但是,由于 mdbook 本身不支持国际化,其默认主题中也没有用于切换不同语言的菜单,因此,我们需要修改默认的主题模板文件才能实现动态切换不同的语言。

index.hbs

index.hbs 是 mdbook 的生成的所有页面的基础 HTML 模板,我们可以创建 theme/index.hbs 文件然后修改其中的内容,在构建时,mdbook 将自动使用当前目录下的 theme/index.hbs 文件,从而实现自定义主题!

对于要支持国际化的需求,我们首先需要在原在 index.hbs 的基础上,在顶部菜单栏的右侧增加一个切换不同语言的图标以及各种语言的菜单,并实现用户单击语言图标显示语言菜单以及点击语言菜单项时切换对应的语言的文档的功能。其次,当切换对应语言之后,还需要同步更新 URL 的路径并刷新显示。具体修改参见 theme/index.hbs 文件!

增加样式

为了美化样式,我们还需要增加了一个 CSS 样式文件 theme/language-picker.css,用于控制新增的语言选择菜单的样式。而且,要使该文件生效,需要在 book.toml 文件中的 output.html.additional-css 中显式指明该文件。

部署 Github Pages

mdbook 生成的文档本身就支持部署在 Github Pages,但是,在我们增加了国际化之后需要增加一些额外的执行步骤。

在没有国际化时,我们只需要通过 mdbook build 构建出文档,而支持国际化之后,则需要额外构建出每个语言对应的文档,并将构建出的不同语言的文档放到原构建目录中以 ISO 639 中定义的语言名为文件夹名字的子目录中即可(这是由于我们修改的 index.hbs 中强制写死了文件名)。具体修改见 .github/workflows/build_deploy.yml

局限性

  1. 由于 mdbook 本身不支持国际化,因此需要对 mdbook 做比价多的改动

  2. mdbook 的搜索不支持中文

  3. 目前无法再本地同时预览多个语言的版本(我们添加的动态切换无效),这是由于 mdbook 会清理掉其他语言的文档。暂时的解决方法是,在启动 mdbook serve 之后,再把构建的其他语言的文档放到 book 目录下

Copyright © 2025 • Created by ArceOS Team

使用

Copyright © 2025 • Created by ArceOS Team

管理

Copyright © 2025 • Created by ArceOS Team

部署

Copyright © 2025 • Created by ArceOS Team

Copyright © 2025 • Created by ArceOS Team

AxVisor Design Discussions

Copyright © 2025 • Created by ArceOS Team