2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

mikanOS 2章を整理

Last updated at Posted at 2022-03-13

はじめに

いつも好き勝手に書き散らして申し訳ない。
過去に以下のような記事を書いてますが、
今一つ、身についた自覚がなく、悩んでました。

1つ目 mikanOS 動作確認に必要な操作をまとめてた。
2つ目 とにかく全てが分からないから気の向くままに調べた内容をメモ。

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

モヤモヤの原因は C 言語の理解不足

以下のサイトに出会い、世界が変わりました。

モノづくりC言語塾
ココでの説明も素晴らしいですが、併せて紹介されている
新・標準プログラマーズライブラリ C言語 ポインタ完全制覇
素晴らしかった(立ち読みだけど)

更に協力いただいた戦友も紹介します。

Cの絵本 第2版 C言語が好きになる新しい9つの扉
ほとんど絵での説明だったので分かりやすかった。
導入としては個人的に 100点

新・明解C言語 ポインタ完全攻略 (明解シリーズ)
mikanOS のコードを読んでいて、一番謎だった "*" の存在。
それを真っ先にクリアにしたかった。
パラ読みして、イメージを作りながら自力で書き直すと自分の理解度が測れて楽しい。

もし C 言語の初見さんが mikanOS に挑戦している場合は
上記の入門書籍を眺める所から始めた方が賢明です。

UEFI:構造体ベースのアクセスを理解

非難を覚悟で書きますが、UEFI で定義された IF の基本はタンスです。
例えば中身が空である 2段のタンスがあったとします。
1段目の引き出しに"リンゴ"と書いた紙を入れます。

数分後、タンスの2段目をあけてみると、あら不思議、
真っ赤で美味しそうなリンゴが出てくるではありませんか。

そんな事、ある??(笑)
そうです。実はタンスの裏側には、穴があって、
親切な誰かが願い事を書いた紙を見て、
こっそり2段目にリンゴをしまってくれただけなのです。

上記のような感じで OS ブートローダーとファーム(BIOS)は構造体(タンス)を介して
情報をやり取りしてシステムを運用しています。

では、具体的にどんな願い事をしているのでしょうか?
コードの流れは以下のイメージです(細かく言うとちょっと違うけど。。)。

フローチャート.png

メモリマップ作成 依頼

例えば Memory map を生成するために GetMemoryMap() なんてものが用意されています。
getmemorymap.png
上図の補足にサラッと概要を書きましたが
要は、メモリマップのファイルサイズだけを指定してください。
そのサイズでメモリマップを作りますよ。
ただし格納場所はwork memory に格納ですよって話です。

アクセス手法の一例として構造体を使うアイディアがあります。
パラメータの集合を用意し、GetMemoryMap() に各パラメータを宛がえば OK です。

GetMemoryMap_sample.c
//[注意]
//以下のコードで何してるか分からない人は、
//冒頭の参考書を読み直す事を推奨します.
//理解不足の原因は UEFI の仕様の理解不足ではなく、
//C 言語の理解、そのものが不足している事が原因である可能性が高いです。

struct MemoryMap {
  UINTN buffer_size;
  VOID* buffer;
  UINTN map_size;
  UINTN map_key;
  UINTN descriptor_size;
  UINT32 descriptor_version;
};

EFI_STATUS GetMemoryMap(struct MemoryMap* map) {
  map->map_size = map->buffer_size;
  return gBS->GetMemoryMap(
      &map->map_size,
      (EFI_MEMORY_DESCRIPTOR*)map->buffer,
      &map->map_key,
      &map->descriptor_size,
      &map->descriptor_version);
}

EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    //status checker when access to API
    EFI_STATUS status;
    Print(L"Hello, AKpirios World!\n");

    //Make a memory map on workmemory
    CHAR8 memmap_buf[4096 * 4];
    struct MemoryMap memmap = {sizeof(memmap_buf), memmap_buf, 0, 0, 0, 0};
    GetMemoryMap(&memmap);

    while (1);
    return EFI_SUCCESS;
}

Root Open 依頼

メモリマップは work memory に居るので
電源 OFF すると消えてしまう。

っであれば、ハードディスク(HD)にメモリマップを保存しましょう。
ハードディスクの領域はボリュームと言うらしいです。
UEFI には以下の仕様があります。

openvolume.png

また EFI_SIMPLE_FILE_SYSTEM_PROTOCOL は OpenProtocol からアクセスが出来るようになる。
以下、証拠(すべてのプロトコル(タンス)は OpenProtocol を通して探し出すことが出来る)。
図1.png

更に、EFI_SIMPLE_FILE_SYSTEM_PROTOCOL はファイルシステムをいじるので
デバイスハンドルなのかなっと考えてみる。

無題.png

Open_root_sample.c
// HD を open する所だけ切り抜いた
/*
[方針]
1.OpenProtocol で EFI_LOAD_IMAGE_PROTOCOL を Open
2.EFI_LOAD_IMAGE_PROTOCOL の DeviceHandle を Open
※DeviceHandle を Open する際は GUID で FileSystem を
 示す iD &gEfiSimpleFileSystemProtocolGuid を使う

*/
EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root) 
{
  EFI_STATUS status;
  EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;

  status = gBS->OpenProtocol(
      image_handle,
      &gEfiLoadedImageProtocolGuid,
      (VOID**)&loaded_image,
      image_handle,
      NULL,
      EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
  if (EFI_ERROR(status)) {
    return status;
  }

  status = gBS->OpenProtocol(
      loaded_image->DeviceHandle,
      &gEfiSimpleFileSystemProtocolGuid,
      (VOID**)&fs,
      image_handle,
      NULL,
      EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
  if (EFI_ERROR(status))
    return status;

return fs->OpenVolume(fs, root);

}

EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    //open root dir by using file system
    EFI_FILE_PROTOCOL* root_dir;
    status = OpenRootDir(image_handle, &root_dir);
    if (EFI_ERROR(status)) {Print(L"Fail to open rootdir\n");}

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

ファイル新規作成

HD を Open したので新規ファイルを作成してみます。
以下にある Open の力を借ります。
open.png
今さっき開いたばかりなのに、
何も開くものないでしょ? っと思った方、正解です。
open_補足.png

Open_sample.c
EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    
    //make new file on root dir
    EFI_FILE_PROTOCOL* memmap_file;
    status = root_dir->Open(
         /*This*/       root_dir, 
         /*NewHandle*/  &memmap_file, 
         /*FileName*/   L"\\memmap",
         /*OpenMode*/   EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0);
    if (EFI_ERROR(status)) {Print(L"Fail to make file\n");}

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

Memory Map ファイル保存

Workmemory に居る Memory Map を先ほど開いた新規ファイルに移します。
特に UEFI とか関係ないので取り合えずコピペで次に進みます(気が向いたら解説つけるかも)。

saveMemMap_sample.c

const CHAR16* GetMemoryTypeUnicode(EFI_MEMORY_TYPE type) {
  switch (type) {
    case EfiReservedMemoryType: return L"EfiReservedMemoryType";
    case EfiLoaderCode: return L"EfiLoaderCode";
    case EfiLoaderData: return L"EfiLoaderData";
    case EfiBootServicesCode: return L"EfiBootServicesCode";
    case EfiBootServicesData: return L"EfiBootServicesData";
    case EfiRuntimeServicesCode: return L"EfiRuntimeServicesCode";
    case EfiRuntimeServicesData: return L"EfiRuntimeServicesData";
    case EfiConventionalMemory: return L"EfiConventionalMemory";
    case EfiUnusableMemory: return L"EfiUnusableMemory";
    case EfiACPIReclaimMemory: return L"EfiACPIReclaimMemory";
    case EfiACPIMemoryNVS: return L"EfiACPIMemoryNVS";
    case EfiMemoryMappedIO: return L"EfiMemoryMappedIO";
    case EfiMemoryMappedIOPortSpace: return L"EfiMemoryMappedIOPortSpace";
    case EfiPalCode: return L"EfiPalCode";
    case EfiPersistentMemory: return L"EfiPersistentMemory";
    case EfiMaxMemoryType: return L"EfiMaxMemoryType";
    default: return L"InvalidMemoryType";
  }
}

EFI_STATUS SaveMemoryMap(struct MemoryMap* map, EFI_FILE_PROTOCOL* file) {
  EFI_STATUS status;
  CHAR8 buf[256];
  UINTN len;

  CHAR8* header =
    "Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\n";
  len = AsciiStrLen(header);
  status = file->Write(file, &len, header);
  if (EFI_ERROR(status)) {
    return status;
  }

  Print(L"map->buffer = %08lx, map->map_size = %08lx\n",
      map->buffer, map->map_size);

  EFI_PHYSICAL_ADDRESS iter;
  int i;
  for (iter = (EFI_PHYSICAL_ADDRESS)map->buffer, i = 0;
       iter < (EFI_PHYSICAL_ADDRESS)map->buffer + map->map_size;
       iter += map->descriptor_size, i++) {
    EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)iter;
    len = AsciiSPrint(
        buf, sizeof(buf),
        "%u, %x, %-ls, %08lx, %lx, %lx\n",
        i, desc->Type, GetMemoryTypeUnicode(desc->Type),
        desc->PhysicalStart, desc->NumberOfPages,
        desc->Attribute & 0xffffflu);
    status = file->Write(file, &len, buf);
    if (EFI_ERROR(status)) {
      return status;
    }
  }

  return EFI_SUCCESS;
}

EFI_STATUS EFIAPI UefiMain(
    EFI_HANDLE image_handle,
    EFI_SYSTEM_TABLE* system_table) 
  {
    //save memmap on new file
    status = SaveMemoryMap(&memmap,memmap_file);
    if (EFI_ERROR(status)) {Print(L"Fail to save memmap\n");}
    status = memmap_file->Close(memmap_file);
    if (EFI_ERROR(status)) {Print(L"Fail to close\n");}

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

2
1
1

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?