minos 1.2 内存虚拟化——guest

首发公号:Rand_cs

该项目来自乐敏大佬:https://github.com/minosproject/minos

项目来自乐敏大佬:https://github.com/minosproject/minos

本文继续讲述 minos 中的内存虚拟化中关于 guest 的部分,主要弄清楚一个问题,minos 如何管理 guest vm 的内存。

对于虚拟机的内存管理主要是 ipa 的管理,ipa 如何映射到 pa,先来看看虚拟机内存管理在 minos 中的结构体表示

struct vm {
    ...
    struct mm_struct mm;
    ...
}

struct mm_struct {
    void *pgdp;
    spinlock_t lock;

    /*
     * vmm_area_free : list to all the free vmm_area
     * vmm_area_used : list to all the used vmm_area
     * lock      : spin lock for vmm_area allocate
     */
    struct list_head vmm_area_free;
    struct list_head vmm_area_used;
};

每一个虚拟机都对应着一个 struct vm 结构体,每一个 vm 又有一个 mm_struct 结构体,我们可以看到 mm_struct 结构体的定义和前文提到的 struct vspace 结构体定义很像。

struct vspace 只有一个实例,host_vspace,里面存放着 hyp 的 pgd 页表地址,代表着 hyp 的虚拟地址空间,这张页表可以将 hyp 层次的虚拟地址转换为实际的物理地址。

而 mm_struct 中记录的 pgd 页表则是 vm stage2 转换需要用到的页表。在 guest os 中,每个进程都有自己的页表,这里我们称之为 stage1 页表,它负责将进程的 Virtual Address 转换为 Intermediate Physical Address(站在guest os 进程的角度这就是物理地址)。每个虚拟机又有一张 stage2 页表,存放在 vm->mm_struct->pgd 中,它负责将 Intermediate Physical Address 再转换为真实的物理地址。

对于虚拟机的内存管理相关图示如下:

minos 中的虚拟机可以分为两大类:

  • 一类叫作 native vm(后面我称作 nvm),目前简单理解为由 minos 启动的 vm 就是 native vm(就是在设备树配置文件中设置了一个 vm 描述,minos 启动的时候分析设备树,发现有 vm 节点,就创建该 vm)。host vm (后面称作 hvm)也是 native vm,只不过它的权限更大,可以看作是服务型 vm,它会直接操作大多数物理设备。在 qemu 平台下,这个 vm 也是个 linux。
  • 另一类叫做 guest vm(后面称作 gvm),它是由 host vm 通过命令创建的(就是我们登入 hvm,在 hvm 通过命令手动创建一个 vm)

对于 nvm,在物理内存初始化的阶段就已经为其分配了属于它那一份的物理内存。启动阶段,minos 会分析设备树节点,解析 memory 节点,知道了物理内存的始末,将这信息记录到第一个 mem_region 结构体中。随后分析 vm 节点,从中获取 vm.memory 节点信息,获取其始末,然后记录到另一个 mem_region 结构体中。

而对于 gvm,前文中我们提到过 0x4645 a000 ~ 0x4660 0000、0x8660 0000 ~ 0x1 4000 0000 两部分区域内存可以看作是空闲内存,这两部分内存会全部转换成 block 的形式,当创建普通 vm 的时候,就会从中分配内存。

IPA 地址空间

对于 hypervisor 中涉及的各种地址空间有很多,这里再来捋一下:

对于 vm 来说,内核运行在 EL1,运行在内核地址空间,由内核页表映射到物理地址空间。进程运行在 EL0,运行在自己的虚拟地址空间,由进程页表映射到物理地址空间。内核负责管理进程的虚拟地址空间,负责创建进程页表映射页表等操作,并将进程内存相关信息记录在了 task_struct->mm_struct。

对于 minos 来说,运行在 EL2,运行在 minos 的虚拟地址空间,minos 也有一张自己的页表,负责将 minos 自身的一些数据代码映射到物理地址空间。vm 运行在 EL0/EL1,运行在 ipa 地址空间,由 vm 的 stage2 页表映射到实际的物理地址空间。而 minos 负责 vm stage2 页表的创建,映射等操作,并将 vm 内存相关信息记录在了 vm->mm_struct 结构体中

这么一看,其实这种 type1 类型的虚拟机在内存管理方面跟内核极其相似,vm 内核对应 minos,进程对应 vm。

vm 的 ipa 地址空间大小为 1T(40bit),但当然不可能这么大的物理地址空间都有对应的物理内存。

struct mm_struct {
    void *pgdp;
    spinlock_t lock;

    /*
     * vmm_area_free : list to all the free vmm_area
     * vmm_area_used : list to all the used vmm_area
     * lock      : spin lock for vmm_area allocate
     */
    struct list_head vmm_area_free;
    struct list_head vmm_area_used;
};

从上述 mm_struct 定义来看,有 free vmm_area 和 used vmm_area 之分,vmm_area 就是一段 ipa 地址空间,used vmm_area 可以看作是有真正物理内存对应的 ipa 地址空间,free vmm_area 反之。对于 vmm_area 的定义如下:

/*
 * pstart - if this area is mapped as continous the pstart
 * is the phsical address of this vmm_area
 */
struct vmm_area {
    unsigned long start;   // ipa 地址——start
    unsigned long end;     // ipa 地址——end
    unsigned long pstart;
    int flags;
    int vmid;           /* 0 - for self other for VM */
    struct list_head list;
    // 如果分配的内存是 block 形式
    struct mem_block *b_head;

    /* if this vmm_area is belong to VDEV, this will link
     * to the next vmm_area of the VDEV */
    struct vmm_area *next;
};

struct mem_block {
    uint32_t bfn;  // 块号,可以直接转换为物理地址
    struct mem_block *next;
};

一个 vmm_area 就是一段 ipa 地址空间,对于 vm 来说,就是一段物理内存。start ~ end 就是该段 ipa 地址空间的始末,pstart 表示 start 这个 ipa 对应的 pa。如果分配的物理内存是 block 形式,那么这个 “pstart” 记录在 mem_block 中。

vmm_area 操作集

既然有多个 vmm_area,那必然也涉及到管理的一系列操作,来简单看一看 vmm_area 相关操作集

// 分配一个 vmm_area 结构体,记录 base、size 信息,只是记录信息,没有实际的内存分配
static struct vmm_area *__alloc_vmm_area_entry(unsigned long base, size_t size)
{
    struct vmm_area *va;

    va = zalloc(sizeof(struct vmm_area));
    if (!va)
        return NULL;

    va->start = base;
    // -1(0xfffffffffff),表示未分配映射实际的物理内存
    va->pstart = BAD_ADDRESS;
    va->end = base + size;
    va->flags = 0;

    return va;
}

这是 vmm_area 结构体分配函数,注意初始化的 pstart 字段,设置为 -1,表示未分配映射实际的物理内存。

// 从 mm->vmm_area_free 所有的 vmm_list 中,找一个合适的 vmm_area,
// 从中切出一个 vmm_area,此 vmm_area 的 start 为 base,end 为 start+size
struct vmm_area *split_vmm_area(struct mm_struct *mm,
        unsigned long base, size_t size, int flags)
{
    unsigned long end = base + size;
    struct vmm_area *va, *out = NULL;

    if ((flags & VM_NORMAL) && (!IS_PAGE_ALIGN(base) || !IS_PAGE_ALIGN(size))) {
        pr_err("vm_area is not PAGE align 0x%p 0x%x\n",
                base, size);
        return NULL;
    }

    spin_lock(&mm->lock);
    // 遍历 vmm_area_free 中所有 vma 结构体
    list_for_each_entry(va, &mm->vmm_area_free, list) {
        // 如果 [base,end] < [start, end],break
        if ((base >= va->start) && (end <= va->end)) {
            out = va;
            break;
        }
    }

    if (!out)
        goto exit;

    // split 参数中的 vmm_area 结构体,建立新的 vmm_area 结构体,插入到 mm->vmm_area_used 链表
    // 被分割的 vmm_area,其用剩下的,重新插入到 mm->vmm_area_free 链表
    out = __split_vmm_area(mm, out, base, end, flags);
exit:
    spin_unlock(&mm->lock);

    if (!out)
        pr_err("split vma [0x%lx 0x%lx] failed\n", base, end);

    return out;
}

整个过程就是稍微复杂了那么一点的链表操作,仔细看代码图示应该能懂,不再详述

native vm ipa 管理

这一小节讲述 vm ipa 地址空间初始化

int vm_mm_struct_init(struct vm *vm)
{
    struct mm_struct *mm = &vm->mm;

    mm->pgdp = NULL;
    spin_lock_init(&mm->lock);
    init_list(&mm->vmm_area_free);
    init_list(&mm->vmm_area_used);

    // 分配该 vm 的 stage2 页表/pgd
    mm->pgdp = arch_alloc_guest_pgd();
    if (mm->pgdp == NULL) { 
        pr_err("No memory for vm page table\n");
        return -ENOMEM;
    }
    // 分配和初始化该 vm 的 vma 结构体
    vmm_area_init(mm, !vm_is_32bit(vm));

    /*
     * attch the memory region to the native vm.
     */
    return vm_memory_init(vm);
}

vm_mm_struct_init 函数主要初始化 vm 最初的一些内存信息

// 创建 vm 的第一个 vmm_area
static void vmm_area_init(struct mm_struct *mm, int bit64)
{
    unsigned long base, size;
    struct vmm_area *va;

    /*
     * the virtual memory space for a virtual machine:
     * 64bit - 40bit (1TB) IPA address space.
     * 32bit - 32bit (4GB) IPA address space. (Without LPAE)
     * 32bit - TBD (with LPAE)
     */
    if (bit64) {
        base = 0x0;
        size = (1UL << 40);
    } else {
#ifdef CONFIG_VM_LPAE
        base = 0x0;
        size = 0x100000000;
#else
        base = 0x0;
        size = 0x100000000;
#endif
    }

    // 分配一个初始的 vmm_area
    va = __alloc_vmm_area_entry(base, size);
    if (!va)
        pr_err("failed to alloc free vmm_area\n");
    // 将 vmm_area 插入到 vmm_area_free 链表
    else
        list_add_tail(&mm->vmm_area_free, &va->list);
}

此函数创建 vm 的第一个 free vmm_area 结构体,起始地址为 0,大小为 1T

static int vm_memory_init(struct vm *vm)
{
    struct memory_region *region;
    struct vmm_area *va;
    int ret = 0;

    // mvm 创建的 vm 直接返回 0
    if (!vm_is_native(vm))
        return 0;

    /*
     * find the memory region which belongs to this
     * VM and register to this VM.
     */
    // 遍历 mem_region
    for_each_memory_region(region) {
        // 寻找为当前 vm 分配的 mem_region,如果不是 continue
        if (region->vmid != vm->vmid)
            continue;

        // 切割出一个 vmm_area,其 base、size 为 mem_region 大小
        va = split_vmm_area(&vm->mm, region->phy_base,
                region->size, VM_NATIVE_NORMAL);
        if (!va)
            return -EINVAL;
    }

    /*
     * check whether the entry address, setup_data address and load
     * address are in the valid memory region.
     */

    ret = check_vm_address(vm, (unsigned long)vm->load_address);
    ret += check_vm_address(vm, (unsigned long)vm->entry_point);
    ret += check_vm_address(vm, (unsigned long)vm->setup_data);

    return ret;
}

对于 native vm,前面说过,在解析设备树 memory 节点的时候就会为该 vm 分配一段内存,并记录到了 memory_region 当中,这里就是查找该 memory_region,获取其中的内存起始位置,大小等信息,然后从该 vm free vmm_area 中 split 出一个对应的 used vmm_area。(used vmm_area 简单理解为有物理内存对应的 ipa 地址空间,minos 为 native vm 划分了一段物理内存,信息记录在 memory_region,这里就是将信息取出来,记录到相应的 used vmm_area)

int vm_mm_init(struct vm *vm)
{
    int ret;
    unsigned long base, end, size;
    struct vmm_area *va, *n;
    struct mm_struct *mm = &vm->mm;

    if (test_and_set_bit(VM_FLAGS_BIT_SKIP_MM_INIT, &vm->flags))
        return 0;

    // dump 出目前所有的 vmm_area 
    dump_vmm_areas(&vm->mm);

    /* just mapping the physical memory for native VM */
    // used vmm_area 都有分配实际的物理内存,这里建立映射关系
    list_for_each_entry(va, &mm->vmm_area_used, list) {
        if (!(va->flags & __VM_NORMAL))
            continue;
        // 建立映射,而且是直接映射, 即 [va->start, va->end) => [va->start, va->end)
        ret = map_vmm_area(mm, va, va->start);
        if (ret) {
            pr_err("map mem failed for vm-%d [0x%lx 0x%lx]\n",
                vm->vmid, va->start, va->end);
            return ret;
        }
    }

    /*
     * make sure that all the free vmm_area are PAGE aligned
     * when caculated the end address need to plus 1.
     */
    // 规整 free vmm_area
    list_for_each_entry_safe(va, n, &mm->vmm_area_free, list) {
        base = BALIGN(va->start, PAGE_SIZE);
        end = ALIGN(va->end, PAGE_SIZE);
        size = end - base;

        if (size < PAGE_SIZE) {
            pr_debug("drop unused vmm_area [0x%lx 0x%lx]\n",
                    va->start, va->end);
            list_del(&va->list);
            free(va);
            continue;
        }

        if (size != (va->end - va->start)) {
            pr_debug("adjust vma [0x%lx 0x%lx] to [0x%lx->0x%lx]\n",
                    va->start, va->end, base, end);
            va->start = base;
            va->end = end;
        }
    }

    return 0;
}

前面虽然一直再说给 native vm 分配了内存,但是其实也只是记录了信息到 used vmm_area,并没有建立实际的映射关系(在我的观念中,只有建立了映射关系,才算是真正的分配了物理内存),但此函数中,遍历了所有的 uesd vmm_area,并建立了实际的映射关系,使得 vmm_area 代表的 ipa 空间映射到了 pa 空间。

guest vm ipa 管理

guest vm ipa 空间的初始化操作与 native vm 是一样的,都会调用 vm_mm_struct_init 函数来创建一张 stage2 页表,分配和初始化第一个 vmm_area 结构体,并注册到 vmm_area_free 链表。但是 mem_list 中没有一个 memory_region 代表将要创建的 guest vm,这是 native vm 的特权,在物理内存初始化的时候就为 native vm 划分了一块内存。

guest vm 的物理内存分配和映射是另外一条路子,所有的 guest vm 都是从块内存池中分配物理内存。所以在继续讲述 guest vm ipa 的管理时,先来看看这个块内存池。

块内存池
struct mem_block {
    uint32_t bfn;
    struct mem_block *next;
};

struct block_section {
    unsigned long start;  //该块区的起始地址
    unsigned long size;   //该块区的大小
    unsigned long end;    //该块区的结束地址
    unsigned long free_blocks;  //该块区剩余的空闲块数
    unsigned long total_blocks; //该块区总共有多少块
    unsigned long current_index; //下一个空闲块号
    unsigned long *bitmap;  //块区分配位图
    struct block_section *next; //下一个块区
};

static struct block_section *bs_head;  // 块区链表头结点
static DEFINE_SPIN_LOCK(bs_lock);  // 分配所
static unsigned long free_blocks;  // 目前所有块区空闲块数

一个块区其实是一片连续的内存,只是转换成了一个个块的形式。多个块区组成了用来给 guest vm 分配内存的块内存池。各个块区也是通过链表连接的,其头结点是 bs_head

// 遍历 mem_region,将空闲的 NORMAL 内存 转换为 block,作为 guest vm、
// [       0.000000@00 000] NIC MEM: 0x000000004645a000 -> 0x0000000046600000 [0x00000000001a6000] Normal/Host
// 这部分内存小于 1 个 block size,舍弃掉
// [       0.000000@00 000] NIC MEM: 0x0000000086600000 -> 0x00000000c0000000 [0x0000000039a00000] Normal/Host
// 这部分内存转换为 block
void vmm_init(void)
{
    struct memory_region *region;
    struct block_section *bs;
    unsigned long start, end;
    int size;

    ASSERT(!is_list_empty(&mem_list));

    /*
     * all the free memory will used as the guest VM
     * memory. The guest memory will allocated as block.
     */
    // 对于每一个 memory_region
    list_for_each_entry(region, &mem_list, list) {
        // 遍历所有的空闲内存,准备全部用作 guest vm,转换成 block 形式
        if (region->type != MEMORY_REGION_TYPE_NORMAL)
            continue;

        /*
         * block section need BLOCK align.
         */
        // 如果该段 mem_region 小于一个 block,放过它
        start = BALIGN(region->phy_base, BLOCK_SIZE);
        end = ALIGN(region->phy_base + region->size, BLOCK_SIZE);
        if (end - start <= 0) {
            pr_warn("VMM drop memory region [0x%lx 0x%lx]\n",
                    region->phy_base,
                    region->phy_base + region->size);
            continue;
        }

        pr_notice("VMM add memory region [0x%lx 0x%lx]\n", start, end);

        // 分配一个 block_section,记录信息
        bs = malloc(sizeof(struct block_section));
        ASSERT(bs != NULL);
        bs->start = start; //块区的起始地址就是该memory_region的起始地址
        bs->end = end; 
        bs->size = bs->end - bs->start; //块区大小就是memory_region大小
        bs->total_blocks = bs->free_blocks = bs->size >> BLOCK_SHIFT; //计算该memory_region有多少块
        bs->current_index = 0;
        free_blocks += bs->total_blocks; //记录到总的空闲块数

        /*
         * allocate the memory for block bitmap.
         */
        // 分配对应的 bitmap
        size = BITS_TO_LONGS(bs->free_blocks) * sizeof(long);
        bs->bitmap = malloc(size);
        ASSERT(bs->bitmap != NULL);
        memset(bs->bitmap, 0, size);

        bs->next = bs_head;  // 将该块区头插到 bs_head
        bs_head = bs;
    }
}

上述函数就是将空闲的 memory_region 中的内存转换为块,可以看出当前系统有两个空闲 memory_region,有一个 memory_region 太小了,直接暴力的舍弃掉了,另外一个就转换成了块区

// 为 guest vm 分配和映射物理内存
int alloc_vm_memory(struct vm *vm)
{
    struct mm_struct *mm = &vm->mm;
    struct vmm_area *va;

    // 如果是刚创建 vm 时走到这里的话,
    // vmm_area_used 链表中应该只有一个 vma 结构,此结构是从整体的 ipa vma 中 split 下来的,见 guest_mm_init 函数流程
    list_for_each_entry(va, &mm->vmm_area_used, list) {
        if (!(va->flags & VM_NORMAL))
            continue;
        // 从 block 中分配内存
        if (__alloc_vm_memory(mm, va)) {
            pr_err("alloc memory for vm-%d failed\n", vm->vmid);
            goto out;
        }

        // 建立 stage2 映射
        if (map_vmm_area(mm, va, 0)) {
            pr_err("map memory for vm-%d failed\n", vm->vmid);
            goto out;
        }
    }

    return 0;
out:
    release_vm_memory(vm);
    return -ENOMEM;
}

此函数同样的也是要遍历该 vm 所有的 used vmm_area 结构体,然后调用 __alloc_vm_memory 函数分配物理内存,map_vmm_area 函数来映射物理内存

// 从 block_section 中分配一个 bock,返回其块号
static int get_memblock_from_section(struct block_section *bs, uint32_t *bfn)
{
    uint32_t id;
    // 遍历位图,寻找空闲块 idx
    id = find_next_zero_bit_loop(bs->bitmap,
            bs->total_blocks, bs->current_index);
    if (id >= bs->total_blocks)
        return -ENOSPC;

    set_bit(id, bs->bitmap);
    bs->current_index = id + 1;
    bs->free_blocks -= 1;
    free_blocks -= 1;

    // (bs->start >> MEM_BLOCK_SHIFT) 表示第一个块的块号
    // (bs->start >> MEM_BLOCK_SHIFT) + id 表示下一个空闲块的块号
    *bfn = (bs->start >> MEM_BLOCK_SHIFT) + id;

    return 0;
}

// 分配一个 block,返回 mem_block 结构
struct mem_block *vmm_alloc_memblock(void)
{
    struct block_section *bs;
    struct mem_block *mb;
    int success = 0, ret;
    uint32_t bfn = 0;

    spin_lock(&bs_lock);
    bs = bs_head;
    while (bs) {
        if (bs->free_blocks != 0) {
            // 获取一个空闲块,返回其块号
            ret = get_memblock_from_section(bs, &bfn);
            if (ret == 0) {
                success = 1;
                break;
            } else { 
                pr_err("memory block content wrong\n");
            }
        }
        bs = bs->next;
    }
    spin_unlock(&bs_lock);

    if (!success)
        return NULL;

    // 分配 mem_block 结构体
    mb = malloc(sizeof(struct mem_block));
    if (!mb) {
        spin_lock(&bs_lock);
        __vmm_free_memblock(bfn);
        spin_unlock(&bs_lock);
        return NULL;
    }
    // 记录块号
    mb->bfn = bfn;
    mb->next = NULL;

    return mb;
}

// 为 guest vm 分配块内存
static int __alloc_vm_memory(struct mm_struct *mm, struct vmm_area *va)
{
    int i, count;
    unsigned long base;
    struct mem_block *block;

    base = ALIGN(va->start, MEM_BLOCK_SIZE);
    if (base != va->start) {
        pr_err("memory base is not mem_block align\n");
        return -EINVAL;
    }

    va->b_head = NULL;
    va->flags |= VM_MAP_BK;
    // 计算该 vmm_area 大小等于多少个 mem_block
    count = VMA_SIZE(va) >> MEM_BLOCK_SHIFT;

    /*
     * here get all the memory block for the vm
     * TBD: get contiueous memory or not contiueous ?
     */
    // 这里分配所有所有块
    for (i = 0; i < count; i++) {
        block = vmm_alloc_memblock();
        if (!block)
            return -ENOMEM;

        // 头插法到 va->b_head 链表
        block->next = va->b_head;
        va->b_head = block;
    }

    return 0;
}

上述函数从块内存池中分配内存给 guest vm

// 释放 block_section 中的 bfn 所在的 block
static int __vmm_free_memblock(uint32_t bfn)
{
    // format block 地址
    unsigned long base = bfn << MEM_BLOCK_SHIFT;
    
    struct block_section *bs = bs_head;

    // 遍历所有 block_section
    while (bs) {
        // 该 bfn 所在的 block_section
        if ((base >= bs->start) && (base < bs->end)) {
            // 获取该 bfn 所在 block 在对应的 block_seciton 的比特位
            bfn = (base - bs->start) >> MEM_BLOCK_SHIFT;
            // 清除该比特位
            clear_bit(bfn, bs->bitmap);
            // 更新信息
            bs->free_blocks += 1;
            free_blocks += 1;
            return 0;
        }

        bs = bs->next;
    }

    pr_err("wrong memory block 0x%x\n", bfn);

    return -EINVAL;
}

// 释放掉 block mb
int vmm_free_memblock(struct mem_block *mb)
{
    uint32_t bfn = mb->bfn;
    int ret;

    free(mb);
    spin_lock(&bs_lock);
    ret = __vmm_free_memblock(bfn);
    spin_unlock(&bs_lock);

    return ret;
}

这是释放 block 内存到块内存池,都是位图链表的一些基本操作不赘述

stage2 映射

上述是给 vm 分配内存相关代码讲解,下面来说说 stage2 映射相关的

// 对 vma_area 中的内存建立映射
int map_vmm_area(struct mm_struct *mm,
        struct vmm_area *va, unsigned long pbase)
{
    int ret;

    switch (va->flags & VM_MAP_TYPE_MASK) {
    case VM_MAP_PT:  // 建立直接映射
        va->pstart = va->start;
        ret = vmm_area_map_ln(mm, va);
        break;
    case VM_MAP_BK:
        ret = vmm_area_map_bk(mm, va);
        break;
    // create_hvm_shmem_map 的时候走 default
    default:
        va->pstart = pbase;
        ret = vmm_area_map_ln(mm, va);
        break;
    }

    return ret;
}

// 创建 stage2 block 映射
static int vmm_area_map_bk(struct mm_struct *mm, struct vmm_area *va)
{
    struct mem_block *block = va->b_head;;
    unsigned long base = va->start;
    unsigned long size = VMA_SIZE(va);
    int ret;

    // 遍历 vma_area 中的所有 block,建立映射
    while (block) {
        ret = __create_guest_mapping(mm, base, BFN2PHY(block->bfn),
                MEM_BLOCK_SIZE, va->flags | VM_HUGE | VM_GUEST);
        if (ret)
            return ret;

        base += MEM_BLOCK_SIZE;
        size -= MEM_BLOCK_SIZE;
        block = block->next;
    }

    ASSERT(size == 0);

    return 0;
}

所有的 stage2 映射都是调用上述函数,映射方式也有几种,线性、passthrough 等等,先不管这些概念以及用途,反正这里最后都是调用到 __create_guest_mapping,而 __create_guest_mapping 又类似 __create_host_mapping,就是一系列的页表操作函数。可以看 stage2.c 文件中的函数和 stage1.c 文件中的函数差不了太多,都是对 pgd、pud、pmd、pte 的操作,所以这里就不细说了,可以自行查看源码

简要总结:无虚拟化的情况下,Linux 内核需要对进程的虚存进行管理,类似,有虚拟化情况下,hypervisor 负责对虚机内存管理。虚机里面的内存,比如说虚机中的进程内存呢?那是虚机 OS 内核如 Linux 干的事情,hypervisor 不管。而 minos 中对于虚机内存,抽象为 vmm_area,minos

首发公号:Rand_cs

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

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

相关文章

LNMP网络架构

操作准备&#xff1a;准备三台虚拟机 安装 MySQL 服务 &#xff08;1&#xff09;准备好mysql目录上传软件压缩包并解压 cd /opt mkdir mysql tar xf mysql-boost-5.7.44.tar.gz &#xff08;2&#xff09;安装mysql环境依赖包 yum -y install ncurses ncurses-devel bison…

fpga入门 串口定时1秒发送1字节

一、 程序说明 FPGA通过串口定时发送数据&#xff0c;每秒发送1字节&#xff0c;数据不断自增 参考小梅哥教程 二、 uart_tx.v timescale 1ns / 1psmodule uart_tx(input wire sclk,input wire rst_n,output reg uart_tx);parameter …

基本算法——位运算

a^b 原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 题目描述 运行代码 #include<iostream> using namespace std; long long a,b,c,t1; int main() {cin>>a>>b>>c;for(;b;b/2){if(b&1)tt*a%c;aa*a%c;}cout<<t%c; } 代码思路…

Unity MiniCPM-V 让引擎拥有视觉

Unity MiniCPM-V 让引擎拥有视觉 前言项目Python环境布置Unity场景布置代码编写添加并设置脚本总结 鸣谢AI提示 前言 新发布的MiniCPM-V&#xff0c;忍不住玩一下&#xff0c;可以让之前制作的语音助手拥有一定的视觉能力&#xff08;不是OpenCV不行&#xff0c;而是AI更加符合…

项目中MySQL数据库设计(尚庭公寓)

数据库设计 1 数据库设计理论 1.1 数据库模型 数据库设计中最常采用的模型为实体&#xff08;Entity&#xff09;关系&#xff08;Relationship&#xff09;模型&#xff0c;简称ER模型。其核心思想是将现实世界中的复杂数据表示为一组实体&#xff0c;并描述这些实体之间的…

minos 2.5 中断虚拟化——vGIC

首发公号&#xff1a;Rand_cs 该项目来自乐敏大佬&#xff1a;https://github.com/minosproject/minos 这一节开始讲述真正的中断虚拟化&#xff0c;首先来看硬件方面的虚拟化。前文 minos 2.3 中断虚拟化——GICv2 管理 主要讲述 GICv2 的 Distributor 和 CPU Interface&…

Nvidia Jetson/Orin +FPGA+AI大算力边缘计算盒子:3D扫描仪 实时创建 VR 内容

虽然 VR 技术彻底改变了娱乐、医疗、建筑、教育和产品设计等各个日常生活领域&#xff0c;但创建 VR 内容仍然是一项不易突破的挑战。 英伟达在旧金山举行的 Jetson TX2发布会上&#xff0c;展示了Jetson TX2如何能够加快 AI 计算、图形和计算机视觉的运行速度&#xff0c;并且…

【一小时学会Charles抓包详细教程】Charles 抓包相关设置 (7)

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 Charles 抓包相…

数据库学习总结

Mysql学习总结 汇总数据 聚集函数&#xff1a; 函数 说明 AVG() 返回某列的平均值 COUNT() 返回某列的行数 MAX() 返回某列的最大值 MIN() 返回某列的最小值 SUM() 返回某列值之和 例&#xff1a; AVG函数&#xff1a; select avg(grade) from topic; COUNT函…

WiFi蓝牙模块促进传统零售数字化转型:智能零售体验再升级

随着科技的不断发展&#xff0c;数字化转型已经成为了各行各业的必然趋势。在传统零售业中&#xff0c;WiFi蓝牙模块的应用正逐渐推动着行业的数字化转型&#xff0c;为消费者带来更加智能化、便捷化的零售体验。本文MesoonRF美迅物联网将从以下几个方面阐述WiFi蓝牙模块在传统…

稍微学学react

文章开始前&#xff0c;先划划水~ 今日份开心&#xff1a; 今天看之前发布的按钮npm包下载量有162次&#xff0c;早知道好好做了 今日份不开心&#xff1a; 爬岗位看到一个整体都挺满意的岗位&#xff0c;公司位置和发展大方向都好喜欢&#xff01;&#xff01;&#xff01;…

机器学习学习(2)

基于数据流图的编程范式:声明式编程(Declarative Programming )、命令式编程(Imperative Programming ); 声明式编程(Declarative Programming ) 代表性框架:TensorFlow, CNTK, Caffe2 特点:用户只需要表达模型结构和需要执行的任务,无需关注底层的执行流程,框…

【UE+GIS】UE5GIS CAD或shp构建3D地形

贴合地形的矢量图形实现方法 一、灰度图的制作和拉伸换算1、基于高程点集实现2、基于等高线实现3、拉伸计算 二、生成地形模型的实现方案1、3Dmax导入灰度图2、使用ArcMap/Arcpro/FME等GIS数据处理工具3、UE导入灰度图 三、地形上叠加地形渲染效果的实现方案1、贴花2、数据渲染…

【transformers】pytorch基础

传送门&#xff1a;https://transformers.run/c2/2021-12-14-transformers-note-3/ pytorch基础知识 tensor &#xff1a; 张量。 需要知道的内容&#xff1a; 张量构建张量计算自动微分形状调整广播机制索引与切片升降维度 Tensor 张量&#xff1a;理解成高纬度的向量就完…

【最新鸿蒙应用开发】——什么是状态管理?

状态管理 在声明式UI编程框架中&#xff0c;UI是程序状态的运行结果&#xff0c;用户构建了一个UI模型&#xff0c;其中应用的运行时的状态是参数。当参数改变时&#xff0c;UI作为返回结果&#xff0c;也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染&#xf…

系统安全及其应用

系统安全&#xff1a; 1&#xff09;保护数据安全&#xff0c; 2&#xff09;互联网&#xff0c;网络业务服务等&#xff0c;必须要通过工信部的资质审核 3&#xff09;保护品牌形象 应用&#xff1a; 账号安全 1&#xff09;把不需要或者不想登录的用户设置为nologin us…

echarts绘制三维柱状图

echarts ECharts 是一个使用 JavaScript 实现的开源可视化库&#xff0c;主要用于数据的可视化展示。ECharts 支持丰富的图表类型&#xff0c;如折线图、柱状图、饼图、地图、K线图等&#xff0c;可以满足不同类型数据的展示需求。 文档地址&#xff1a;echarts 本次所绘制三…

Django request.POST获取提交的表单数据

在Django中&#xff0c;request.POST 是一个特殊的属性&#xff0c;它是一个类似于字典的对象&#xff0c;用于访问通过POST方法提交的表单数据。如果你在视图中使用 print(request.POST.get(username))&#xff0c;这通常意味着你正在尝试从一个HTML表单中获取一个名为 userna…

数学建模之MATLAB入门教程(上)

前言&#xff1a; • MATLAB是美国Math Works公司出品的商业数学软件&#xff0c;用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人&#xff0c;控制系统等领域。 • MATLAB将数值分析、矩阵计算、科学数据可视化以及非线性动…

Ubuntu server 24 (Linux) 普通用户不能sudo 也不能使用root登录 忘记root密码 修复解决方案

一 普通用户无法sudo&#xff0c;同时也没有其他用户可用 #test用户使用sudo报错&#xff0c;没有权限 testtest:~$ sudo vi /etc/sudoers [sudo] password for test: test is not in the sudoers file. 二 关闭ubuntu 服务器&#xff0c;重新开机 按下ESC 键 1 出现GRUB…