panda's tech note

Advent Calendar 2018: advos

Day 8: システムメモリマップ

7日目まででおおよそブートローダは終わりです。ただ,ひとつ無視してきたのが,1 MiB以上のメモリ領域のマッピングです。メモリアロケータを作る前にこの情報が必要なため,通常は16ビットモードでBIOSサービスを使って取得しておきます。GRUBなどのブートローダもブートローダがシステムメモリマップを取得し,カーネル(OS)にこのシステムメモリマップの情報を提供します。

今日はLinuxのブートメッセージで以下のように出ているものを取得するコードです。

[    0.000000] e820: BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007ffdffff] usable
[    0.000000] BIOS-e820: [mem 0x000000007ffe0000-0x000000007fffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000feffc000-0x00000000feffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved

ブートローダからカーネルに情報を提供するメモリ領域

3日目に以下の1 MiB未満の領域のメモリマップのうち,0x8000-0x8fffのブート情報領域を使います。

Start End Description
00000500 00007bff ブートローダのスタック領域
00007c00 00007dff MBR
00007e00 00007fff 未使用
00008000 00008fff ブート情報(ドライブ番号など)
00009000 0000cfff ブートモニタ (16 KiB)
0000d000 0000ffff 未使用 (ブートモニタ拡張用に予約)
00010000 00079fff 未使用
00079000 0007ffff ロングモード用ページテーブル (24 KiB = 6 * 4 KiB以上必要)

この領域を以下のように使います。メモリマップは0x8800-0x8fffに保存します。メモリマップのエントリ数はシステム依存なので,エントリ数を0x87f0に入れます。

Start Size Description
0x8000 0x0001 ドライブ番号
0x87f0 0x0002 メモリマップのエントリ数
0x8800 0x0800 メモリマップ

このアドレス情報やサイズをsrc/boot/bootinfo.hに定数としてまとめています。

INT 15h, AX=E820hによるシステムメモリマップの読み込み

上記の通り,メモリマップを保存するメモリ領域はブート情報保存用に割り当てた領域の一部に確保しました。最も一般的に使われているシステムメモリマップを取得する方法は,INT 15h, AX=E820hのBIOSサービスコールを使う方法です。INT 15h, AX=E820hの詳細はQuery System Address Mapを参照してください。

INT 15h, AX=E820h

  • 入力パラメータ
    • EBX: エントリを継続的(連続的)に読み込む場合,前のエントリを読み出したINT 15h, AX=E820hのEBXの返り値(Continuation)
    • ES:DI: BIOSがメモリマップエントリを書き込むバッファのアドレス
    • ECX: メモリマップエントリの最大バイト数(20バイト以上)
    • EDX: シグネチャ(0x534d4150="SMAP")
  • 返り値パラメータ
    • CF: エラーの場合セット
    • ES:DI: 入力と同じ
    • ECX: バッファに読み込まれたメモリマップエントリのバイト数
    • EBX: Continuation用の値

メモリマップエントリが20バイト形式の場合,各エントリは

  • 64 bit: ベースアドレス
  • 64 bit: 長さ
  • 32 bit: タイプ で構成されます。ACPI 3.0以降でサポートされた24バイト形式の場合は,最後に32 bitの属性が入ります。

タイプは

  • 1: Usable memory
  • 2: Reserved (unusable)
  • 3: ACPI reclaimable memory
  • 4: ACPI NVS memory
  • 5: Area containing bad memory です。

属性は

  • Bit 0: 有効ビット(無視するOSも多い)
  • Bit 1: Non-volatileメモリ を表しています。

システムメモリマップ読み込みのコード

以下のコードでシステムメモリマップの最大DXエントリをES:DIに読み込みます。読み込んだエントリ数はAXに入れられて帰ります。エントリ数がDXを超えたり,INT 15h, AX=E820hの呼び出しが失敗した場合はCFがセットされます。以下の例ではエントリが20ビット形式で返ってきた場合もES:DIの領域は24ビット形式として書き込んでいます。これにより,ES:DIからAX個の24ビットメモリマップエントリが配列として書き込まれます。

load_mm:
    /* Save registers */
    pushl	%ebx
    pushl	%ecx
    pushw	%di
    pushw	%bp

    xorl	%ebx,%ebx	/* Continuation value for int 0x15 */
    xorw	%bp,%bp		/* Counter */
load_mm.1:
    movl	$0x1,%ecx	/* Write 1 once */
    movl	%ecx,%es:20(%di)	/*  to check support ACPI >=3.x? */
    /* Read the system address map */
    movl	$0xe820,%eax
    movl	$MME_SIGN,%edx	/* Set the signature */
    movl	$MME_SIZE,%ecx	/* Set the buffer size */
    int	$0x15		/* Query system address map */
    jc	load_mm.error	/* Error */
    cmpl	$MME_SIGN,%eax	/* Check the signature SMAP */
    jne	load_mm.error

    cmpl	$24,%ecx	/* Check the read buffer size */
    je	load_mm.2	/*  %ecx==24 */
    cmpl	$20,%ecx
    je	load_mm.3	/*  %ecx==20 */
    jmp	load_mm.error	/* Error otherwise */
load_mm.2:
    /* 24-byte entry */
    testl	$0x1,%es:20(%di)	/* 1 must be present in the attribute */
    jz	load_mm.error	/*  error if it's overwritten */
load_mm.3:
    /* 20-byte entry or 24-byte entry coming from above */
    incw	%bp		/* Increment the number of entries */
    testl	%ebx,%ebx	/* %ebx=0: No remaining info */
    jz	load_mm.done	/* jz/je */
    cmpw	%bp,%dx
    je	load_mm.error	/* Exceeded the maximum number to read */
load_mm.4:
    addw	$MME_SIZE,%di	/* Next entry */
    jmp	load_mm.1	/* Load remaining entries */
load_mm.error:
    stc			/* Set CF */
load_mm.done:
    movw	%bp,%ax		/* Return value */
    popw	%bp
    popw	%di
    popl	%ecx
    popl	%ebx
    ret

64ビットコード(C言語)からシステムメモリマップの表示

アセンブリでシステムメモリマップを確認するのは大変なので,昨日作成したC言語のコードで読み込んだシステムメモリマップを表示します。

昨日のC言語のコードではshortが16ビットであると想定してtypedef unsigned short u16;と書きました。同様にu32u64を定義したいのですが,整数型のビット幅はコンパイラのOS・マシンアーキテクチャオプション依存なので,今回はヘッダファイルstdint.hを作成し,コンパイラの情報を見てuint16_tuint32_tuint64_tを定義します。

ソースコードからはsrc/include/stdint.hをincludeするようにしますが,その実態はOS依存型定義ファイルsrc/include/advos/types.hで定義することにします。今回はLP64のみ対応します。

コードはsrc/boot/boot.cを参照してください。C言語なので詳細は説明しません。

これを実行すると以下のようにシステムメモリマップが表示されると思います。

Welcome to advos (64-bit)!             
System memory map; # of entries = 0x0006
Base             Length           Type     Attribute
0000000000000000 000000000009fc00 00000001 00000001
000000000009fc00 0000000000000400 00000002 00000001
00000000000f0000 0000000000010000 00000002 00000001
0000000000100000 000000003fedf000 00000001 00000001
000000003ffdf000 0000000000021000 00000002 00000001
00000000fffc0000 0000000000040000 00000002 00000001

1 MiB以下はすべてType=Usableとなっていますが,3日目に説明した通り,一部はBIOSで使用されているので,カーネルでは,

Base             Length           Type     Attribute
0000000000100000 000000003fedf000 00000001 00000001

この領域を主に使用する予定です。

まとめと明日の予定

今日はシステムメモリマップを読み込む機能をブートローダに実装し,64ビットモードのコードから,1 MiB以上のメモリ領域について,利用可能な物理メモリの領域を確認しました。

明日からはカーネルに移ろうと思います。どういう順番で進めるかはまだ悩んでいるので,明日のお楽しみです!