img

3.1 设备内存分布

项目使用qemuaarch64virt机器来运行

CPU="cortex-a72"
QCPU="cortex-a72"
QEMU="qemu-system-aarch64"
GIC_VERSION=3
MACHINE="virt,gic-version=$GIC_VERSION,virtualization=on"
NCPU=4
QEMUOPTS="-cpu $QCPU -machine $MACHINE -smp $NCPU -m 128M -nographic \
-bios ./u-boot/u-boot.bin \
-device loader,file=./build/X-Hyper_Uimage,addr=0x40200000,force-raw=on"

其中virt的内存分布如下,定义在qemu源码的hw/arm/virt.c

static const MemMapEntry base_memmap[] = {
/* Space up to 0x8000000 is reserved for a boot ROM */
[VIRT_FLASH] = { 0, 0x08000000 },
[VIRT_CPUPERIPHS] = { 0x08000000, 0x00020000 },
/* GIC distributor and CPU interfaces sit inside the CPU peripheral space */
[VIRT_GIC_DIST] = { 0x08000000, 0x00010000 },
[VIRT_GIC_CPU] = { 0x08010000, 0x00010000 },
[VIRT_GIC_V2M] = { 0x08020000, 0x00001000 },
[VIRT_GIC_HYP] = { 0x08030000, 0x00010000 },
[VIRT_GIC_VCPU] = { 0x08040000, 0x00010000 },
/* The space in between here is reserved for GICv3 CPU/vCPU/HYP */
[VIRT_GIC_ITS] = { 0x08080000, 0x00020000 },
/* This redistributor space allows up to 2*64kB*123 CPUs */
[VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 },
[VIRT_UART] = { 0x09000000, 0x00001000 },
[VIRT_RTC] = { 0x09010000, 0x00001000 },
[VIRT_FW_CFG] = { 0x09020000, 0x00000018 },
[VIRT_GPIO] = { 0x09030000, 0x00001000 },
[VIRT_SECURE_UART] = { 0x09040000, 0x00001000 },
[VIRT_SMMU] = { 0x09050000, 0x00020000 },
[VIRT_PCDIMM_ACPI] = { 0x09070000, MEMORY_HOTPLUG_IO_LEN },
[VIRT_ACPI_GED] = { 0x09080000, ACPI_GED_EVT_SEL_LEN },
[VIRT_NVDIMM_ACPI] = { 0x09090000, NVDIMM_ACPI_IO_LEN},
[VIRT_PVTIME] = { 0x090a0000, 0x00010000 },
[VIRT_SECURE_GPIO] = { 0x090b0000, 0x00001000 },
[VIRT_MMIO] = { 0x0a000000, 0x00000200 },
/* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */
[VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 },
[VIRT_SECURE_MEM] = { 0x0e000000, 0x01000000 },
[VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 },
[VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 },
[VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 },
/* Actual RAM size depends on initial RAM and device memory settings */
[VIRT_MEM] = { GiB, LEGACY_RAMLIMIT_BYTES },
};
  • -bios指定的uboot的镜像会被加载到FLASH处执行
  • DRAM的起始地址为GIBGIB的值为:#define GiB (INT64_C(1) << 30)0x4000 0000
  • -device loader指令可以把X-Hyper_Uimage镜像加载到0x40200000

3.2 uboot配置

uboot的下载编译执行脚本如下:

#!/bin/bash

if [ -d "./u-boot" ]; then
rm -rf ./u-boot
fi

git clone --depth=1 https://github.com/u-boot/u-boot.git

cd u-boot
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- qemu_arm64_defconfig

# 借助sed命令修改配置文件,把预启动命令从原本的 "usb start" 替换成 "bootm 0x40200000 - ${fdtcontroladdr}"。
# 这一修改的目的是让 U-Boot 启动时直接从内存地址0x40200000加载内核镜像。
# Set preboot
sed -i 's/CONFIG_PREBOOT="usb start"/CONFIG_PREBOOT="bootm 0x40200000 - ${fdtcontroladdr}"/g' "./.config"
make CROSS_COMPILE=aarch64-linux-gnu- -j4
  • sed -i 's/CONFIG_PREBOOT="usb start"/CONFIG_PREBOOT="bootm 0x40200000
  • 这个修改目的是让uboot先使用bootm从内存地址0x40200000加载镜像

3.3 Xhyper镜像生成

#!/bin/bash

load_addr=0x40200000
entry_point=0x40200000
output_name=X-Hyper_Uimage
image_name=X-Hyper

cd guest
make clean
make

cd ..
make
cd build
# mkimage
mkimage -A arm64 -O linux -C none -a $load_addr -e $entry_point -d ${image_name} ${output_name}

# -A arm64:指定架构为 ARM64。
# -O linux:指定操作系统为 Linux。
# -C none:不压缩内核(若内核已压缩,可改为-C gzip)。
# -a $load_addr:设置加载地址为 0x40200000。
# -e $entry_point:设置入口点地址为 0x40200000。
# -d ${image_name}:输入文件为X-Hyper(编译生成的内核)。
# ${output_name}:输出 UImage 文件名为X-Hyper_Uimage。
  • 使用mkimage来生成*X-Hyper_Uimage*这样得到的镜像会包含一个UImage Header

img

  • 此头部的信息可以使用如下的命令看见

img

Ulmage Header 中关键的两个值:

  • Load Address :即 U-boot 加载管理器镜像的内存地址,在 U-boot 中使用 bootm 加载Ulmage 后,U-boot 会解析 Ulmage 的 Header,并根据 Load Address 将 Uimage 的虚拟机管理器镜像文件重新加载到 0x40200000 处;
  • Entry Point: U-boot 加载完虚拟机管理器镜像文件到指定内存地址后,再根据Entry Point 跳转到0x40200000 处,也就是虚拟机管理器镜像文件中的的start 处开始执行程序;

流程如下:

img

3.4 Guest管理

3.4.1 Guest os 镜像生成

Guest os 的镜像文件编译后链接首先生成Guest_VM.elf

img

然后通过objcopy指令转换成去除elf头部的二进制可执行文件Guest_VM,最后通过ld指令将Guest_VM转换成可重定位目标文件Guest_VM.o

img

Xhyper通过链接Guest_VM.oGuest os的镜像加入到xhper的镜像文件中:

img

  • 其中Guest_VM.o的链接地址定义再layout.h中:#define HIMAGE_VADDR 0x40200000

img

  • 通过如下指令可以看见被链接的Guest_VM.o镜像的信息

img

  • 输出的三行是 ld 链接器在处理二进制文件时自动生成的符号,对应之前通过 ld -r -b binary 转换的二进制文件

3.4.2 Xhyper读入Guest os

首先定义一个guest结构体用来描述一个guest os的镜像:

//vm.h
typedef struct guest {
char *guest_name;
u64 start_addr;
u64 end_addr;
u64 image_size;
} guest_t;

定义guest_vm_image来读取:

//guest.c
/* Guest vm image are packaged in X-Hyper Image with aarch64-linux-gnu--ld */
extern char _binary_build_Guest_VM_start[];
extern char _binary_build_Guest_VM_end[];
extern char _binary_build_Guest_VM_size[];

guest_t guest_vm_image = {
.guest_name = "guest_vm",
.start_addr = (u64)_binary_build_Guest_VM_start,
.image_size = (u64)_binary_build_Guest_VM_size,
};

接着定义vm配置结构体,用vm_config_t来代替一个Guest os

typedef struct vm_config {
guest_t *guest_image;
guest_t *guest_dtb;
guest_t *guest_initrd;
u64 entry_addr;
} vm_config_t;

extern guest_t guest_vm_image;
vm_config_t guest_vm_cfg = {
.guest_image = &guest_vm_image,
.guest_dtb = NULL,
.guest_initrd = NULL,
.entry_addr = 0x80200000,
};