メモリ情報の習得

ここらで,僕らの興味の対象はメモリ管理へと移る。マシンに搭載されているメモリの量を知らなければ,僕らのメモリ管理の物語は始まらないので,搭載メモリ容量を習得する方法について模索しよう。
起動されたプロテクトモードの OS から,搭載メモリ容量を知るにはどうすればいいだろうか? これは一般に次のような方法で確認できる。まず,メモリ0x0に或る値を書き込み,そのメモリから値を読み出しこの値を書き込んだ値と比較する。次に,メモリアドレスをインクリメントして先ほどと同様の操作を行い,これを繰り返す。もし比較した値が一致しなければ,有効なメモリ外へ書き込み・読み込みを行ったと推測できるので,ここまでに,繰り返したカウントが搭載メモリ容量となる。―― 実際には,この処理を行うプログラムは複雑である。なぜならば,メモリキャッシュを無効にしなければならないし,プログラム自身が読み込まれているメモリ領域まで,書き込みを行うことを考慮しなければならない。(具体的なコードについては,メモリチェックに使う或る値によく `0x55aa55aa` が使われるらしく?「mem_count (0x55AA55AA|0xaa55aa55)」とググれば出てくるので参考にしよう)
以上より搭載メモリ容量を知る事ができるが,この方法は如何せんスマートではないので僕らは他の道を探すことにする。
先ほどまでは,プロテクトモードの OS において話を進めていたが,リアルモードではこの問題はずっと簡単になる。BIOS コールで搭載メモリ容量が確認できるからだ。僕らの OS はプロテクトモードで起動するのだから,当然この仕事はブートローダーが担うことになる。OS はブートローダーから搭載メモリ容量を教えてもらうだけで良い。そして,GRUB はこの仕事を巧くこなしてくれる。
今回の興味の対象は,とりわけメモリサイズの習得であり,ブートローダーからの情報の習得はマニュアル(Multiboot Specification version 0.6.95)に良くまとまっているので,今回はブートローダーから受け取る情報のうちメモリサイズを習得する方法だけを述べる。

GRUB から情報を受け取る

GRUB が起動時に集めたマシンの情報は,Boot information format と呼ばれる構造体に記録されている。
GRUBカーネルの起動に成功すれば,マシンのレジスタEAXにはマジックナンバー`0x2BADB002`(ブートローダーにより,起動が正常に行われたことを示す),レジスタEBXには Boot information format へのポインタアドレスが設定されている。これらのレジスタ値を,head.S でスタックにつみ, kernel.c でmain関数の引数として受け取ることにした。
したがって head.S の先頭は次のようになる:

#define ASM     1
#include "multiboot.h"

        .text
        .code32                         // 32bit-protectmode
        .intel_syntax noprefix          // I hate AT&T-style
        .global _start
_start:
        jmp entry
        .align 4
        // multiboot header
        .long MULTIBOOT_HEADER_MAGIC
        .long MULTIBOOT_HEADER_FLAGS
        .long MULTIBOOT_HEADER_CHECKSUM

entry:
        push    ebx                     // push multiboot_info structure pointer
        push    eax                     // push MBOOT_HEADER_MAGIC

        call     main                   // jmp kernel main

loop:
        hlt
        jmp     loop

情報の習得

Boot information format について

Boot information format の構造は,次に示す multiboot.h で定義された multiboot_info 構造体のようになっている:

#ifndef MULTIBOOT_H
#define MULTIBOOT_H

#define MULTIBOOT_HEADER_MAGIC          0x1BADB002
#define MULTIBOOT_HEADER_FLAGS          0x00000000
#define MULTIBOOT_HEADER_CHECKSUM       -(MULTIBOOT_HEADER_MAGIC+MULTIBOOT_HEADER_FLAGS)
#define KERNEL_STACK_SIZE               0x100000

#define MULTIBOOT_BOOTLOADER_MAGIC      0x2BADB002


#ifndef ASM
// Do not include here in head.S

// The Multiboot information.
typedef struct multiboot_info {
  unsigned long flags;
  unsigned long mem_lower;
  unsigned long mem_upper;
  unsigned long boot_device;
  unsigned long cmdline;
  unsigned long mods_count;
  unsigned long mods_addr;

  // The section header table for ELF
  unsigned long num;
  unsigned long size;
  unsigned long addr;
  unsigned long shndx;

  unsigned long mmap_length;
  unsigned long mmap_addr;
} __attribute__((__packed__)) multiboot_info;

// The memory map
typedef struct memory_map {
  unsigned long size;
  unsigned long base_addr_low;
  unsigned long base_addr_high;
  unsigned long length_low;
  unsigned long length_high;
  unsigned long type;
} __attribute__((__packed__)) memory_map;


#endif /* ASM */
#endif /* MULTIBOOT_H */
メモリサイズの取得

multiboot_info構造体の中でメモリサイズを示すメンバは次の二つである:

  • mem_lower ―― 640KB 以下のメモリサイズを表す。単位は KB。
  • mem_upper ―― 640KB より多くのメモリを搭載していれば,そのサイズ以降の大きさを表す。ただし,1MB分サバ読んでる。

具体的に,搭載メモリサイズは`mem_lower + mem_upper + 1024`となる。

メモリマップの習得

ブートローダーからは,メモリサイズ以外にもさらに詳細なメモリ情報であるメモリマップを受け取ることができる。メモリマップには,搭載メモリの或る領域の状態(OSが使用できるか否か)が書き込まれている。
メモリマップは次のような memory_map 構造体の配列になっていて,その配列の先頭アドレス及び全サイズはそれぞれ multiboot_info 構造体の mmap_length,mmap_addr から習得できる:

offset name 意味
-4 size このメンバを除いた構造体のサイズ ― 16
0 base_addr_low 或るメモリ領域の下位ビット
4 base_addr_high 或るメモリ領域の上位ビット
8 length_low 或るメモリ領域サイズの下位ビット
12 length_high 或るメモリ領域サイズの上位ビット
16 type 或るメモリ領域の状態を表す次に示すような数字
Type メモリの状態
1 使えるメモリ
2 予約されている ― 使えない
3 ACPI が使用済み ― 使える
4 ACPI が使用 ― 使えない
5 何らかの理由で,使えないメモリ
習得した情報を表示してみる

kernel.c は次のようになる:

#include "types.h"
#include "i386.h"
#include "video.h"
#include "interrupt.h"
#include "multiboot.h"

void main(unsigned long mboot_magic, multiboot_info *mboot_info) {
  init_video();

  if(mboot_magic != MULTIBOOT_BOOTLOADER_MAGIC) {
    printf("Error: Invalid magic number");
    return;
  }

  printf("lower=0x%x[KB], upper=0x%x[KB], TotalMemory=%d[MB]\n\n",
         mboot_info->mem_lower,
         mboot_info->mem_upper,
         (mboot_info->mem_lower + mboot_info->mem_upper + 1024)/1024);

  printf("MemoryMap[B]:\n");
  for(memory_map *mmap = (memory_map *) mboot_info->mmap_addr;
      (unsigned) mmap < (mboot_info->mmap_addr + mboot_info->mmap_length);
      mmap++)
    printf("base_addr=0x%x%x, length=0x%x%x, type=%d\n",
           mmap->base_addr_high, mmap->base_addr_low,
           mmap->length_high, mmap->length_low,
           mmap->type);

  init_interrupts();

  for(;;)
    hlt();
}

スクリーンショット

上記のコードを実行すれば,次のような画面が表示される: