Kernel Exploring
  • 前言
  • 支持
  • 老司机带你探索内核编译系统
    • 编译出你的第一个内核
    • 内核编译中的小目标
    • 可能是kbuild中最直接的小目标 – help
    • 使用了一个kbuild函数的目标 – cscope
    • 内核中单个.o文件的编译过程
    • 根目录vmlinux的编译过程
    • 启动镜像bzImage的前世今生
    • setup.bin的诞生记
    • 真假vmlinux–由vmlinux.bin揭开的秘密
    • bzImage的全貌
    • kbuild系统浅析
  • 启动时的小秘密
    • INIT_CALLS的秘密
    • 内核参数
  • 内核加载全流程
    • bootloader如何加载bzImage
    • 内核压缩与解压
    • 内核加载的几个阶段
    • 保护模式内核代码赏析
  • 内存管理
    • 内核页表成长记
      • 未解压时的内核页表
      • 内核早期的页表
      • cleanup_highmap之后的页表
      • 映射完整物理地址
      • 启用init_level4_pgt
    • 自底而上话内存
      • e820从硬件获取内存分布
      • 原始内存分配器--memblock
      • 页分配器
        • 寻找页结构体的位置
        • 眼花的页结构体
        • Node-Zone-Page
        • 传说的伙伴系统
        • Compound Page
        • GFP的功效
        • 页分配器的用户们
      • slub分配器
        • slub的理念
        • 图解slub
      • 内存管理的不同粒度
      • 挑战和进化
        • 扩展性的设计和实现
        • 减少竞争 per_cpu_pageset
        • 海量内存
        • 延迟初始化
        • 内存热插拔
        • 连续内存分配器
    • 虚拟内存空间
      • 页表和缺页中断
      • 虚拟地址空间的管家--vma
      • 匿名反向映射的前世今生
      • 图解匿名反向映射
      • THP和mapcount之间的恩恩怨怨
      • 透明大页的玄机
      • NUMA策略
      • numa balance
      • 老版vma
    • 内存的回收再利用
      • 水线
      • Big Picture
      • 手动触发回收
      • Page Fram Reclaim Algorithm
      • swapfile原理使用和演进
    • 内存隔离
      • memcg初始化
      • 限制memcg大小
      • 对memcg记账
    • 通用
      • 常用全局变量
      • 常用转换
    • 测试
      • 功能测试
      • 性能测试
  • 中断和异常
    • 从IDT开始
    • 中断?异常?有什么区别
    • 系统调用的实现
    • 异常向量表的设置
    • 中断向量和中断函数
    • APIC
    • 时钟中断
    • 软中断
    • 中断、软中断、抢占和多处理器
  • 设备模型
    • 总线
    • 驱动
    • 设备
    • 绑定
  • nvdimm初探
    • 使用手册
    • 上帝视角
    • nvdimm_bus
    • nvdimm
    • nd_region
    • nd_namespace_X
    • nd_dax
      • dev_dax
  • KVM
    • 内存虚拟化
      • Qemu内存模型
      • KVM内存管理
  • cgroup
    • 使用cgroup控制进程cpu和内存
    • cgroup文件系统
    • cgroup层次结构
    • cgroup和进程的关联
    • cgroup数据统计
  • 同步机制
    • 内存屏障
    • RCU
  • Trace/Profie/Debug
    • ftrace的使用
    • 探秘ftrace
    • 内核热补丁的黑科技
    • eBPF初探
    • TraceEvent
    • Drgn
  • 内核中的数据结构
    • 双链表
    • 优先级队列
    • 哈希表
    • xarray
    • B树
    • Maple Tree
    • Interval Tree
  • Tools
  • Good To Read
    • 内核自带文档
    • 内存相关
    • 下载社区邮件
Powered by GitBook
On this page
  • Qemu中的内存模型
  • 树形和平面的内存模型
  • AddressSpace
  • MemoryRegion
  • FlatView/FlatRange
  • 大串联
  • 瞅一眼代码逻辑
  • 握手KVM
  • NOTE
  1. KVM
  2. 内存虚拟化

Qemu内存模型

Qemu中的内存模型

相信看过源代码的朋友会有这样的体会Qemu中有着一个非常庞(奇)大(葩)的内存模型。对于头一次看 代码的小伙伴估计很容易Lost,因为会有很多相互关联有长得很像的小伙伴们

  • AddressSpace

  • MemoryRegion/MemoryRegionSection

  • FlatView/FlatRange

  • RAMBlock

反正第一次看得我几乎要吐了。接下来我们就一点点揭开这个庞然大物吧。

树形和平面的内存模型

AddressSpace

AddressSpace是Qemu内存模型最顶层的数据结构,其他的概念都通过它链接。

整个虚拟机中也不只有一个AddressSpace,通过GDB的调试,我们可以看到完整的虚拟机中有如下这些:

(gdb) dump_address_spaces 0
AddressSpace : memory(0x5555566ed920)
     Root MR : 0x555556804b00
    FlatView : 0x555557a2e650
AddressSpace : I/O(0x5555566ed8c0)
     Root MR : 0x5555567fbf00
    FlatView : 0x555557a47d30
AddressSpace : cpu-memory-0(0x555556877460)
     Root MR : 0x555556804b00
    FlatView : 0x555557a2e650
AddressSpace : cpu-smm-0(0x555556877640)
     Root MR : 0x5555567e0400
    FlatView : 0x555557a3b800
AddressSpace : i440FX(0x5555569de1a0)
     Root MR : 0x5555569de200
    FlatView : 0x5555568301e0
AddressSpace : PIIX3(0x555557177830)
     Root MR : 0x555557177890
    FlatView : 0x5555568301e0
AddressSpace : VGA(0x555557257c30)
     Root MR : 0x555557257c90
    FlatView : 0x5555568301e0
AddressSpace : e1000(0x7ffff7e08220)
     Root MR : 0x7ffff7e08280
    FlatView : 0x5555568301e0
AddressSpace : piix3-ide(0x5555576e3840)
     Root MR : 0x5555576e38a0
    FlatView : 0x5555568301e0
AddressSpace : PIIX4_PM(0x55555781e7b0)
     Root MR : 0x55555781e810
    FlatView : 0x5555568301e0

具体其中的关联和区别,小编现在还不清楚。待我慢慢深究,这里先放下不表。

MemoryRegion

顾名思义,这是用来表达一段内存区域的。其中重要的两个成员就是:addr和size,表示了这段内存区域 对应的起始地址和大小。

从内存模型的角度出发,MemoryRegion重要的特征是形成了一棵内存区域树。

来一个简单的图示意一下:

                            struct MemoryRegion
                            +------------------------+                                         
                            |name                    |                                         
                            |  (const char *)        |                                         
                            +------------------------+                                         
                            |addr                    |                                         
                            |  (hwaddr)              |                                         
                            |size                    |                                         
                            |  (Int128)              |                                         
                            +------------------------+                                         
                            |subregions              |                                         
                            |    QTAILQ_HEAD()       |                                         
                            +------------------------+                                         
                                       |
                                       |
               ----+-------------------+---------------------+----
                   |                                         |
                   |                                         |
                   |                                         |

     struct MemoryRegion                            struct MemoryRegion
     +------------------------+                     +------------------------+
     |name                    |                     |name                    |
     |  (const char *)        |                     |  (const char *)        |
     +------------------------+                     +------------------------+
     |addr                    |                     |addr                    |
     |  (hwaddr)              |                     |  (hwaddr)              |
     |size                    |                     |size                    |
     |  (Int128)              |                     |  (Int128)              |
     +------------------------+                     +------------------------+
     |subregions              |                     |subregions              |
     |    QTAILQ_HEAD()       |                     |    QTAILQ_HEAD()       |
     +------------------------+                     +------------------------+

耳听为虚,眼见为实

那我们来看看一个MemoryRegion的树形结构会是什么样子的。

(gdb) dump_memory_region 0x555556804b00
Dump MemoryRegion:system
[0000000000000000-100000000ffffffff]:system
  [00000000fee00000-000000000feefffff]:apic-msi
  [00000000000ec000-000000000000effff]:pam-ram
  [00000000000ec000-000000000000effff]:pam-pci
  [00000000000ec000-000000000000effff]:pam-rom
  [00000000000a0000-000000000000bffff]:smram-region
  [00000000fed00000-000000000fed003ff]:hpet
  [00000000fec00000-000000000fec00fff]:ioapic
  [0000000000000000-00000000007ffffff]:ram-below-4g
  [0000000000000000-100000000ffffffff]:pci
    [00000000000a0000-000000000000bffff]:vga-lowmem
    [00000000000c0000-000000000000dffff]:pc.rom
    [00000000000e0000-000000000000fffff]:isa-bios
    [00000000fffc0000-000000000ffffffff]:pc.bios

怎么样,小编没有骗你吧。

FlatView/FlatRange

再来一次顾名思义,FlatView就是平面视图。那是啥的平面视图呢?我就知道你聪明,不用猜就知道。 是MemoryRegion的平面视图。刚才咱不是看了么,MemoryRegion形成了一棵高大雄伟的树,但是 要用的时候还是得铺平了看起来舒服。

和刚才一样,我们也来瞅一眼这个数据结构的样子。

FlatView (An array of FlatRange)
+----------------------+
|nr                    |
|nr_allocated          |
|   (unsigned)         |         FlatRange             FlatRange
+----------------------+         
|ranges                | ------> +---------------------+---------------------+
|   (FlatRange *)      |         |offset_in_region     |offset_in_region     |
+----------------------+         |                     |                     |
                                 +---------------------+---------------------+
                                 |addr(AddrRange)      |addr(AddrRange)      |
                                 |    +----------------|    +----------------+
                                 |    |start (Int128)  |    |start (Int128)  |
                                 |    |size  (Int128)  |    |size  (Int128)  |
                                 +----+----------------+----+----------------+
                                 |mr                   |mr                   |
                                 | (MemoryRegion *)    | (MemoryRegion *)    |
                                 +---------------------+---------------------+

FlatRange是FlatView的一个成员,这个成员是一个一维数组,每个项目代表了平面视图上的一段内存空间。

(gdb) dump_flatview 0x555557a2e650
[00000000000000000-000000000000a0000], offset_in_region 0000000000000000
[000000000000a0000-000000000000c0000], offset_in_region 0000000000000000
[000000000000c0000-000000000000e0000], offset_in_region 0000000000000000
[000000000000e0000-00000000000100000], offset_in_region 0000000000020000
[00000000000100000-00000000008000000], offset_in_region 0000000000100000
[000000000fec00000-000000000fec01000], offset_in_region 0000000000000000
[000000000fed00000-000000000fed00400], offset_in_region 0000000000000000
[000000000fee00000-000000000fef00000], offset_in_region 0000000000000000
[000000000fffc0000-00000000000000000], offset_in_region 0000000000000000

嗯,眼花了,大家凑合看一下~

大串联

看过了这么几个鼎鼎有名的数据结构,接下来我们来看看这几者之间的关联。这样你就能有一个比较全局的 视野了。

AddressSpace               
+-------------------------+
|name                     |
|   (char *)              |          FlatView (An array of FlatRange)
+-------------------------+          +----------------------+
|current_map              | -------->|nr                    |
|   (FlatView *)          |          |nr_allocated          |
+-------------------------+          |   (unsigned)         |         FlatRange             FlatRange
|                         |          +----------------------+         
|                         |          |ranges                | ------> +---------------------+---------------------+
|                         |          |   (FlatRange *)      |         |offset_in_region     |offset_in_region     |
|                         |          +----------------------+         |                     |                     |
|                         |                                           +---------------------+---------------------+
|                         |                                           |addr(AddrRange)      |addr(AddrRange)      |
|                         |                                           |    +----------------|    +----------------+
|                         |                                           |    |start (Int128)  |    |start (Int128)  |
|                         |                                           |    |size  (Int128)  |    |size  (Int128)  |
|                         |                                           +----+----------------+----+----------------+
|                         |                                           |mr                   |mr                   |
|                         |                                           | (MemoryRegion *)    | (MemoryRegion *)    |
|                         |                                           +---------------------+---------------------+
|                         |
|                         |
|                         |
|                         |          MemoryRegion(system_memory/system_io)
+-------------------------+          +----------------------+
|root                     |          |                      | root of a MemoryRegion
|   (MemoryRegion *)      | -------->|                      | tree
+-------------------------+          +----------------------+

丑是丑了点,但是中心思想还是比较清晰的。

每个AddressSpace关联着一棵MemoryRegion树,而随着这棵树的变化对应着一个内存空间的平面视图FlatView.

这样是不是知道这么一大坨玩的是什么花样了?

瞅一眼代码逻辑

上来看了这么动(呆)人(板)的图片,接着来电代码接接地气。

memory_region_add_subregion()  // 添加subregion
    ...
    memory_region_transaction_commit()
        ...
        flatviews_reset()      // 重新生成FlatView
        address_space_set_flatview()
            ...
            address_space_update_topology_pass()
                ...
                MEMORY_LISTENER_UPDATE_REGION(region_add/del)  // 调用相应的region_add/del

在实际的编程过程中,直接操作的是MemoryRegion这棵树,而其对应的FlatView将会根据需要生成。

在理解了这些内容之后,就可以来看看之前没有提及的内容了。也就是上述代码片段中最后一行,在这一行中 我们将会看到Qemu的内存模型是如何逐渐和KVM联系在一起的。

握手KVM

当内存树发生变化,Qemu就会对相关的FlatView调用相应的回调函数,而Qemu的内存模型就是在这个过程中 和KVM发生联系的。

在Qemu初始化的过程中,通过kvm_memory_listener_register()注册了相应的回调函数kvm_region_add/del。 所以这个对应的回调函数就是kvm_region_add/del。

具体的细节就不在这里深究了,我们来看一下究竟Qemu告诉了KVM什么。

struct kvm_userspace_memory_region {
	__u32 slot;
	__u32 flags;
	__u64 guest_phys_addr;
	__u64 memory_size; /* bytes */
	__u64 userspace_addr; /* start of the userspace allocated memory */
};

Qemu和KVM之间的通信格式就是上面的这段数据结构。很清楚,我们看到他们交换了这么几个重要的信息

  • GPA: guest_phys_addr

  • HVA: userspace_addr

好了,你懂了。

NOTE

Previous内存虚拟化NextKVM内存管理

Last updated 1 year ago

本节使用到的GDB脚本可以在中获得。

gdb-script