3
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?

BIOSは死んだ、UEFIの時代だ Part5(最終回)

Last updated at Posted at 2025-12-10

UEFI完全入門シリーズ
Part1【環境構築】 | Part2【GOP】 | Part3【ファイル読み込み】| Part4【メモリマップ】| Part5【カーネルロード】

はじめに

ついに最終回!

これまで学んだことを総動員して、 カーネルをロードして実行 します。

  • Part1: 環境構築、Hello World
  • Part2: GOP(グラフィックス)
  • Part3: ファイル読み込み
  • Part4: メモリマップ

今回はこれらを組み合わせて、UEFIから自作カーネルに制御を渡します。

全体の流れ

1. GOPを取得(フレームバッファ情報)
2. カーネルファイルを開く
3. メモリを確保してカーネルをロード
4. ブート情報を準備
5. メモリマップを取得
6. ExitBootServices()
7. カーネルにジャンプ!

簡易カーネル

まずは超シンプルなカーネルを作ります。フレームバッファに直接描画するだけ。

kernel.c

typedef unsigned long long uint64_t;
typedef unsigned int uint32_t;

// ブートローダーから渡される情報
typedef struct {
    uint64_t framebuffer_base;
    uint32_t horizontal_resolution;
    uint32_t vertical_resolution;
    uint32_t pixels_per_scan_line;
} BootInfo;

void kernel_main(BootInfo *boot_info) {
    // フレームバッファを取得
    uint32_t *fb = (uint32_t *)boot_info->framebuffer_base;
    uint32_t width = boot_info->horizontal_resolution;
    uint32_t height = boot_info->vertical_resolution;
    uint32_t pitch = boot_info->pixels_per_scan_line;
    
    // 画面を青で塗りつぶす
    for (uint32_t y = 0; y < height; y++) {
        for (uint32_t x = 0; x < width; x++) {
            fb[y * pitch + x] = 0x000080FF;  // BGR形式の青
        }
    }
    
    // 中央に白い四角を描画
    uint32_t rect_w = 200;
    uint32_t rect_h = 100;
    uint32_t rect_x = (width - rect_w) / 2;
    uint32_t rect_y = (height - rect_h) / 2;
    
    for (uint32_t y = rect_y; y < rect_y + rect_h; y++) {
        for (uint32_t x = rect_x; x < rect_x + rect_w; x++) {
            fb[y * pitch + x] = 0x00FFFFFF;  // 白
        }
    }
    
    // 停止
    while (1) {
        __asm__ volatile("hlt");
    }
}

kernel.ld(リンカスクリプト)

ENTRY(kernel_main)
SECTIONS
{
    . = 0x100000;
    .text : { *(.text) }
    .rodata : { *(.rodata) }
    .data : { *(.data) }
    .bss : { *(.bss) }
}

ビルド

$ gcc -ffreestanding -nostdlib -mno-red-zone -c kernel.c -o kernel.o
$ ld -nostdlib -T kernel.ld -o kernel.elf kernel.o
$ objcopy -O binary kernel.elf kernel.bin
$ ls -la kernel.bin
-rwxr-xr-x 1 root root 352 Dec 10 14:55 kernel.bin

わずか352バイトの超軽量カーネル!

ブートローダーの実装

コード全体

#include <efi.h>
#include <efilib.h>

// GUIDs
EFI_GUID LoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID SimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_GUID FileInfoGuid = EFI_FILE_INFO_ID;
EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

// カーネルに渡す情報
typedef struct {
    UINT64 framebuffer_base;
    UINT32 horizontal_resolution;
    UINT32 vertical_resolution;
    UINT32 pixels_per_scan_line;
} BootInfo;

// カーネルのエントリポイント型
typedef void (*KernelEntry)(BootInfo *);

#define KERNEL_LOAD_ADDRESS 0x100000

EFI_STATUS EFIAPI
efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
    EFI_STATUS Status;
    EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
    EFI_FILE_PROTOCOL *Root, *KernelFile;
    EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
    
    InitializeLib(ImageHandle, SystemTable);
    ST->ConOut->ClearScreen(ST->ConOut);
    
    Print(L"=== UEFI Bootloader ===\n\n");
    
    // 1. GOPを取得
    Status = uefi_call_wrapper(
        BS->LocateProtocol, 3,
        &GraphicsOutputProtocolGuid, NULL, (VOID **)&Gop
    );
    Print(L"GOP: %dx%d, FB=0x%lx\n", 
          Gop->Mode->Info->HorizontalResolution,
          Gop->Mode->Info->VerticalResolution,
          Gop->Mode->FrameBufferBase);
    
    // 2. ファイルシステムを取得
    uefi_call_wrapper(BS->HandleProtocol, 3,
        ImageHandle, &LoadedImageProtocolGuid, (VOID **)&LoadedImage);
    uefi_call_wrapper(BS->HandleProtocol, 3,
        LoadedImage->DeviceHandle, &SimpleFileSystemProtocolGuid, (VOID **)&FileSystem);
    uefi_call_wrapper(FileSystem->OpenVolume, 2, FileSystem, &Root);
    
    // 3. カーネルファイルを開く
    Status = uefi_call_wrapper(
        Root->Open, 5, Root, &KernelFile, L"kernel.bin", EFI_FILE_MODE_READ, 0
    );
    Print(L"kernel.bin opened\n");
    
    // 4. ファイルサイズを取得
    UINT8 InfoBuffer[256];
    UINTN InfoSize = sizeof(InfoBuffer);
    uefi_call_wrapper(KernelFile->GetInfo, 4, KernelFile, &FileInfoGuid, &InfoSize, InfoBuffer);
    EFI_FILE_INFO *FileInfo = (EFI_FILE_INFO *)InfoBuffer;
    UINTN KernelSize = FileInfo->FileSize;
    Print(L"Kernel size: %d bytes\n", KernelSize);
    
    // 5. メモリを確保
    UINTN Pages = (KernelSize + 4095) / 4096;
    EFI_PHYSICAL_ADDRESS KernelAddress = KERNEL_LOAD_ADDRESS;
    uefi_call_wrapper(BS->AllocatePages, 4,
        AllocateAddress, EfiLoaderData, Pages, &KernelAddress);
    Print(L"Memory allocated at 0x%lx\n", KernelAddress);
    
    // 6. カーネルを読み込み
    UINTN ReadSize = KernelSize;
    uefi_call_wrapper(KernelFile->Read, 3, KernelFile, &ReadSize, (VOID *)KernelAddress);
    Print(L"Kernel loaded!\n\n");
    
    uefi_call_wrapper(KernelFile->Close, 1, KernelFile);
    uefi_call_wrapper(Root->Close, 1, Root);
    
    // 7. ブート情報を準備
    BootInfo *boot_info;
    uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, sizeof(BootInfo), (VOID **)&boot_info);
    boot_info->framebuffer_base = Gop->Mode->FrameBufferBase;
    boot_info->horizontal_resolution = Gop->Mode->Info->HorizontalResolution;
    boot_info->vertical_resolution = Gop->Mode->Info->VerticalResolution;
    boot_info->pixels_per_scan_line = Gop->Mode->Info->PixelsPerScanLine;
    
    Print(L"Press any key to boot kernel...\n");
    UINTN Index;
    uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &Index);
    
    // 8. ExitBootServices
    UINTN MemoryMapSize = 0;
    EFI_MEMORY_DESCRIPTOR *MemoryMap = NULL;
    UINTN MapKey, DescriptorSize;
    UINT32 DescriptorVersion;
    
    uefi_call_wrapper(BS->GetMemoryMap, 5,
        &MemoryMapSize, NULL, &MapKey, &DescriptorSize, &DescriptorVersion);
    MemoryMapSize += 2 * DescriptorSize;
    uefi_call_wrapper(BS->AllocatePool, 3, EfiLoaderData, MemoryMapSize, (VOID **)&MemoryMap);
    uefi_call_wrapper(BS->GetMemoryMap, 5,
        &MemoryMapSize, MemoryMap, &MapKey, &DescriptorSize, &DescriptorVersion);
    
    Status = uefi_call_wrapper(BS->ExitBootServices, 2, ImageHandle, MapKey);
    
    // 9. カーネルにジャンプ!
    KernelEntry kernel_entry = (KernelEntry)KernelAddress;
    kernel_entry(boot_info);
    
    while (1) { __asm__ volatile("hlt"); }
    return EFI_SUCCESS;
}

実行結果

=== UEFI Bootloader - Kernel Loader ===

GOP: 1024x768, FB=0x80000000
FileSystem opened
kernel.bin opened
Kernel size: 352 bytes
Memory allocated at 0x100000 (1 pages)
Kernel loaded at 0x100000

BootInfo prepared:
  FB: 0x80000000
  Resolution: 1024x768

Press any key to jump to kernel...

キーを押すとExitBootServicesが呼ばれ、カーネルにジャンプします。

カーネルは画面を青く塗りつぶし、中央に白い四角形を描画します。

重要なポイント解説

AllocatePages vs AllocatePool

カーネルをロードするには AllocatePages を使います:

EFI_PHYSICAL_ADDRESS KernelAddress = KERNEL_LOAD_ADDRESS;
Status = uefi_call_wrapper(
    BS->AllocatePages, 4,
    AllocateAddress,     // 指定アドレスに確保
    EfiLoaderData,       // メモリタイプ
    Pages,               // ページ数
    &KernelAddress       // [in/out] アドレス
);

AllocateAddress を指定することで、特定のアドレス(0x100000)にカーネルをロードできます。

ExitBootServices

Status = uefi_call_wrapper(BS->ExitBootServices, 2, ImageHandle, MapKey);

これを呼ぶと:

  • Boot Services が使えなくなる
  • Print() などのUEFI関数が使えなくなる
  • EfiBootServicesCode/Data のメモリが解放される
  • Runtime Services は引き続き使える

注意: MapKey が古いと失敗します。失敗したら再取得してリトライ:

Status = BS->ExitBootServices(ImageHandle, MapKey);
if (EFI_ERROR(Status)) {
    // メモリマップを再取得
    GetMemoryMap(&MemoryMapSize, MemoryMap, &MapKey, ...);
    // リトライ
    BS->ExitBootServices(ImageHandle, MapKey);
}

カーネルへのジャンプ

typedef void (*KernelEntry)(BootInfo *);
KernelEntry kernel_entry = (KernelEntry)KernelAddress;
kernel_entry(boot_info);

C言語の関数ポインタでカーネルのエントリポイントを呼び出します。

ディレクトリ構造

uefi-bootloader/
├── Makefile
├── src/
│   └── main.c          (ブートローダー)
├── kernel/
│   ├── kernel.c
│   ├── kernel.ld
│   └── kernel.bin      (ビルド成果物)
└── esp/
    ├── EFI/
    │   └── BOOT/
    │       └── BOOTX64.EFI
    └── kernel.bin

QEMUで実行

$ qemu-system-x86_64 \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -drive format=raw,file=fat:rw:esp \
    -m 256

GUIモードで実行すると、カーネルが描画した青い画面と白い四角形が見えるはずです!

この先の展望

ここまでの知識で、以下のことができるようになりました:

  • ✅ UEFIアプリケーションの作成
  • ✅ グラフィックス出力(GOP)
  • ✅ ファイル読み込み
  • ✅ メモリマップ取得
  • ✅ カーネルのロードと実行

次のステップ:

  • ページング設定
  • GDT/IDTの設定
  • 割り込みハンドラ
  • キーボード入力
  • メモリアロケータ

これらを実装していけば、本格的なOSに近づいていきます。

まとめ

5回にわたるUEFIシリーズ、いかがでしたか?

「BIOSは死んだ、UEFIの時代だ」

...と言いましたが、実際にはUEFIの理解はこれからの自作OS開発で必須の知識です。

GRUBなどのブートローダーに頼らず、ブートの仕組みから理解することで、OSの動作原理がより深く理解できるようになります。

ぜひ自分でも動かしてみてください!

参考資料


UEFI完全入門シリーズ 完

次はRISC-V自作CPUシリーズもよろしく!

3
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
3
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?