先说几个英文缩写:

GVA - Guest Virtual Address,虚拟机的虚拟地址

GPA - Guest Physical Address,虚拟机的物理地址

HVA - Host Virtual Address,宿主机虚拟地址,也就是对应kvmtool中申请的地址

HPA - Host Physical Address,宿主机物理地址

使用 kvmtool 启动arm guest时 (树莓派4b上测试),kvmtool为guest准备的系统内存分布如下 (kvmtool: arm/include/arm-common/kvm-arch.h),

使用如下命令启动guest时,那么guest 使用的”物理”内存(GPA)段为:0x80000000 ~ 0xBFFFFFFF。

lkvm run -k Image  --console virtio -i rootfs.cpio.gz   -c 2 -m 1024 -d /dev/ram0 --vsock 3

见guest 启动日志,

kvmtool需要为virtio设备分配mmio space、irq等,mmio space的起始地址是0x3000000(即guest内存分布图48M位置),

Info: virtio-mmio.devices=0x200@0x3000000:100
Info: virtio-mmio.devices=0x200@0x3000200:101
Info: virtio-mmio.devices=0x200@0x3000400:102
Info: virtio-mmio.devices=0x200@0x3000600:103

GPA是guest系统中假的物理地址,guest对GPA的访问,最终还是需要访问HVA,为此需要给GPA内存段0x80000000~0xBFFFFFFF准备同样长度的HVA地址段,kvmtool是通过 mmap 或 hugetlb 为guest准备HVA地址段 (kvmtool: arm/kvm.c: kvm__arch_init),设置完成后我们会得到如下数据,然后将GPA与HVA段注册到kvmtool中的mem_banks中(kvmtool:kvm.c:kvm__register_mem),

kvm__initkvm__arch_initkvm->ram_size = ram_size;kvm->ram_start = mmap_hugetlbfs(kvm, hugetlbfs_path, size)or mmap(NULL, size, PROT_RW, MAP_ANON_NORESERVE, -1, 0)madvise(kvm->ram_start, kvm->ram_size, MADV_MERGEABLE) // 相同页合并, KSM may merge identical pageskvm__init_ramphys_size  = kvm->ram_size;host_mem   = kvm->ram_start;kvm__register_ram(kvm, phys_start, phys_size, host_mem);
printf("GPA: %llx mapped to HVA %p with size %llx\n", kvm->arch.memory_guest_start, kvm->ram_start, kvm->ram_size);
eg,GPA: 80000000 mapped to HVA: 0x7f73a00000 with size 40000000

mem_banks主要用于在virtio 用户态设备驱动 (eg,virtio-console的后端处理)处理guest数据时完成GPA与HVA之间的转换(同时kvm__register_mem还会调用“ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &mem)” 将mem注册到内核kvm)。

例如,如果guest有串口数据需要host帮其显示,guest填充好数据后写virtio-console的VIRTIO_MMIO_QUEUE_NOTIFY字段实现kick host 然后VM_EXIT到 host(写 IO 指令导致虚拟机vm_exit),为virtio-console tx queue注册的线程会被 VIRTIO_MMIO_QUEUE_NOTIFY eventfd唤醒,tx queue处理函数 (VIRTIO_CONSOLE_TX_QUEUE callback) 会被调用,

static void virtio_console_handle_callback(struct kvm *kvm, void *param)
{struct iovec iov[VIRTIO_CONSOLE_QUEUE_SIZE];struct virt_queue *vq;u16 out, in, head;u32 len;vq = param;/** The current Linux implementation polls for the buffer* to be used, rather than waiting for an interrupt.* So there is no need to inject an interrupt for the tx path.*/while (virt_queue__available(vq)) {//从tx queue中获取iovhead = virt_queue__get_iov(vq, iov, &out, &in, kvm); //打印到terminal fd:writev(term_fds[term][TERM_FD_OUT], iov, iovcnt);len = term_putc_iov(iov, out, 0); virt_queue__set_used_elem(vq, head, len);}
}virt_queue__get_iov() -> virt_queue__get_head_iov() 从vring desc_chain中获取到GPA,并将其转化为HVA,do {/* Grab the first descriptor, and check it's OK. */iov[*out + *in].iov_len = virtio_guest_to_host_u32(vq, desc[idx].len);// HVA = guest_flat_to_host(kvm, GPA)iov[*out + *in].iov_base = guest_flat_to_host(kvm, virtio_guest_to_host_u64(vq, desc[idx].addr)); /* If this is an input descriptor, increment that count. */if (virt_desc__test_flag(vq, &desc[idx], VRING_DESC_F_WRITE))(*in)++;else(*out)++;} while ((idx = next_desc(vq, desc, idx, max)) != max);void *guest_flat_to_host(struct kvm *kvm, u64 offset)
{struct kvm_mem_bank *bank;list_for_each_entry(bank, &kvm->mem_banks, list) { u64 bank_start = bank->guest_phys_addr;u64 bank_end = bank_start + bank->size;// 找到GPA所在的bankif (offset >= bank_start && offset < bank_end) return bank->host_addr + (offset - bank_start); // GPA = HVA_start + GPA_offset}return NULL;
}

如果virtio设备后端驱动是vhost模式,那么host处理guest数据请求时,就需要在内核态完成GPA与HVA之间的转换,为此需要为vhost驱动把mem_banks注册到内核态,例如vsock(kvmtool: virtio/vsock.c)的注册,

        i = 0;list_for_each_entry(bank, &kvm->mem_banks, list) {mem->regions[i] = (struct vhost_memory_region) {.guest_phys_addr = bank->guest_phys_addr, //GPA start.memory_size     = bank->size,.userspace_addr  = (unsigned long)bank->host_addr, // HVA start};i++;}mem->nregions = i;r = ioctl(vdev->vhost_fd, VHOST_SET_MEM_TABLE, mem);if (r != 0)die_perror("VHOST_SET_MEM_TABLE failed");

内核中注册mem_banks流程如下(kernel: drivers/vhost/vhost.c),

vhost_dev_ioctl() -> vhost_set_memory() ->vhost_iotlb_add_range,
int vhost_iotlb_add_range(struct vhost_iotlb *iotlb, u64 start,u64 last, u64 addr, unsignedint perm)
{               struct vhost_iotlb_map *map;      map = kmalloc(sizeof(*map), GFP_ATOMIC);map->start = start; //GPA startmap->size = last - start + 1;map->last = last;   //GPA endmap->addr = addr;   //HVA startmap->perm = perm;iotlb->nmaps++;vhost_iotlb_itree_insert(map, &iotlb->root);//map信息插入到rb treeINIT_LIST_HEAD(&map->link);list_add_tail(&map->link, &iotlb->list);return0;
}

vhost设备驱动接收到guest的kick后,需要从ving desc_chain中获取GPA,转化为HVA后即可执行copy_from_iter获取guest填充的数据,

tx kick func(eg vhost_vsock_handle_tx_kick)-> vhost_get_vq_desc() -> translate_desc(),
static int translate_desc(struct vhost_virtqueue *vq, u64 addr, u32 len, struct iovec iov[], int iov_size, int access)
{const struct vhost_iotlb_map *map;//参数addr就是virtio前端驱动填充到vring desc_chain中的GPA,根据GPA找到mapmap = vhost_iotlb_itree_first(umem, addr, addr + len - 1); //size = map->size - offset (addr - map->start)size = map->size - addr + map->start;iov->iov_len = min((u64)len - s, size);//HVA = HVA_start + offset(GPA - GPA_start)//拿到HVA后,vhost设备驱动可通过copy_from_iter获取guest发送的数据了_iov->iov_base = (void __user *)(unsigned long)(map->addr + addr - map->start);
}

对于反方向的数据发送(host call guest)原理也是类似的。

上面说到 virtio 前端驱动向vring desc_chain中填充的是GPA,对于所有的 virtio 前端驱动处理流程都是一样的,以split vring为例,

virtqueue_add_sgsvirtqueue_addvirtqueue_add_split desc[i].addr = cpu_to_virtio64(_vq->vdev, addr); // addr is GPAdesc[i].len = cpu_to_virtio32(_vq->vdev, sg->length);

在virtio前端驱动填充GPA到desc_chain之前,如果guest需要向GPA中写入数据 (eg,virtio-blk的写IO操作、virtio-net的guest发包操作),其流程为 wirte data to GVA -> GPA -> HVA -> HPA, 对于arm-v8来说 GPA -> HPA 由Stage 2 translation完成。而上文讨论的GPA与HVA之间的转换不涉及Stage 2 translation。下图展示了多种地址转换所处的位置,

VIRTIO 前后端驱动中 GPA,HVA 转换原理相关推荐

  1. 前后端分离中使用基于jwt的token进行身份认证

    基于jwt的Token认证机制可以看之前的文章: 基于JWT的Token认证机制实现 在前后端分离中,我们与前端约定一种身份认证机制.当用户登录的时候,我们会返回给前端一个token,前端会将toke ...

  2. vue+node.js前后端交互中的token令牌验证

    这篇文章分享一下本人学习vue+node.js前后端交互中的登录token令牌的心得 最近准备写一个个人博客网站,前端采用的是vue+element,后端用node.js 在做用户登录的时候就想到 如 ...

  3. 前后端分离中的权限管理思路

    在传统的前后端不分的开发中,权限管理主要通过过滤器或者拦截器来进行(权限管理框架本身也是通过过滤器来实现功能),如果用户不具备某一个角色或者某一个权限,则无法访问某一个页面. 但是在前后端分离中,页面 ...

  4. 在 Spring Boot 前后端分离系统中集成 JustAuth 实现第三方账号登录?

    " JustAuth 是一个开箱即用的整合第三方登录的开源组件,网上没有搜到它在前后端分离系统中的使用案例,本篇文章将以 QQ 登录为例为大家讲解该场景下的使用步骤,建议收藏 " ...

  5. 关于大型网站技术演进的思考(十五)--网站静态化处理—前后端分离—中(7)

    出处:夏天的森林博客 上篇里我讲到了一种前后端分离方案,这套方案放到服务端开发人员面前比放在web前端开发人员面前或许得到的掌声会更多,我想很多资深前端工程师看到这样的技术方案可能会有种说不出来的矛盾 ...

  6. 关于大型网站技术演进的思考(十五)--网站静态化处理—前后端分离—中(7)...

    2019独角兽企业重金招聘Python工程师标准>>> 出处:夏天的森林博客 上篇里我讲到了一种前后端分离方案,这套方案放到服务端开发人员面前比放在web前端开发人员面前或许得到的掌 ...

  7. 前后端交互中,返回前端的自定义响应数据结构

    在前后端交互分离的体系下,我们后端一般会将返回给前端的信息数据进行格式的统一,然后返回前端json串:,一下我提供了一个模板,在实际开发中可进行参考 package com.project.util; ...

  8. 前后端分离中后端常用的响应类

    在前后端分离开发过程中,后端一般会将数据集封装成一个JSON对象响应给前端 package cn.wideth.response;import lombok.AllArgsConstructor; i ...

  9. 解决前后端分离中文件传输跨域失败问题

    笔者在之前的开发中就遇到过这种情况:给与后端的文件下载接口,我在浏览器在postman上试过都是可以的,但在前端上就报这种错误: 看上面的介绍是说前后端跨域问题,可是后端代码中明明配置了放行所有请求, ...

最新文章

  1. 自定义控件:QQ气泡效果粘性控件的实现
  2. php如何从左往右轮播,js实现从左向右滑动式轮播图效果
  3. 六.激光SLAM框架学习之A-LOAM框架---项目工程代码介绍---4.laserMapping.cpp--后端建图和帧位姿精估计(优化)
  4. Mego(05) - Mego Tools使用教程
  5. ansys 19.2无法连接到服务器证书,360运行时ansys19.0就无法连接许可服务了
  6. java文字水印模糊_使用Java给图片添加文字水印并消除锯齿
  7. 蚌埠住了,让我虎躯一震的代码!
  8. 浏览器有网微信没网络连接服务器,电脑可以登陆微信但是浏览器无法联网是怎么回事儿...
  9. safari 模拟手机显示
  10. java error while loading shared libraries: libjli.so: cannot open shared object file
  11. Python 标准库之 shutil 高阶文件操作『详细』
  12. 计算机无法找到手机热点,苹果手机个人热点找不到如何解决
  13. DNF史诗计算机最新版,dnf全职业史诗装备搭配计算器2020
  14. 奈学教育《大数据架构师》课程大纲
  15. 成为“黑客”前,必须学习的“计算机网络通信原理”
  16. 计算机网络:路由的概念及其分类
  17. 【linux内核分析与应用-陈莉君】动手实践-编写一个文件系统
  18. 想要让数据更生动?试试这5种图表工具
  19. 深入理解acquire和release原理源码及lock独有特性acquireInterruptibly和tryAcquireNanos
  20. SXSSFWorkbook导入/导出Excel文档简单操作

热门文章

  1. python中response对象的方法_django HttpResponse对象 - 刘江的django教程
  2. html5拨打电话自动录音,html5实现手机触摸出现录音以及离开停止录音并上传的功能(代码)...
  3. linux u盘版下载官网,Linux助手:Universal USB Installer新版下载
  4. 服务器使用固态硬盘的优缺点
  5. 2022年化工自动化控制仪表考试总结及化工自动化控制仪表模拟考试
  6. Graphics2D的使用详情
  7. 2020年某计算机菜鸡的中九保研之旅(武大、华科、北理、西交、中山)
  8. 163邮箱登录入口你知道吗?163邮箱登录方法大全
  9. Oracle Acs资深顾问罗敏 老罗技术核心感悟:11g的数据压缩技术
  10. 【物理应用】Matlab实现两端固支梁热力耦合的有限元分析