1. 基礎資料結構
1.1 LIST_ENTRY 雙向鏈結串列
LIST_ENTRY 是 EDK2 中最基礎的資料結構,用於實現雙向鏈結串列。
它被嵌入到其他結構體中,用於將多個結構體串連起來。
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *ForwardLink; // 指向下一個節點
struct _LIST_ENTRY *BackLink; // 指向前一個節點
} LIST_ENTRY;
LIST_ENTRY 結構圖解
1.2 MEMORY_MAP_ENTRY 記憶體描述結構
MEMORY_MAP_ENTRY 描述一塊連續的記憶體區域,包含其類型、起始位址、頁數等資訊。
多個 MEMORY_MAP_ENTRY 透過 LIST_ENTRY 串連成記憶體映射表。
typedef struct {
UINT32 Signature; // 簽名: 'mmap'
LIST_ENTRY Link; // 串連用的雙向鏈結
EFI_MEMORY_TYPE Type; // 記憶體類型
EFI_PHYSICAL_ADDRESS Start; // 起始物理位址
EFI_PHYSICAL_ADDRESS End; // 結束物理位址
UINT64 Attribute; // 記憶體屬性
} MEMORY_MAP_ENTRY;
MEMORY_MAP_ENTRY 結構與串連
1.3 EFI_MEMORY_TYPE 記憶體類型
EfiReservedMemoryType
保留記憶體,不可使用
EfiLoaderCode
OS Loader 程式碼
EfiLoaderData
OS Loader 資料
EfiBootServicesCode
Boot Services 程式碼
EfiBootServicesData
Boot Services 資料
EfiRuntimeServicesCode
Runtime Services 程式碼
EfiRuntimeServicesData
Runtime Services 資料
EfiConventionalMemory
可用的空閒記憶體
EfiACPIReclaimMemory
ACPI 可回收記憶體
EfiACPIMemoryNVS
ACPI NVS 記憶體
EfiMemoryMappedIO
記憶體映射 I/O
EfiPersistentMemory
持久性記憶體
2. 記憶體初始化配置圖
2.1 系統記憶體整體佈局
在 UEFI 系統啟動過程中,記憶體會被劃分為不同的區域。以下是一個典型的記憶體佈局範例:
2.2 記憶體映射表初始狀態
系統啟動後,記憶體管理器會建立一個 Memory Map 鏈結串列來追蹤所有記憶體區域的狀態:
3. 記憶體配置流程
3.1 AllocatePages 配置流程
當呼叫 gBS->AllocatePages() 時,記憶體管理器會執行以下步驟來找到適合的記憶體區塊:
步驟 1:接收配置請求
應用程式呼叫 AllocatePages(AllocateAnyPages, EfiBootServicesData, 4, &Address)
請求配置 4 頁 (16KB) 的 Boot Services Data 記憶體
// 配置請求
EFI_STATUS Status;
EFI_PHYSICAL_ADDRESS Address;
Status = gBS->AllocatePages (
AllocateAnyPages, // 配置類型:任意位址
EfiBootServicesData, // 記憶體類型
4, // 頁數 (4 * 4KB = 16KB)
&Address // 輸出:配置的位址
);
3.2 配置演算法詳解
First-Fit 演算法
- 從 gMemoryMap 鏈結串列的頭部開始遍歷
- 對每個 MEMORY_MAP_ENTRY 檢查:
- 類型是否為 EfiConventionalMemory(可用記憶體)
- 大小是否足夠容納請求的頁數
- 位址是否符合對齊要求
- 找到第一個符合條件的區塊後停止搜尋
- 分割該區塊,更新記憶體映射表
4. 記憶體釋放流程
4.1 FreePages 釋放流程
當呼叫 gBS->FreePages() 時,記憶體管理器會將記憶體歸還並嘗試合併相鄰的空閒區塊:
步驟 1:接收釋放請求
應用程式呼叫 FreePages(0x00200000, 4)
請求釋放位址 0x00200000 的 4 頁 記憶體
// 釋放請求
EFI_STATUS Status;
Status = gBS->FreePages (
0x00200000, // 要釋放的記憶體位址
4 // 頁數 (4 * 4KB = 16KB)
);
4.2 記憶體合併 (Coalescing)
釋放記憶體後,管理器會檢查相鄰區塊是否也是空閒的,如果是則合併它們以減少碎片化:
合併前
8 頁
4 頁
12 頁
合併後
24 頁
5. Pool 記憶體配置
5.1 AllocatePool 配置流程
AllocatePool 用於配置小型記憶體區塊。它在已配置的頁面中管理 Pool,使用 POOL_HEAD 結構追蹤每個配置。
步驟 1:接收 Pool 配置請求
應用程式呼叫 AllocatePool(EfiBootServicesData, 128, &Buffer)
5.2 Pool 結構詳解
typedef struct {
UINT32 Signature; // 'phd0'
UINT32 Reserved; // Padding
EFI_MEMORY_TYPE Type; // Memory type
UINTN Size; // Allocation size (including header)
CHAR8 Data[1]; // Start of user data
} POOL_HEAD;
6. 啟動階段記憶體時間線
6.1 記憶體映射演進
隨著系統啟動,記憶體映射會經歷不同的階段變化。點擊各階段查看該階段的記憶體狀態:
記憶體映射快照
7. S4 復甦記憶體映射
7.1 S4 復甦階段記憶體映射
點擊各階段,查看系統從休眠喚醒到重新交還給 OS 的記憶體狀態與映射變化。
S4 復甦記憶體映射快照
8. 配置策略比較
8.1 配置演算法比較
對相同的記憶體佈局和配置請求,不同的演算法會選擇不同的區塊。選擇演算法查看差異:
9. 記憶體碎片化視覺化
9.1 碎片化熱力圖
互動式模擬記憶體配置與釋放,觀察碎片化程度的變化:
10. ExitBootServices 記憶體交接
10.1 啟動服務到 Runtime 交接
當 OS Loader 呼叫 ExitBootServices() 後,Boot Services 記憶體被回收,只有 Runtime 和 ACPI 區域保留: