UEFI完全入門シリーズ
Part1【環境構築】 | Part2【GOP】 | Part3【ファイル読み込み】| Part4【メモリマップ】| Part5【カーネルロード】
はじめに
前回はSimple File System Protocolでファイルを読み書きしました。
今回は メモリマップ を取得します。
OSに制御を移す前に、「どのメモリ領域が使えるか」を正確に把握する必要があります。UEFIが使っている領域を上書きしたら即クラッシュですからね。
メモリマップとは
UEFIの GetMemoryMap() は、システムの物理メモリの配置を教えてくれます。
各領域には「タイプ」があり、使用中か空きかがわかります。
メモリタイプ
typedef enum {
EfiReservedMemoryType, // 0: 使用禁止
EfiLoaderCode, // 1: ブートローダーのコード
EfiLoaderData, // 2: ブートローダーのデータ
EfiBootServicesCode, // 3: BootServicesのコード
EfiBootServicesData, // 4: BootServicesのデータ
EfiRuntimeServicesCode, // 5: RuntimeServicesのコード
EfiRuntimeServicesData, // 6: RuntimeServicesのデータ
EfiConventionalMemory, // 7: 空きメモリ(使用可能!)
EfiUnusableMemory, // 8: エラーのあるメモリ
EfiACPIReclaimMemory, // 9: ACPI再利用可
EfiACPIMemoryNVS, // 10: ACPI NVS
EfiMemoryMappedIO, // 11: MMIO
EfiMemoryMappedIOPortSpace, // 12: MMIO ポート空間
EfiPalCode, // 13: PALコード
EfiPersistentMemory, // 14: 永続メモリ
EfiMaxMemoryType
} EFI_MEMORY_TYPE;
OSが自由に使えるのは EfiConventionalMemory だけ!
ExitBootServices() を呼んだ後は、EfiBootServicesCode/Data も使えるようになります。
GetMemoryMap の呼び方
この関数は少しトリッキーです。
2段階の呼び出し
UINTN MemoryMapSize = 0;
EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL;
UINTN MapKey;
UINTN DescriptorSize;
UINT32 DescriptorVersion;
// 1回目: 必要なバッファサイズを取得
Status = uefi_call_wrapper(
BS->GetMemoryMap, 5,
&MemoryMapSize, // [in/out] バッファサイズ
MemoryMap, // [out] バッファ(NULL可)
&MapKey, // [out] マップキー
&DescriptorSize, // [out] 各エントリのサイズ
&DescriptorVersion // [out] バージョン
);
// → EFI_BUFFER_TOO_SMALL が返る
// バッファを確保(余裕を持たせる)
MemoryMapSize += 2 * DescriptorSize;
BS->AllocatePool(EfiLoaderData, MemoryMapSize, (VOID **)&MemoryMap);
// 2回目: 実際にメモリマップを取得
Status = uefi_call_wrapper(
BS->GetMemoryMap, 5,
&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion
);
なぜ余裕を持たせるのか?
AllocatePool を呼ぶとメモリマップが変わる可能性があるため、少し大きめに確保しておきます。
実行結果
=== Memory Map Demo ===
Memory map requires 6672 bytes
Descriptor size: 48 bytes
Descriptor version: 1
Memory Map (showing first 20 entries):
Type PhysicalStart Pages Attr
----------------------------------------------------------------------
EfiBootServicesCode 0000000000000000 1 0000000F
EfiConventionalMemory 0000000000001000 159 0000000F
EfiConventionalMemory 0000000000100000 1792 0000000F
EfiACPIMemoryNVS 0000000000800000 8 0000000F
EfiConventionalMemory 0000000000808000 3 0000000F
EfiACPIMemoryNVS 000000000080B000 1 0000000F
EfiConventionalMemory 000000000080C000 4 0000000F
EfiACPIMemoryNVS 0000000000810000 240 0000000F
EfiBootServicesData 0000000000900000 3072 0000000F
EfiConventionalMemory 0000000001500000 43574 0000000F
EfiBootServicesData 000000000BF36000 32 0000000F
EfiConventionalMemory 000000000BF56000 8705 0000000F
EfiLoaderCode 000000000E157000 17 0000000F
EfiConventionalMemory 000000000E168000 13 0000000F
EfiBootServicesData 000000000E175000 596 0000000F
EfiConventionalMemory 000000000E3C9000 1 0000000F
EfiLoaderData 000000000E3CA000 2 0000000F
EfiBootServicesData 000000000E3CC000 6 0000000F
EfiConventionalMemory 000000000E3D2000 1 0000000F
EfiBootServicesData 000000000E3D3000 1202 0000000F
----------------------------------------------------------------------
Total entries: 133
Total memory: 255 MB
Usable memory (EfiConventionalMemory): 212 MB
読み方
- PhysicalStart: 領域の開始物理アドレス
- Pages: ページ数(1ページ = 4KB)
- Attr: メモリ属性(キャッシュ可能性など)
例えば:
EfiConventionalMemory 0000000001500000 43574 0000000F
これは 0x01500000 から始まる 43574ページ(約170MB)の空き領域があることを示しています。
EFI_MEMORY_DESCRIPTOR構造体
typedef struct {
UINT32 Type; // メモリタイプ
UINT32 Pad; // パディング
EFI_PHYSICAL_ADDRESS PhysicalStart; // 開始アドレス
EFI_VIRTUAL_ADDRESS VirtualStart; // 仮想アドレス
UINT64 NumberOfPages; // ページ数
UINT64 Attribute; // 属性
} EFI_MEMORY_DESCRIPTOR;
注意: DescriptorSize は sizeof(EFI_MEMORY_DESCRIPTOR) と一致しない可能性があります!
必ず DescriptorSize を使ってエントリを列挙してください:
EFI_MEMORY_DESCRIPTOR *Desc = MemoryMap;
for (UINTN i = 0; i < EntryCount; i++) {
// Descを処理...
// 次のエントリへ(sizeof(Desc)ではなくDescriptorSizeを使う!)
Desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)Desc + DescriptorSize);
}
メモリ属性
Attribute フィールドはビットフラグです:
| ビット | 名前 | 説明 |
|---|---|---|
| 0 | UC | アンキャッシュ |
| 1 | WC | ライトコンバイン |
| 2 | WT | ライトスルー |
| 3 | WB | ライトバック |
| 12 | WP | 書き込み保護 |
| 15 | RP | 読み取り保護 |
| 62 | NV | 不揮発性 |
| 63 | RUNTIME | ランタイムで使用 |
実行結果の 0000000F は UC | WC | WT | WB がセットされていることを示します。
完全なサンプルコード
#include <efi.h>
#include <efilib.h>
const CHAR16 *MemoryTypeStrings[] = {
L"EfiReservedMemoryType",
L"EfiLoaderCode",
L"EfiLoaderData",
L"EfiBootServicesCode",
L"EfiBootServicesData",
L"EfiRuntimeServicesCode",
L"EfiRuntimeServicesData",
L"EfiConventionalMemory",
L"EfiUnusableMemory",
L"EfiACPIReclaimMemory",
L"EfiACPIMemoryNVS",
L"EfiMemoryMappedIO",
L"EfiMemoryMappedIOPortSpace",
L"EfiPalCode",
L"EfiPersistentMemory",
L"EfiMaxMemoryType"
};
EFI_STATUS
EFIAPI
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
EFI_STATUS Status;
UINTN MemoryMapSize = 0;
EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL;
UINTN MapKey;
UINTN DescriptorSize;
UINT32 DescriptorVersion;
InitializeLib(ImageHandle, SystemTable);
ST->ConOut->ClearScreen(ST->ConOut);
Print(L"=== Memory Map Demo ===\n\n");
// サイズを取得
Status = uefi_call_wrapper(
BS->GetMemoryMap, 5,
&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion
);
Print(L"Memory map requires %d bytes\n", MemoryMapSize);
Print(L"Descriptor size: %d bytes\n\n", DescriptorSize);
// バッファ確保
MemoryMapSize += 2 * DescriptorSize;
Status = uefi_call_wrapper(
BS->AllocatePool, 3, EfiLoaderData, MemoryMapSize, (VOID **)&MemoryMap
);
// メモリマップ取得
Status = uefi_call_wrapper(
BS->GetMemoryMap, 5,
&MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion
);
// 集計と表示
UINTN EntryCount = MemoryMapSize / DescriptorSize;
UINT64 TotalMemory = 0;
UINT64 UsableMemory = 0;
EFI_MEMORY_DESCRIPTOR *Desc = MemoryMap;
for (UINTN i = 0; i < EntryCount; i++) {
UINT64 Size = Desc->NumberOfPages * 4096;
TotalMemory += Size;
if (Desc->Type == EfiConventionalMemory) {
UsableMemory += Size;
}
Desc = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)Desc + DescriptorSize);
}
Print(L"Total entries: %d\n", EntryCount);
Print(L"Total memory: %d MB\n", TotalMemory / (1024 * 1024));
Print(L"Usable memory: %d MB\n", UsableMemory / (1024 * 1024));
uefi_call_wrapper(BS->FreePool, 1, MemoryMap);
UINTN Index;
uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &Index);
return EFI_SUCCESS;
}
MapKey の重要性
GetMemoryMap() が返す MapKey は、メモリマップの「バージョン番号」のようなものです。
ExitBootServices() を呼ぶときに必要です!
// メモリマップ取得
Status = GetMemoryMap(&MemoryMapSize, MemoryMap, &MapKey, ...);
// ExitBootServicesにはMapKeyが必要
Status = BS->ExitBootServices(ImageHandle, MapKey);
ExitBootServices() 呼び出し時に MapKey が古いと失敗します。そのため、取得直後に呼ぶか、失敗したら再取得してリトライするパターンが一般的です。
メモリマップの活用
カーネルに渡すべき情報:
- 使用可能なメモリ領域のリスト
- フレームバッファの情報(GOP経由で取得)
- ACPI テーブルのアドレス(System Table経由)
// カーネルに渡す構造体の例
typedef struct {
UINT64 MemoryMapAddr;
UINT64 MemoryMapSize;
UINT64 DescriptorSize;
UINT64 FrameBufferBase;
UINT64 FrameBufferSize;
UINT32 HorizontalResolution;
UINT32 VerticalResolution;
UINT64 AcpiTableAddr;
} BootInfo;
まとめ
今回学んだこと:
- GetMemoryMap の2段階呼び出し
- EFI_MEMORY_TYPE - どの領域が使えるか
- EFI_MEMORY_DESCRIPTOR の構造
- MapKey の重要性(ExitBootServicesに必要)
- DescriptorSize を使ったエントリの列挙
次回はいよいよ最終回!カーネルをロードして ExitBootServices() を呼び、OSに制御を渡します。