p2m 表项不只记"对应哪个 MFN",还带一个类型标签(p2m_type),决定访问它时的行为。这一篇把 Xen 的 p2m 类型讲透:完整枚举 + 各自用例,以及它们之上两个容易混淆的"分组/空间"概念——类型组掩码(把类型归类)和XENMAPSPACE(往 physmap 放页时的来源空间)。
1. p2m_type 是什么
Xen 的 p2m 表项 =MFN + 类型 + 权限。类型(p2m_type_t)告诉 Xen:当 guest 访问这个 GFN 时,该怎么处理缺页/写入/权限。
p2m[GFN] = { mfn, p2m_type, access_rights }同一个 GFN,类型不同,行为天差地别:有的直接读写真实内存,有的一访问就 exit 给 qemu 模拟,有的写入时标脏。
2. 完整的 p2m 类型枚举(x86)
x86 的p2m_type_t一共 16 种(xen/arch/x86/include/asm/p2m.h):
| 值 | 类型 | 含义 | 典型用例 |
|---|---|---|---|
| 0 | p2m_ram_rw | 普通可读写 RAM | guest 绝大多数常规内存 |
| 1 | p2m_invalid | 此处什么都没映射 | physmap 空洞;访问触发缺页 |
| 2 | p2m_ram_logdirty | 临时只读,写触发标脏 | 热迁移追踪脏页 |
| 3 | p2m_ram_ro | 只读 RAM,写被静默丢弃 | 只读区 |
| 4 | p2m_mmio_dm | 读写都交给 device model | 模拟设备寄存器(转 qemu) |
| 5 | p2m_mmio_direct | 直通真实 MMIO | PCI passthrough 的 BAR |
| 6 | p2m_populate_on_demand | 空内存占位(PoD) | 超额分配 / 延迟落地(气球) |
| 7 | p2m_grant_map_rw | 读写 grant 映射 | 跨域共享(netfront/blkfront) |
| 8 | p2m_grant_map_ro | 只读 grant 映射 | 同上,只读 |
| 9 | p2m_ram_paging_out | 正在换出的内存 | mem paging |
| 10 | p2m_ram_paged | 已换出 | mem paging |
| 11 | p2m_ram_paging_in | 正在换入 | mem paging |
| 12 | p2m_ram_shared | 可共享 / 已共享 | 内存去重(CoW / mem sharing) |
| 13 | p2m_ram_broken | 坏页 | 硬件故障页,访问即崩 domain |
| 14 | p2m_map_foreign | 映射另一个域的 RAM 页 | 跨域直接映射 |
| 15 | p2m_ioreq_server | 交给 ioreq server 处理 | 用户态设备模型介入的内存(如显存脏页跟踪) |
小注:ARM 上 foreign 细分成
p2m_map_foreign_rw/ro;x86 只有一个p2m_map_foreign,读写权限用访问位区分。
3. 类型分组:type 之上的"region"概念
Xen 内部几乎不逐个判断具体类型,而是把类型按位掩码归成组,配一组p2m_is_*谓词来判断。这就是"每个 type 归属哪个类别"的 region 概念(asm/p2m.h):
| 组(掩码) | 大致包含 | 谓词 | 语义 |
|---|---|---|---|
P2M_RAM_TYPES | ram_rw / ro / logdirty / paging* / shared / ioreq_server | p2m_is_ram | 映射到真实机器帧 |
P2M_MMIO_TYPES | mmio_dm / mmio_direct | p2m_is_mmio | 设备内存 |
P2M_GRANT_TYPES | grant_map_rw / ro | p2m_is_grant | grant 跨域映射 |
P2M_RO_TYPES | logdirty / ram_ro / grant_ro / shared | p2m_is_readonly | 只读,写需特殊处理 |
P2M_SPECIAL_TYPES | grant +p2m_map_foreign+ mmio_direct | p2m_is_special | 需专门访问器建立/拆除 |
P2M_PAGING_TYPES | paging_out / paged / paging_in | p2m_is_paging | 换页中 |
P2M_POD_TYPES | populate_on_demand | p2m_is_pod | 按需填充占位 |
| (单类) | p2m_map_foreign | p2m_is_foreign | 别的域的页 |
作用:建 EPT/NPT 表项、缺页处理、迁移、CoW、换页……都用这些组统一判断,而不是写死某个类型。例如写一个p2m_is_readonly的页,会统一走"写处理":logdirty 标脏、grant_ro 丢弃、shared 破 CoW。新增/调整一个类型时,只要把它放进对的组,全套逻辑自动适配。
4. 另一个"空间"概念:XENMAPSPACE(来源空间)
type 描述的是"这个 GFN 现在是什么"。而当你要往 guest physmap 里放一个页时(XENMEM_add_to_physmap[_batch]),还要给一个space参数——告诉 Xen"这个页从哪来"。space 决定了 Xen 怎么取 MFN、以及最终装成哪种 p2m 类型(xen/include/public/memory.h):
XENMAPSPACE_* | 值 | 来源 | 落成的 p2m 类型 |
|---|---|---|---|
shared_info | 0 | Xen 的 shared_info 页 | p2m_ram_rw |
grant_table | 1 | grant 表页 | p2m_ram_rw |
gmfn | 2 | 本域已有的一个 GFN | 重定位,类型随源 |
gmfn_range | 3 | 本域一段 GFN | 同上,批量 |
gmfn_foreign | 4 | 另一个域的 GFN | p2m_map_foreign(RAM)/p2m_mmio_direct(设备页) |
dev_mmio | 5 | 设备 MMIO 区(ARM) | 直通 MMIO |
还有一个值为 6 的扩展 space,留到下一篇结合具体机制讲。
要点:space 是"入口/来源",type 是"结果/属性"。同一段 GFN 经不同 space 放进来,会得到不同的 type——例如经gmfn_foreign放进来的 RAM 页,落成的就是p2m_map_foreign。所以回答"每个 type 下有没有 space 概念":它俩是"入口→结果"的对应关系,而非从属关系,一张对照表就能把 space 和 type 串起来。
type / region / space / class 一图看清
把前面几节的四个概念放在一起,它们的关系是:
- space → type:入口决定结果——同一 GFN 换个 space,落成的 type 不同。
- type → region:每个 type 按掩码归入若干组,Xen 用组(
p2m_is_*)统一判断,而非逐个类型。 - class:是"这是哪张 p2m 表"的维度,和 type / space正交(日常用
p2m_host)。
5. 跨域共享的两类 type:grant 与 foreign
5.1 为什么单独讲这两类
前面的 RAM/MMIO 类型都是"本域自己的内存"。而有两类 type 专门表示"这一页其实属于另一个域"——它们让同一块物理页同时出现在两个域里,是跨域零拷贝共享的地基,也是理解后续机制的关键,值得单独讲透。
先厘清:独占 vs 跨域、所有权 vs 映射,这是理解后续所有机制的基石,先分清两组概念:
- 独占:一个 host 物理页(MFN)只出现在一个domain 的 physmap 里,只有它能访问——普通的
p2m_ram_rw就是这样,每个 guest 的 RAM 都是分配给它私有的 MFN。 - 跨域共享:同一个 MFN 同时出现在多个 domain 的 physmap 里,于是这块真实物理内存能被多个域同时看到、访问。
独占: MFN_X ◄── domA 的某个 GFN (只有一条) 跨域: MFN_X ◄── domA 的 GFN ◄── domB 的 GFN (同一个 MFN,多条)再区分"所有权"和"映射"——这点最容易混:
- 所有权(owner):每个物理页永远只有一个 owner domain(
page_get_owner),共享不改变所有权。 - 映射(mapping):跨域做的是——在另一个域的 physmap 里也加一条指向这个 MFN 的项。
所以准确的说法是:跨域 = 一个页仍归某一个域所有,但被"额外"映射进了别的域的地址空间。为防止 owner 在别人还用着时回收它,跨域映射会对该页
get_page加一次引用计数,撤映射(remove_from_physmap)时再put_page释放。
下面两类 type,就是这种"额外映射"的两种建立方式,区别只在谁是 owner、谁主动、往哪个方向映射。
5.2 为什么这是"虚拟化能用"的性能基石
跨域共享看着只是个内存技巧,实则决定了虚拟化 I/O 是"能跑"还是"慢到不能用"。先把两层分开:
- CPU / 内存虚拟化(EPT/NPT 两级翻译)——靠硬件,本身没有拷贝问题,和共享无关,解决的是"能跑"。
- I/O、设备、GPU 数据通路——这层才是"拷贝 vs 共享"的战场,决定"跑得快不快"。
设想没有共享的 PV 网络:guest 发一个包要先拷到 dom0 的 backend,backend 再交给网卡;收包反向再来一遍。每个包至少两次内存拷贝 + 跨域切换,10/40Gbps 下光拷贝就吃满 CPU 和内存带宽。grant table 的设计初衷正是干掉这次拷贝——backend 直接读写 guest 那块内存,包"零拷贝"地过去。这也是 Xen PV 驱动能接近裸机的关键。GPU 更极端:几十 GB 的显存 blob,任何一次拷贝都不可接受,所以必须让 host GPU 栈与 guest 共享同一份物理页——没有跨域共享,GPU 虚拟化根本无从谈起。
但共享有代价:它削弱隔离。这正是虚拟化设计的核心张力——
| 拷贝 | 共享 | |
|---|---|---|
| 性能 | 差 | 好(零拷贝) |
| 隔离 | 强(各自独立) | 弱(同一页两个域都能碰) |
所以下面 grant/foreign 才要那套授权 + 引用计数 + notifier + XSM 校验:本质是"在尽量不破坏隔离的前提下,换取零拷贝的性能"。跨域共享机制的全部复杂度,都是在这架天平上找平衡。
5.3 grant 映射(p2m_grant_map_rw/ro)
- 谁发起:源域(内存拥有者)主动授权——它在自己的 grant table 里登记一条 grant entry(“允许 domX 访问我的第 N 页”),拿到一个 grant ref 交给对方。
- 谁映射:被授权方拿着 grant ref 发 hypercall,把那一页映射进自己的physmap,落成
p2m_grant_map_rw/ro。 - 方向:拥有者 → 授权给使用者。典型是 guest 把自己的收发缓冲授权给后端(netfront 授权给 dom0 的 netback)。
- 一个细节:Xen 认为 grant 项"随时可能被源域撤回",所以
p2m_is_valid特意不把 grant 算作 valid——用它的人必须容忍随时失效。
5.4 foreign 映射(p2m_map_foreign)
- 谁发起:有特权的目标域(通常 dom0)主动把"另一个域的某个 GFN"塞进自己的physmap,落成
p2m_map_foreign(内部走set_foreign_p2m_entry)。 - 不需要源域授权:靠发起方的特权(配
xsm_map_gmfn_foreign做 XSM 校验),源域甚至不感知。 - 方向:使用者主动去"借"拥有者的页。典型是 dom0 的工具 / 设备模型要读写某个 guest 的内存。
- 引用计数:映射时对源页
get_page加引用,XENMEM_remove_from_physmap时释放——保证映射期间源页不被回收/搬走。
5.5 两者对比
| grant | foreign | |
|---|---|---|
| 谁主动 | 源域授权 | 目标域(特权)直接映射 |
| 需授权? | 需要(grant ref) | 不需要(特权 + XSM) |
| 典型方向 | owner → user | user 主动借 owner |
| p2m 类型 | p2m_grant_map_rw/ro | p2m_map_foreign |
| 建立入口 | grant map hypercall | XENMAPSPACE_gmfn_foreign/set_foreign_p2m_entry |
| 生命周期 | 源域可随时撤 | 显式 remove 前有效 |
两者本质相同——让同一块物理页同时落在两个域的地址空间里,实现跨域零拷贝;区别只在"谁发起、要不要授权、怎么维护一致性"。后面要讲的机制,正是选了foreign 这一支来扩展,原因也正是上表透露的:由特权方主动、不依赖源域配合、适合成片大范围映射。至于它在 foreign 之上具体加了什么,留到下一篇。
6. 再往上一层:p2m 的"类"(host / nested / altp2m)
type(项属性)、space(入口)都是单张表层面的。整张 p2m 表还分类(p2m_class_t):
p2m_host:正常的 guest p2m,落到 EPT/NPT;p2m_nested:嵌套虚拟化里,L1 guest 给它的 L2 guest 用的 p2m;p2m_alternate(altp2m):同一个 guest 的多份 p2m 视图,可运行时切换(安全监控、写保护等用)。
这是"表级"的 region 概念,和 type(项级)、space(入口)互相正交。日常内存虚拟化主要用p2m_host。
7. 本篇小结 & 下一步
- p2m 项 =MFN + 类型 + 权限;类型决定访问行为。
- x86 共16 种p2m 类型,覆盖 RAM / MMIO / grant / foreign / paging / PoD / shared / broken / ioreq_server。
- 跨域共享靠两类 type:
p2m_grant_map_*(源域授权)与p2m_map_foreign(特权域主动借)——它们让"同一物理页出现在两个域",是后续机制的地基。 - 两个易混的"空间/分组"概念:①类型组掩码(
P2M_*_TYPES+p2m_is_*)是对"项属性"的分类;②XENMAPSPACE是"往 physmap 放页时的来源空间",决定落成哪种类型——space 是入口,type 是结果。 - 再往上还有表级的
p2m_class(host / nested / altp2m)。
下一篇:04 · 跨域协作全景:一张 p2m 表的五种填法 —— 有了这张"类型 + space"地图,下一篇把p2m_map_foreign这条主线铺成完整全景:p2m 被填充的5 种情形(1 种单域基准 + 4 种跨域协作),讲清每种的方向、授权模型、以及用什么标识源页(GFN 还是 hva)。之后再逐个深入具体机制——hmem 从第 5 篇起。
关联阅读
- 附录A:Xen 虚拟化术语表