StratoVirt地址空間管理-基於Rust的實現與優化

語言: CN / TW / HK
StratoVirt 是開源在 openEuler 社群的輕量級虛擬化平臺,具備輕量低噪、強安全性的行業競爭力。StratoVirt 程序執行在使用者態,在虛擬機器啟動之前, StratoVirt 會完成啟動之前的準備工作,包括虛擬機器記憶體初始化、CPU 暫存器初始化、裝置初始化等。其中,記憶體初始化工作和虛擬機器的地址空間管理, 都是由 StratoVirt 的地址空間管理模組 AddressSpace完成。如下是 StratoVirt 地址空間管理模組的組成,以及其在 StratoVirt 中的位置。
stratovirt
├── acpi
├── address_space
│   ├── Cargo.toml
│   └── src
│       ├── address.rs
│       ├── address_space.rs
│       ├── host_mmap.rs
│       ├── lib.rs
│       ├── listener.rs
│       ├── region.rs
│       └── state.rs
├── boot_loader
├── Cargo.lock
├── Cargo.toml
├── cpu
├── devices
├── docs
├── hypervisor
├── license
├── machine
├── machine_manager
├── Makefile
├── migration
├── migration_derive
├── ozone
├── pci
├── README.ch.md
├── README.md
├── src
├── sysbus
├── tests
├── util
├── vfio
└── virtio

StratoVirt 地址空間模組整體設計

上圖中的主要結構含義如下:

  • AddressSpace

    地址空間:為地址空間模組的管理結構, 負責整個虛擬機器的地址空間管理

  • Region

       代表一段地址區間,根據這段地址區間的使用者,可以分為以下型別:

  1. RAM:虛擬機器記憶體使用該段地址區間。
  2. IO:虛擬機器裝置使用該段地址區間。
  3. Container :作為容器使用,可以包含多個子 Region。如描述 PCI 匯流排域的地址管理就可以使用型別為 ContainerRegion,它可以包含 PCI 匯流排域下的 PCI 裝置使用的地址區間。該型別的 Region可以幫助管理並區分儲存器域、PCI 匯流排域的地址管理。
地址空間模組的設計,採用樹狀結構和平坦檢視結合的方案。通過樹狀結構可以快速的瞭解到各個 Region 之間的拓撲結構關係。這種分層、分類的設計, 可以管理並區分儲存器域與 PCI 匯流排域的地址管理,並形成與 PCI 裝置樹相呼應的樹狀管理結構。對於平坦檢視 FlatView,則是根據這些 Region的地址範圍和優先順序屬性形成的線性檢視。在通過地址空間管理結構 AddressSpace訪問裝置或者記憶體時, 使用平坦檢視 FlatView可以更加方便快捷地找到對應的 Region
在樹狀拓撲結構中,每個 Region都會對應一個優先順序 priority屬性,如果低優先順序的 Region佔用的地址區間和高優先順序的 Region佔用的地址區間重疊,則低優先順序的 Region 的重疊部分,將會被覆蓋,即在平坦檢視 FlatView中不可見。
樹狀拓撲結果的更新,很大可能會帶來平坦檢視 FlatView的更新。一些裝置或者模組需要獲取最新的平坦檢視,並相應的執行一些操作。例如 Vhost 裝置,需要將平坦檢視中的全部記憶體資訊同步到核心 Vhost 模組,以便通過共享記憶體方式完成訊息通知的流程。另外,我們也需要將已經分配並對映好的虛擬機器實體地址和宿主機虛擬地址資訊註冊到 KVM 模組,這樣可以藉助硬體輔助虛擬化加速記憶體訪問的效能。基於以上需求,我們引入上圖中的地址空間監聽函式連結串列,該連結串列在平坦檢視 FlatView更新後被依次呼叫,可以方便的完成資訊同步。該連結串列允許其他模組新增一個自定義的監聽回撥函式。

地址空間優化

作為 StratoVirt 的基礎模組和訪問密集型的模組, 地址空間模組不僅要滿足介面易用性、功能魯棒性,而且需要不斷優化效能。如下是迭代過程中的幾個優化點。

拓撲結構更新優化

地址空間管理模組提供向樹狀拓撲結構中新增和刪除 Region的介面,並設定 AddressSpace結構負責管理整個資料結構並生成更新後的 FlatView結構。
新增子 Region的方式為, 呼叫 Region結構的 add_subregion介面,注意父 Region必須是 Container型別。這樣會帶來一個問題,如果向樹狀結構中的某個 Region中新增或者刪除子 Region,並引起樹狀結構的拓撲發生變化,負責生成並更新平坦檢視的 FlatViewAddressSpace結構體如何得知已經發生變化呢?

最簡單的實現為在 Region結構中新增成員並指向自己所屬的 AddressSpace,如上圖所示。熟悉 Rust 語言的同學應該知道,這種實現方式會引入資源相互引用的問題,導致 AddressSpaceRegion兩者因相互引用而在生命週期結束時無法釋放記憶體資源的問題。因此,在地址空間模組的樹狀結構中,所有 Region對自己所屬的 AddressSpace的指標都使用 std::sync::Weak類指標, Weak指標不會增加所指向物件的引用計數,可確保在生命週期結束時對應結構的析構和資源釋放。
pub struct Region {
    region_type: RegionType,
    priority: Arc<AtomicI32>,
    size: Arc<AtomicU64>,
    offset: Arc<Mutex<GuestAddress>>,
    mem_mapping: Option<Arc<HostMemMapping>>,
    ops: Option<RegionOps>,
    io_evtfds: Arc<Mutex<Vec<RegionIoEventFd>>>,
    space: Arc<RwLock<Weak<AddressSpace>>>,
    subregions: Arc<RwLock<Vec<Region>>>,
}

鎖優化

鎖粒度最小化

為增加介面的易用性,地址空間模組的設計必須保證多執行緒安全性,作為主要介面的 AddressSpace結構體如下。可以看到, AddressSpace的關鍵成員都以 Arc<Mutex<..>>的方式保證了多執行緒共享的安全性。
pub struct AddressSpace {
    root: Region,
    flat_view: ArcSwap<FlatView>,
    listeners: Arc<Mutex<Vec<Box<dyn Listener>>>>,
    ioeventfds: Arc<Mutex<Vec<RegionIoEventFd>>>,
}
地址空間空間管理在設計時將鎖的粒度最小化,目的是降低多執行緒資料競爭帶來的影響。

鎖效能優化

// AddressSpace優化前結構
pub struct AddressSpace {
    root: Region,
    flat_view: Arc<RwLock<FlatView>>,
    listeners: Arc<Mutex<Vec<Box<dyn Listener>>>>,
    ioeventfds: Arc<Mutex<Vec<RegionIoEventFd>>>,
}
對於地址空間管理模組的關鍵資料結構,代表平坦檢視的 FlatView具有重要作用。其一,在樹狀拓撲結構發生變化時,例如新增和刪除 Region,會引起平坦檢視 FlatView發生變化,因此應該獲取 AddressSpaceflat_view成員的 寫鎖,用於更新平坦檢視;其二,裝置訪問記憶體、VCPU 退出到 StratoVirt 訪問裝置,都要通過 AddressSpaceflat_view成員,獲取 讀鎖,找到對應的 Region,然後進行讀寫操作。
但是,使用 Rust 讀寫鎖 RwLock仍然存在兩個問題:其一,經過測試,Rust 讀寫鎖的效能比互斥鎖差。而讀寫鎖和互斥鎖的效能均比原子型別差;其二,在某些場景下,地址空間管理模組需要實現 函式可重入的支援,即 在持有 FlatView讀鎖的情況下,仍可以對樹狀拓撲結構和平坦檢視 FlatView更新(例如,PCI bar 空間更新,需要通過 AddressSpace訪問裝置暫存器來設定地址,並將確定好地址的 PCI bar 空間新增到 AddressSpace中)。
基於上述問題和場景要求,使用 Rust 中的讀寫鎖和互斥鎖都會帶來問題。而 RCU 鎖不但能滿足多讀者、少寫者的併發情況,而且能允許讀寫同時進行的要求。通過自行實現和已有第三方庫的調研,最終我們選用 arc_swap第三方庫的 RCU-like 的機制,不但可以滿足可重入性的要求,而且通過地址空間模組訪問記憶體的效能可以提升 20%以上。
pub struct AddressSpace {
    root: Region,
    flat_view: ArcSwap<FlatView>,
    listeners: Arc<Mutex<Vec<Box<dyn Listener>>>>,
    ioeventfds: Arc<Mutex<Vec<RegionIoEventFd>>>,
}

關注我們

StratoVirt 當前已經在 openEuler 社群開源。後續我們將開展一系列主題分享,如果您對 StratoVirt 的使用和實現感興趣,歡迎您圍觀和加入。

專案地址:https://gitee.com/openeuler/stratovirt

專案 wiki:https://gitee.com/openeuler/stratovirt/wikis

您也可以訂閱郵件列表:https://mailweb.openeuler.org/postorius/lists/virt.openeuler.org/

如有疑問,也歡迎提交 issue:https://gitee.com/openeuler/stratovirt/issues

入群

如果您對虛擬化技術感興趣,可以進入 openEuler StratoVirt 主頁查詢相關資源,點選閱讀原文進入專案主頁,包括安裝指導、虛擬機器配置、程式碼倉庫、學習資料等。也歡迎加入Virt SIG 技術交流群,討論 StratoVirt、KVM、QEMU 和 Libvirt 等相關虛擬化技術。感興趣的同學可以新增如下微信小助手,回覆 StratoVirt 入群。

本文分享自微信公眾號 - openEuler(openEulercommunity)。
如有侵權,請聯絡 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。