はじめに
今回はカーネルを call してみます
基本的には、mikanOS の sample コードを読み解き、
コードから具体的なフローを学んでいくスタンスでまとめていきます。
⇦前|mikanOS 2章を整理 || mikanOS 3章を整理[後編]|⇨
Kernel を呼び出すフロー
まずは Kernel.elf を取り込んで、
ソフト的に色々できるようにします。
色々と言っても複雑なことではなく、
ファイルサイズを確認したり、kernel を WorkMemory に配置して
アプリケーションとして Call 出来る準備をしたりします。
Kernel の取り込み
冒頭にあるように Kernel.elf を一旦取り込む必要があります。
取り込むために必要な Open() について確認してみましょう。
Open() の概要:
以下 "*This" を基準に "FileName" の "file" もしくは "directory" を検索して開く
/*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* とするようなキャストが必要になります。
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 に説明ありますけど。。。
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 の更新が必要です。
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 は結局、関数なので
引数と戻り値を定義するプロトタイプ宣言が
必要という意義は納得です。
◆その他: 作法としてストックしておけば、とりあえず今は良いかも
//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();