linux虚拟化之kvm(一个200行的arm64虚拟机代码)

一、背景

之前介绍了X86上的一个简易虚拟机:

linux虚拟化之kvm(一个150行的x86虚拟机代码)-CSDN博客

,但作为一名嵌入式开发者,还是需要在ARM64上尝试一番,ARM64上的虚拟化和X86还是有很多差异点;本文介绍arm64下的基于kvm的虚拟机。

环境依赖:

1、X86下的qemu模拟arm64环境

qemu搭建arm64 linux kernel环境-CSDN博客

2、busybox 中增加基础lib库(libc),避免自己交叉编译的程序在arm64的Host OS下无法执行

将交叉工具编译链下libc相关的库也拷贝到busybox构建的rootfs 根目录即可,如我自己的交叉工具链下libc的路径:

/home/geek/tool/aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc

二、arm64虚拟机架构

简单的说就是在X86电脑上用qemu模拟arm64的执行环境(Host OS),然后在arm64环境中通过kvm在虚拟机中执行一段简单的hello world汇编程序。

上图程序流程:

  1. 创建arm64 运行环境
  2. 通过/dev/kvm创建vcpu和设置USER_MEMORY
  3. 设置vpu type,
  4. 设置arm64的pc指针
  5. vcpu执行guest程序
  6. guest程序向地址0x996 写入"Hello"
  7. 由于这段地址非VM的memory空间,会触发KVM_EXIT_MMIO 事件
  8. 事件被host 端程序监听,通过kvm_run结构体信息提取 MMIO信息,获取到guest 程序写入的"Hello"字符
  9. host端通过printf串口输出

三、源码

1、arm64 host 代码(kvm_sample.c)

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kvm.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stddef.h>

#define KVM_DEV  "/dev/kvm"
#define MEM_SIZE  0x1000
#define PHY_ADDR  0x80000

#define AARCH64_CORE_REG(x)        (KVM_REG_ARM64 | KVM_REG_SIZE_U64 | KVM_REG_ARM_CORE | KVM_REG_ARM_CORE_REG(x))

int main(void)
{
    struct kvm_one_reg reg;
    struct kvm_vcpu_init init; //using init the vcpu type
    struct kvm_vcpu_init preferred;
    int ret;

    __u64 guest_entry = PHY_ADDR;
    __u64 guest_pstate;

    int kvmfd = open(KVM_DEV, O_RDWR);

    //ioctl(kvmfd, KVM_GET_API_VERSION, NULL);
    //1. create vm and get the vm fd handler
    int vmfd =  ioctl(kvmfd, KVM_CREATE_VM, 0);

    //2. create vcpu
    int vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, 0);
    if(vcpufd < 0) {
        printf("create vcpu failed\n");
        return -1;
    }

    //3. arm64 type vcpu type init
    //sample code can check the qemu/target/arm/kvm64.c
    memset(&init, 0, sizeof(init));
    //init.target = KVM_ARM_TARGET_GENERIC_V8; //here set KVM_ARM_TARGET_CORTEX_A57 meet failed
    init.target = -1;
    if (init.target == -1) {
        ret = ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &preferred);
        if(!ret) {
            init.target = preferred.target; //KVM_ARM_TARGET_GENERIC_V8
            printf("preferred vcpu type %d\n", init.target);
        }
    }

    ret = ioctl(vcpufd, KVM_ARM_VCPU_INIT, &init);

    if(ret  < 0) {
        printf("init vcpu type failed\n");
        return -1;
    }

    //4. get vcpu resouce map size and get vcpu  kvm_run status  
    int mmap_size = ioctl(kvmfd, KVM_GET_VCPU_MMAP_SIZE, NULL);

    if(mmap_size  < 0) {
        printf("get vcpu mmap size failed\n");
        return -1;
    }

    struct kvm_run *run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpufd, 0);


    //5. load the vm running program to buffer 'ram'
    unsigned char *ram = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, 
            MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    int kfd = open("test.bin", O_RDONLY);
    read(kfd, ram, MEM_SIZE);

    struct kvm_userspace_memory_region mem = {
        .slot = 0,
        .flags = 0,
        .guest_phys_addr = PHY_ADDR,
        .memory_size = MEM_SIZE,
        .userspace_addr = (unsigned long)ram,
    };

    //6. set the vm userspace program ram to vm fd handler
    ret = ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &mem);

    if(ret < 0) {
        printf("set user memory region failed\n");
        return -1;
    }

    //7. change the vcpu register info, arm64 need change the pc value 
    // arm64 not support KVM_SET_REGS, and KVM_SET_SREGS only support at x86 ppc arch
    // arm64 using the KVM_SET_ONE_REG

    //sample code from qemu/target/arm/kvm64.c
    reg.id = AARCH64_CORE_REG(regs.pc);
    reg.addr = (__u64)&guest_entry;
    ret = ioctl(vcpufd, KVM_SET_ONE_REG, &reg);

    if(ret  < 0) {
        printf("change arm64 pc reg failed err %d\n", ret);
        return -1;
    } else {
        printf("set pc addr success\n");
    }

    //7. run the vcpu and get the vcpu result
    while(1) {
        ret = ioctl(vcpufd, KVM_RUN, NULL);
        if (ret == -1)
        {
            printf("exit unknow\n");
            return -1;
        }

        switch(run->exit_reason)
        {
            //when guest program meet io access error will trigger KVM_EXIT_MMIO event, 
            //using the event get guest program output
            case KVM_EXIT_MMIO:
                if (run->mmio.is_write && run->mmio.len == 1) {
                    printf("%c", run->mmio.data[0]);
                }
                break;
            case KVM_EXIT_FAIL_ENTRY:
                puts("entry error");
                return -1;
            default:
                printf("exit_reason: %d\n", run->exit_reason);
                return -1;
        }
    }
    return 0;
}

2、arm64 kvm guest运行的代码(test.S)

/*
 * write "Hello"(ASCII) to port 0x996
 * compile: 
 */
.section ".text"
start:
    mov x5, 0x48
    mov x4, 0x996
    strb w5, [x4] 
    mov x5, 0x65 
    strb w5, [x4] 
    mov x5, 0x6c
    strb w5, [x4] 
    strb w5, [x4] 
    mov x5, 0x6f
    strb w5, [x4] 
    mov x5, 0x0a
    strb w5, [x4] 
    ret

3、链接文件(test.ld)

OUTPUT_ARCH(aarch64)
ENTRY(start)

SECTIONS
{
    . = 0x80000;
    .text : {*(.text)}
}

4、makefile文件

INCLUDES = -I /home/geek/workspace/linux/linux-6.6.1/usr/include
CC=aarch64-none-linux-gnu-gcc
OBJCOPY=aarch64-none-linux-gnu-objcopy
LD=aarch64-none-linux-gnu-ld

all: test.bin kvm_sample

test.bin:test.S
    $(CC)  -nostdlib -nostartfiles -nostdinc -c test.S -o test.o
    $(LD) -nostdlib test.o -T test.ld -o test.tmp.o
    $(OBJCOPY) -O binary test.tmp.o test.bin

kvm_sample: kvm_sample.c
    $(CC) $(INCLUDES) kvm_sample.c -g -o kvm_sample
clean:
    rm kvm_sample test.bin *.o

Makefile中的INCLUDES的内核头文件需要注意一下,需要指定为编译arm64 运行环境的路径,因为我们编译的kvm_sample是要在arm64 linux 下运行, 在构建arm64 linux kernel 运行环境时,使用命令:

make ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- headers_install

即可在对应kernel目录下生成 ./usr/include 头文件,这个路径加入到测试程序头文件即可

5、执行结果

无图无真相,结果比较简单,虽然只是一个简单的Hello,但是背后的实现并不简单:

四、总结

最后总结下kvm的使用流程:

1、创建虚拟机:vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);

2、创建vcpu:vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, 0);

3、初始化虚拟机内存:ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &mem);

4、运行vcpu:ioctl(vcpufd, KVM_RUN, NULL);

无论是arm64还是X86, 流程基本是一样的,差异点在CPU的pc值,CPU类型等参数设置上;上面的流程也和qemu的实现类似,这里的一个加起来200行左右的代码展示了一个VM的基本流程。可扩展的部分:guest程序也可以用c去编写,需要剥离对系统库的依赖(如libc等);guest对host 只有输出,可以通过设置寄存器设置参数传递,完成输入的实验等。

参考:

Documentation - Arm Developer

Documentation - Arm Developer

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

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

相关文章

nosql数据库知识点总结

目录 1、什么是nosql数据库&#xff0c;它包括哪些 文档数据库 建数据 哪一种是最简单的 2、什么是文档数据库 3、创建mongodb时默认会建造三个数据库&#xff0c;是哪三个 4、mongodb支持的数据类型有哪些 5、它的常规语句有哪些 6、副本集和分片集有什么作用 复制 …

​​力扣刷MySQL-第九弹(详细讲解)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

Spring 事务原理二

该说些什么呢&#xff1f;一连几天&#xff0c;我都沉溺在孤芳自赏的思维中无法自拔。不知道自己为什么会有这种令人不齿的表现&#xff0c;更不知道这颗定时炸弹何时会将人炸的粉身碎骨。好在儒派宗师曾老夫子“吾日三省吾身”的名言警醒了我。遂潜心自省&#xff0c;溯源头以…

20240128-读书带来的影响

我本身不算是一个特别喜欢读书的人&#xff0c;更多的时候其实是为了读书而读书。在坚持每天读了一小时书之后&#xff0c;我发现自身开始慢慢有些变化。是什么时候突然有了这种感悟呢&#xff0c;是最近每周5小时左右的微信读书以及纸质书籍的阅读&#xff0c;让我体会到了读书…

C++爱好者的科目四易错点总结

科目四易错点总结 在科目四考试中&#xff0c;一部分内容是可以通过刷题快速掌握的&#xff0c;一部分内容缺因易混淆而降低我们的准确率&#xff0c;本文主要对后者进行总结&#xff0c;期待大家补充与指正。 注&#xff1a; 本文不是全部的知识点总结处 本文不是权威机构 本文…

【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(附通信协议和代码)(上)

基于Netty的分布式通信框架实现 前提介绍回顾Dubbo分布式通信框架组成元素程序执行流程消息协议设计实现机制ChannelInboundHandlerAdapter自定义事件处理 ChannelOutboundHandlerAdapter 编(解)码处理器编码过程阶段ChannelOutboundHandlerAdapter序列化实现ChannelOutboundHa…

NLP自然语言处理的发展:从初创到人工智能的里程碑

自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;人工智能领域中备受关注的重要分支之一。它使得计算机能够理解、解释和使用人类语言。随着技术的不断发展&#xff0c;NLP经历了从初创时期到深度学习时代的巨大演变&#xff0c;推动了互联网产…

【教学类-XX -XX 】20240128名字字卡1.0(15CM正方形手工纸、黑体,说明是某个孩子的第几个名字)

作品展示&#xff1a; 15CM手工纸上一个名字&#xff0c;页眉有这个字是哪一位孩子的第X个名字的说明 背景需求&#xff1a; 去年我制作了中6班孩子的姓名卡片&#xff0c;一张A4纸上6个字&#xff0c;每张卡片大约10CM&#xff09; 【教学类-25-01】20230320 名字卡片绘画游…

使用机器学习算法检测交易中的异常行为

交易中的异常检测意味着识别交易或相关活动中的异常或意外模式。这些模式被称为异常或异常值&#xff0c;明显偏离预期规范&#xff0c;可能表明存在不规则或欺诈行为。 异常检测在各种业务中发挥着至关重要的作用&#xff0c;尤其是那些涉及金融交易、在线活动和安全敏感操作…

IMX6ULL驱动学习——通过总线设备驱动模型点亮野火开发板小灯【参考韦东山老师教程】

参考&#xff1a;【IMX6ULL驱动开发学习】11.驱动设计之面向对象_分层思想&#xff08;学习设备树过渡部分&#xff09;-CSDN博客 韦东山课程&#xff1a;LED模板驱动程序的改造_总线设备驱动模型 我使用的开发板&#xff1a;野火imx6ull pro 欢迎大家一起讨论学习 实现了总线设…

ChatGPT与文心一言:智能回复与语言准确性的较量

在当今数字化时代&#xff0c;随着人们对智能化技术的需求不断增长&#xff0c;智能回复工具也成为了日常生活中不可或缺的一部分。ChatGPT和文心一言作为两个备受瞩目的智能回复工具&#xff0c;在智能回复、语言准确性以及知识库丰富度等方面各有卓越之处。 本文将对这两者进…

JAVA编程语言单词汇总

Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发&#xff0c;并在 1995 年正式推出。后来 Sun 公司被 Oracle &#xff08;甲骨文&#xff09;公司收购&#xff0c;Java 也随之成为 Ora…

算法每日一题: 边权重均等查询 | 公共子祖先

大家好&#xff0c;我是星恒&#xff0c;今天给大家带来的是一道图里面有关公共子祖先的题目&#xff0c;理解起来简单&#xff0c;大家 题目&#xff1a;leetcode 2846 现有一棵由 n 个节点组成的无向树&#xff0c;节点按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n …

【Linux C | 网络编程】详细介绍 “三次握手(建立连接)、四次挥手(终止连接)、TCP状态”

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

强化合作!浪潮信息携手业界伙伴筑牢算力底座

以太平金融科技服务&#xff08;上海&#xff09;有限公司&#xff08;以下简称“太平金科”&#xff09;为例&#xff0c;在算力新型基础设施建设方面&#xff0c;该公司一直不遗余力。近日&#xff0c;该公司更携手全球领先的IT基础设施供应商浪潮信息&#xff0c;优化算力基…

静态代理IP该如何助力Facebook多账号注册运营?

在Facebook运营中&#xff0c;充分利用静态代理IP是多账号运营的关键一环。通过合理运用静态代理IP&#xff0c;不仅可以提高账号安全性&#xff0c;还能有效应对Facebook的算法和限制。以下是这些关键点&#xff0c;可以帮助你了解如何运用静态代理IP进行Facebook多账号运营&a…

如何通俗解释Docker是什么?

要想弄懂Docker&#xff0c;咱们得先从“容器化”讲起。 一、容器化技术及Docker的出现 容器化&#xff0c;它是一种轻量级、可移植的软件打包方式&#xff0c;你就想象成一个快递箱子&#xff0c;里面装着你的应用和所有需要运行的环境&#xff0c;这个箱子能在任何支持容器…

PDF标准详解(一)——PDF文档结构

已经很久没有写博客记录自己学到的一些东西了。但是在过去一年的时间中自己确实又学到了一些东西。一直攒着没有系统化成一篇篇的文章&#xff0c;所以今年的博客打算也是以去年学到的一系列内容为主。通过之前Vim系列教程的启发&#xff0c;我发现还是写一些系列文章对自己的帮…

go学习之air库的使用

首先下载air库 go install github.com/cosmtrek/air之后你需要去找到库下载的地方&#xff0c;若使用的是go mod可以使用命令 go env GOPATH找到下载库的位置 进入后&#xff0c;有bin&#xff0c;pkg目录&#xff0c;进入bin目录&#xff0c;你能看到air.exe文件 这时候将此…

NSSCTF Round#17 RE snake WP

控制流劫持可以非常快&#xff0c;当时困在中间的循环里了&#xff0c;其实一直跳到最后就行…… 运行一下发现是个贪吃蛇 联系到朝雾老师教的打飞机hit-plane那一题&#xff0c;应该通过控制流劫持直接跳转到打印flag的地方 第一个cmp分支处&#xff0c;判断轮数&#xff0c…