簡介:
內存虛擬化就是為虛擬機提供內存,使得虛擬機能夠像在物理機上正常工作,這需要虛擬化軟件為虛擬機展示一種物理內存的假象,內存虛擬化是虛擬化技術中關鍵技術之一。
qemu模擬虛機內存,核心是維護虛機物理地址空間。這個地址空間既要方便qemu管理,向虛機側提供內存,又要方便展示和導出,向平台側提供內存視圖。因此qemu抽象的內存區域有兩種組織結構,一種是樹狀的,用於qemu管理並模擬內存,一種是扁平的,用於展示和導出內存視圖,也方便傳遞給KVM。
- 樹狀視圖有兩個元素,一是AddressSpace,表示一個cpu可訪問的地址空間;一是MemoryRegion,表示一段邏輯內存區域。AddressSpace地址空間由許多邏輯內存區域MemoryRegion組成
- 扁平化視圖同樣有兩個元素,一是FlatView,cpu可訪問地址空間的扁平化表示;一是FlatRange,邏輯內存區域的扁平化描述,表示一段段內存區域。同樣地,FlatView由許多FlatRange組成。
主要結構體間關系:

1. 關鍵數據結構
1.1 AddressSpace
結構體AddressSpace用來表示虛擬機的一片地址空間,不同的設備使用的地址空間不同,但qemu x86中只有兩種, address_space_memory和address_space_io,這也是兩個全局的address_space變量,所有設備的地址空間都被映射到了這兩個上面。其root指向根MemoryRegion, 對於全局變量address_space_memory而言,其root指向系統全局的system_memory,address_space_io的root則指向system_io.由於根MR可能有自己的若干個subregion,因此每個AddressSpace一般包含一系列MemoryRegion,形成樹狀結構。
struct AddressSpace {
/* All fields are private. */
struct rcu_head rcu;
char *name;
MemoryRegion *root; //地址空間的根節點,其他的MemoryReagion會alia到這個節點
/* Accessed via RCU. */
struct FlatView *current_map; //平坦視圖
int ioeventfd_nb;
struct MemoryRegionIoeventfd *ioeventfds;
QTAILQ_HEAD(memory_listeners_as, MemoryListener) listeners; //監聽鏈表的頭部,用於當地址空間發生變化時通知qemu其它模塊或者內核
QTAILQ_ENTRY(AddressSpace) address_spaces_link; //地址空間的鏈表
};
AdressSpace初始化
- 地址空間的初始化有三個地方,1是靜態全局鏈表,2是qemu准備cpu執行環境時,3是qemu初始化特定硬件類型時。下面分別介紹
1. 全局鏈表初始化:qemu有鏈表將所有地址空間組織到一起,全局變量address_spaces指向這個鏈表的頭
static QTAILQ_HEAD(, AddressSpace) address_spaces = QTAILQ_HEAD_INITIALIZER(address_spaces);
- qemu准備cpu執行環境:將系統內存和IO內存的Root MR和地址空間都進行了初始化。Root MR作為地址空間初始化的輸入
main() // vl.c
cpu_exec_init_all() // exec.c
memory_map_init() // exec.c
這里共創建了兩個全局的AS: address_space_memory、address_space_io
初始化后的地址空間構造如下:

1.2 MemoryRegion
QEMU通過MemoryRegion來管理虛擬機內存,通過內存屬性,GUEST物理地址等特點對內存分類,就形成了多個MemoryRegion,這些MemoryRegion 通過樹狀組織起來,掛接到根MemoryRegion下。每個MemoryRegion樹代表了一類作用的內存,如系統內存空間(system_memory)或IO內存空間(system_io),這兩個是qemu中的兩個全局MemoryRegion。
struct MemoryRegion {
Object parent_obj;
bool ram; //標記是否為ram類型的MR
bool readonly; //For RAM regions,標記是否為ROM類型的MR
RAMBlock *ram_block; //實際申請的物理內存塊信息,不為null則關聯一段實際內存
const MemoryRegionOps *ops; // 是否為MMIO類型的MR
MemoryRegion *container; //MemoryRegion所屬的父容器,當前MR為subregion
Int128 size; //虛機內存的物理地址大小
hwaddr addr; //虛機內存的絕對物理地址
bool terminates; //是否為葉子節點
MemoryRegion *alias; //指向別名所在的MR
hwaddr alias_offset; //相對別名MR的起始地址偏移
int32_t priority; //優先級
QTAILQ_HEAD(subregions, MemoryRegion) subregions; //容器MR的子MR組成的鏈表頭部
QTAILQ_ENTRY(MemoryRegion) subregions_link; //用於將子MR組織成鏈表的成員
const char *name; //當前MR的名稱,如根節點為system
};
pc.ram的初始化流程
pc.ram屬於qemu初始化過程中申請的最大一部分內存,可以基於此分析其他內存塊的申請
main() // vl.c
cpu_exec_init_all() // exec.c
memory_map_init() // exec.c
address_space_init() // memory.c
machine_run_board_init() // hw/core/machine.c
machine_class->init() // pc_init1, hw/i386/pc_piix.c
pc_cpus_init() // hw/i386/pc.c
pc_memory_init() // hw/i386/pc.c
memory_region_allocate_system_memory // numa.c
allocate_system_memory_nonnuma // numa.c
memory_region_init_ram_shared_nomigrate // memory.c
qemu_ram_alloc // exec.c
ram_block_add // exec.c
phys_mem_alloc // exec.c
qemu_anon_ram_alloc // util/oslib-win32.c
MemoryRegion的組織關系

AOSP中內存分布

1.3 MemoryListener
1. 當內存地址空間有變化時,比如添加一個MR或者刪除一個MR,整個地址空間都會變化,某些感興趣的實體可能想要讓自己被通知到並調用提前注冊的鈎子函數,這些函數的原型就在這里定義。一個MemoryListener可以只實現其中部分
2. MemoryListener代表的是某個對地址空間感興趣的實體,這些實體不只一個,需要被管理起來,有兩個地方管理這些實體,一是全局鏈表memory_listeners,它管理所有注冊的Listener,結構體的link成員用作連接到這個鏈表。二是地址空間,它管理對自己感興趣的Listener,地址空間的listeners成員維護這個鏈表頭,結構體的link_as成員用作鏈接到這個鏈表。成員的address_space指向這個所屬的地址空間。同時所有listener有一個優先級,由priority表示,決定了在鏈表中的順序。
/**
* MemoryListener: callbacks structure for updates to the physical memory map
*
* Allows a component to adjust to changes in the guest-visible memory map.
* Use with memory_listener_register() and memory_listener_unregister().
*/
struct MemoryListener {
void (*begin)(MemoryListener *listener);
void (*commit)(MemoryListener *listener); //執行內存變更所需的函數
void (*region_add)(MemoryListener *listener, MemoryRegionSection *section); //在添加region的時候被調用
void (*region_del)(MemoryListener *listener, MemoryRegionSection *section);
void (*region_nop)(MemoryListener *listener, MemoryRegionSection *section);
void (*log_start)(MemoryListener *listener, MemoryRegionSection *section,
int old, int nw); //跟臟頁機制的開啟合同步有關
void (*log_stop)(MemoryListener *listener, MemoryRegionSection *section,
int old, int nw);
void (*log_sync)(MemoryListener *listener, MemoryRegionSection *section);
void (*log_global_start)(MemoryListener *listener);
void (*log_global_stop)(MemoryListener *listener);
void (*eventfd_add)(MemoryListener *listener, MemoryRegionSection *section,
bool match_data, uint64_t data, EventNotifier *e);
void (*eventfd_del)(MemoryListener *listener, MemoryRegionSection *section,
bool match_data, uint64_t data, EventNotifier *e);
void (*coalesced_mmio_add)(MemoryListener *listener, MemoryRegionSection *section,
hwaddr addr, hwaddr len);
void (*coalesced_mmio_del)(MemoryListener *listener, MemoryRegionSection *section,
hwaddr addr, hwaddr len);
/* Lower = earlier (during add), later (during del) */
unsigned priority; //表示優先級, 優先級低的在田間是會被優先調用,在刪除時會最后被調用
AddressSpace *address_space; //Listener對應的地址空間
QTAILQ_ENTRY(MemoryListener) link;
QTAILQ_ENTRY(MemoryListener) link_as;
};
1.3.1 全局的memory_listeners
首先系統有一個全局的memory_listeners,上面掛上了所有的MemoryListener
// memory.c
static QTAILQ_HEAD(, MemoryListener) memory_listeners
= QTAILQ_HEAD_INITIALIZER(memory_listeners);
1.3.2 memoryListener注冊
MemoryListener是通過 memory_listener_register注冊的,針對haxm的memoryListerner注冊流程如下
hax_accel_class_init() //hax-all.c
hax_accel_init() //hax-all.c
hax_init() //hax-all.c
hax_memory_init() //hax-mem.c
memory_listener_register(&hax_memory_listener, &address_space_memory) //hax-mem.c
1.3.3 MemoryListener通知
MemoryListener注冊的回調函數會在內存進行更新的時候被調用。
進行內存更新有很多情況,比如:
- 將一個mr添加到另一個mr的subregions中memory_region_add_subregion
- 更改了一端內存的屬性memory_region_set_readonly
- 將一個mr設置使能或者非使能memory_region_set_enabled
以添加subregion為例,通知鏈調用執行流程如下:
void memory_region_add_subregion(MemoryRegion *mr,
hwaddr offset,
MemoryRegion *subregion)
{
subregion->priority = 0;
memory_region_add_subregion_common(mr, offset, subregion);
}
static void memory_region_update_container_subregions(MemoryRegion *subregion)
{
memory_region_transaction_begin();
...
memory_region_transaction_commit();
}
最終會通過memory_region_transaction_commit函數實現鏈表遍歷
//memory.c
void memory_region_transaction_commit(void)
{
AddressSpace *as;
assert(memory_region_transaction_depth);
assert(qemu_mutex_iothread_locked());
--memory_region_transaction_depth;
if (!memory_region_transaction_depth) {
if (memory_region_update_pending) {
flatviews_reset();
MEMORY_LISTENER_CALL_GLOBAL(begin, Forward);
QTAILQ_FOREACH(as, &address_spaces, address_spaces_link) {
address_space_set_flatview(as);
address_space_update_ioeventfds(as);
}
memory_region_update_pending = false;
ioeventfd_update_pending = false;
MEMORY_LISTENER_CALL_GLOBAL(commit, Forward);
} else if (ioeventfd_update_pending) {
QTAILQ_FOREACH(as, &address_spaces, address_spaces_link) {
address_space_update_ioeventfds(as);
}
ioeventfd_update_pending = false;
}
}
}
#define MEMORY_LISTENER_CALL_GLOBAL(_callback, _direction, _args...) \
do { \
MemoryListener *_listener; \
\
switch (_direction) { \
case Forward: \
QTAILQ_FOREACH(_listener, &memory_listeners, link) { \
if (_listener->_callback) { \
_listener->_callback(_listener, ##_args); \
} \
} \
break; \
case Reverse: \
QTAILQ_FOREACH_REVERSE(_listener, &memory_listeners, \
memory_listeners, link) { \
if (_listener->_callback) { \
_listener->_callback(_listener, ##_args); \
} \
} \
break; \
default: \
abort(); \
} \
} while (0)
1.4 FlatView/FlatRange
FlatView管理MR展開后得到的所有FlatRange,ranges是一個數組,記錄FlatView下所有的FlatRange,每個FlatRange對應一段虛擬機物理地址區間,各個FlatRange不會重疊,按照地址的順序保存在數組中。具體的范圍由一個AddrRange結構描述,其描述了地址和大小。當memory region發生變化的時候,執行memory_region_transaction_commit,address_space_update_topology,address_space_update_topology_pass最終完成更新FlatView的目標。
將內存平坦完成的函數是:generate_memory_topology
/* Flattened global view of current active memory hierarchy. Kept in sorted
* order.
*/
struct FlatView {
struct rcu_head rcu;
unsigned ref; //引用計數,為0就銷毀
FlatRange *ranges; //對應的flatrange數組
unsigned nr; //flatrange數目
unsigned nr_allocated;
struct AddressSpaceDispatch *dispatch;
MemoryRegion *root;
};
/* Range of memory in the global map. Addresses are absolute. */
struct FlatRange {
MemoryRegion *mr; //指向所屬的MR
hwaddr offset_in_region; //在MR中的offset
AddrRange addr; //本FR代表的區間
uint8_t dirty_log_mask;
bool romd_mode;
bool readonly;
};
FlatView結構如下:
由圖片可知每個FlatRange的中的AddrRange的start為該段內存區間GPA的首地址,size則描述了該段區間的大小。
1.5 MemoryRegionSection
MemoryRegionSection 指向 MemoryRegion 的一部分 ([offset_within_region, offset_within_region + size]),是注冊到 KVM 的基本單位。
將 AddressSpace 中的 MemoryRegion 映射到線性地址空間后,由於重疊的關系,原本完整的 region 可能會被切分成片段,於是產生了 MemoryRegionSection。
其中偏移offset_within_region描述的是該section在其所屬的MR中的偏移,一個address_space可能有多個MR構成,因此該offset是局部的。而offset_within_address_space是在整個地址空間中的偏移,是全局的offset,如果AddressSpace為系統內存,則該偏移則為GPA的起始地址。
/**
* MemoryRegionSection: describes a fragment of a #MemoryRegion
*
* @mr: the region, or %NULL if empty
* @address_space: the address space the region is mapped in
* @offset_within_region: the beginning of the section, relative to @mr's start
* @size: the size of the section; will not exceed @mr's boundaries
* @offset_within_address_space: the address of the first byte of the section
* relative to the region's address space
* @readonly: writes to this section are ignored
*/
struct MemoryRegionSection {
MemoryRegion *mr; // 指向所屬 MemoryRegion
AddressSpace *address_space; // 所屬 AddressSpace
hwaddr offset_within_region; // 起始地址 (HVA) 在 MemoryRegion 內的偏移量
Int128 size;
hwaddr offset_within_address_space; // 在 AddressSpace 內的偏移量,如果該 AddressSpace 為系統內存,則為 GPA 起始地址
bool readonly;
};
參考:
https://blog.csdn.net/huang987246510/article/details/104012839
https://www.cnblogs.com/ccxikka/p/9477530.html
https://blog.csdn.net/GerryLee93/article/details/106477323