mikanOS 3章を整理 [前編]


はじめに

今回はカーネルを call してみます
基本的には、mikanOS の sample コードを読み解き、
コードから具体的なフローを学んでいくスタンスでまとめていきます。

⇦前|mikanOS 2章を整理 || mikanOS 3章を整理[後編]|⇨

Kernel を呼び出すフロー

まずは Kernel.elf を取り込んで、
ソフト的に色々できるようにします。

色々と言っても複雑なことではなく、
ファイルサイズを確認したり、kernel を WorkMemory に配置して
アプリケーションとして Call 出来る準備をしたりします。

Kernel の取り込み

冒頭にあるように Kernel.elf を一旦取り込む必要があります。
取り込むために必要な Open() について確認してみましょう。

Open() の概要:
以下 "*This" を基準に "FileName" の "file" もしくは "directory" を検索して開く

Main.c
/*take in kernel.elf*/
  EFI_FILE_PROTOCOL* kernel_file;
  status = root_dir->Open(
      root_dir,           //*This 
      &kernel_file,       //**NewHandle
      L"\\kernel.elf",    //*FileName
      EFI_FILE_MODE_READ, //OpenMode
      0);                 //Attributes

/*
"*This" を基準に "FileName" の "file" もしくは "directory" を開く
                     || <=イコール 
  "root_dir" から "kernel.elf" を read mode で開く
*/

Kernel のサイズ確認

application として kernel を call するのであれば、
横やりが入らないように workmemory の一部を kernel 用に確保してあげる必要があります。

そのためにもファイルサイズ情報が必須です。
アプローチとしては GetInfo() を使うのですが、
まずは仕様確認。

欲しいのは Kernel ファイルの情報なので、
EFI_FILE_INFO かな。。って事で仕様をチラ見する。

GetInfo で output するデータは Void * なので
データの中身自体は Void 型となります。
しかし、想定しているデータは EFI_FILE_INFO :構造体です。

よってデータ取得後、
Void* を EFI_FILE_INFO* とするようなキャストが必要になります。

check_kernel_size.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    UINTN BufferSize = sizeof(EFI_FILE_INFO) + sizeof(CHAR16)*12;
    UINT8 void_kernel_info[BufferSize];
    status = kernel_file->GetInfo(
             kernel_file, &gEfiFileInfoGuid,
             &BufferSize,void_kernel_info);
    if (EFI_ERROR(status)) {Print(L"Fail to get kernel info\n");}
    EFI_FILE_INFO* kernel_info = (EFI_FILE_INFO*)void_kernel_info;

    Print(L"All done\n");
    while (1);
    return EFI_SUCCESS;
}

メモリ領域の確保

カーネルサイズを確認したら、
work memory に配置する領域を確保します。

EFI_ALLOCATE_TYPE の説明についてはOS自作入門を買って確認しましょう。
*UEFI specification に説明ありますけど。。。

keep_workmemory.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {

    //alocate kernel on work memory
    EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
    gBS->AllocatePages(
        AllocateAddress, EfiLoaderData,
        (kernel_info.FileSize + 0xfff) / 0x1000, &kernel_base_addr);

    //↓ 以下の記述が無くても動作はする。
    //kernel_file->Read(kernel_file, &(kernel_info.FileSize), (VOID*)kernel_base_addr);

    Print(L"All done\n");
    while (1);
    return EFI_SUCCESS;
}

UEFI Boot service 終了

kernel を呼び出す前に Boot Service を終了させる必要があります。
ココ に参考情報があります。boot service から runtime srvice の
移行するためのお約束として Exit boot service するようです。

当初に get memory map を call したときに map key は取得しているのですが、
その後の操作で map key は変わっている場合がありますので、exit する際には
再度 get memory map を call して map key の更新が必要です。

exit_boot_service.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {    
    //Exit boot services
    MemMap_Make(&MemMap,4096*4);
    status = gBS->ExitBootServices(image_handle, MemMap.map_key);
    if (EFI_ERROR(status)) {
      Print(L"Fail to ExitBootServices\n");}
    }

Kernel の起動

Point 1 : entry point 明確化
base address から 24バイトのオフセットに
Entry point のアドレスが格納されています(らしい)。
そのため (base address + 24)をポインタキャストして * とすれば
Entry point のアドレスになると思います。

Point 2: kernel を call するために関数のプロトタイプ宣言が必要
呼び出す kernel は結局、関数なので
引数と戻り値を定義するプロトタイプ宣言が
必要という意義は納得です。

その他: 作法としてストックしておけば、とりあえず今は良いかも

boot_kernel.c
    //set entry point and call kernel
    UINT64 entry_addr = *(UINT64*)(kernel_base_addr + 24);    /* <= Point 1*/
    typedef void EntryPointType(void);                        /* <= Point 2*/
    EntryPointType* entry_point = (EntryPointType*)entry_addr;
    entry_point();