19
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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;

注意: DescriptorSizesizeof(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 が古いと失敗します。そのため、取得直後に呼ぶか、失敗したら再取得してリトライするパターンが一般的です。

メモリマップの活用

カーネルに渡すべき情報:

  1. 使用可能なメモリ領域のリスト
  2. フレームバッファの情報(GOP経由で取得)
  3. ACPI テーブルのアドレス(System Table経由)
// カーネルに渡す構造体の例
typedef struct {
    UINT64 MemoryMapAddr;
    UINT64 MemoryMapSize;
    UINT64 DescriptorSize;
    UINT64 FrameBufferBase;
    UINT64 FrameBufferSize;
    UINT32 HorizontalResolution;
    UINT32 VerticalResolution;
    UINT64 AcpiTableAddr;
} BootInfo;

まとめ

今回学んだこと:

  1. GetMemoryMap の2段階呼び出し
  2. EFI_MEMORY_TYPE - どの領域が使えるか
  3. EFI_MEMORY_DESCRIPTOR の構造
  4. MapKey の重要性(ExitBootServicesに必要)
  5. DescriptorSize を使ったエントリの列挙

次回はいよいよ最終回!カーネルをロードして ExitBootServices() を呼び、OSに制御を渡します。

参考資料

19
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?