目录
虚拟化技术栈 · QEMU

QEMU FlatView:用 RCU 分离映射发布与回收

通过 RCU 发布新映射,并在引用归零、已有读者退出后回收旧视图。

memory: protect current_map by RCUPaolo Bonzini · 原作者提交说明与修改对照

1问题背景#

设备热插拔或内存映射变更会更新地址空间,而 vCPU 与 IOThread 还在读取映射。逐次保护 current_map 的互斥锁会让这些读者竞争;直接替换并释放又会留下悬空指针。

2原有方案#

历史实现用 flat_view_mutex 保护 current_map。Paolo Bonzini 的修改说明直接指出,多 IOThread 和大系统会在这里产生 futex 争用。

3设计方案#

将映射发布与内存回收分开:新视图准备完成后通过 RCU 指针发布;旧视图引用归零后,由 call_rcu 将销毁推迟到已有读者退出之后。

4改进分析#

读者在 RCU 临界区取得视图,无须为 current_map 竞争同一把互斥锁。引用计数负责显式持有的视图引用,RCU 保护临界区内尚未结束的读取,两者共同确定安全回收的时机。

5源码分析#

5.1RCU 读侧临界区#

在 RCU 临界区取 current_map,再沿着该视图执行读取。它不会在一次访问中主动切换成刚发布的另一份视图。

system/physmem.cv9.2.0 · L2942–L2955
查看原文件
2942MemTxResult address_space_read_full(AddressSpace *as, hwaddr addr,2943                                    MemTxAttrs attrs, void *buf, hwaddr len)2944{2945    MemTxResult result = MEMTX_OK;2946    FlatView *fv;29472948    if (len > 0) {2949        RCU_READ_LOCK_GUARD();2950        fv = address_space_to_flatview(as);2951        result = flatview_read(fv, addr, attrs, buf, len);2952    }29532954    return result;2955}
address_space_read_full

读侧先进入 RCU 临界区,再取得当前 FlatView,并在同一临界区内完成读取。保护的是映射对象生命周期;设备回调和 RAM 内容仍有各自同步规则。

5.2新视图发布与区域回调#

在 BQL 保护下执行区域差异回调,再发布新指针;旧视图仍可能被读者使用,事务级 commit 在发布后执行。

system/memory.cv9.2.0 · L1099–L1119
查看原文件
1099    if (old_view) {1100        flatview_ref(old_view);1101    }11021103    flatview_ref(new_view);11041105    if (!QTAILQ_EMPTY(&as->listeners)) {1106        FlatView tmpview = { .nr = 0 }, *old_view2 = old_view;11071108        if (!old_view2) {1109            old_view2 = &tmpview;1110        }1111        address_space_update_topology_pass(as, old_view2, new_view, false);1112        address_space_update_topology_pass(as, old_view2, new_view, true);1113    }11141115    /* Writes are protected by the BQL.  */1116    qatomic_rcu_set(&as->current_map, new_view);1117    if (old_view) {1118        flatview_unref(old_view);1119    }
address_space_set_flatview

更新者持有 BQL,先持有新旧视图引用并调用 MemoryListener 的区域差异回调,再用 qatomic_rcu_set 发布新视图。事务级 commit 回调在所有 AddressSpace 更新后执行。旧视图的临时引用在完整函数末尾另行归还,不能把片段中的一次 unref 理解为全部生命周期处理。

5.3引用归零后的延迟回收#

引用计数归零只触发延迟回收,已有 RCU 读者退出后才运行销毁回调。

system/memory.cv9.2.0 · L310–L317
查看原文件
310void flatview_unref(FlatView *view)311{312    if (qatomic_fetch_dec(&view->ref) == 1) {313        trace_flatview_destroy_rcu(view, view->root);314        assert(view->root);315        call_rcu(view, flatview_destroy, rcu);316    }317}
flatview_unref

最后一个引用归还后,call_rcu 登记延迟销毁回调;回调在登记时已经存在的 RCU 读侧临界区全部结束后执行。普通引用计数和 RCU 各自保护不同的使用方式。

6适用范围与限制#

回收延迟会让新旧视图短期并存;写者仍由 BQL 串行化。映射安全不等于设备状态、RAM 内容或整个虚拟机操作都自动成为无锁快照。

固定于 QEMU v9.2.0 的 FlatView 生命周期。2015 年提交用于说明互斥锁对照方案与设计动机;未对该版本的性能进行实测。

7参考资料#

memory: protect current_map by RCU

QEMU v9.2.0 RCU 使用说明

QEMU v9.2.0 内存 API

互斥锁对照与动机来自原作者提交;性能收益未在本站实测。

65 个匹配案例