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;
と書きました。同様にu32
やu64
を定義したいのですが,整数型のビット幅はコンパイラのOS・マシンアーキテクチャオプション依存なので,今回はヘッダファイルstdint.h
を作成し,コンパイラの情報を見てuint16_t
,uint32_t
,uint64_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以上のメモリ領域について,利用可能な物理メモリの領域を確認しました。
明日からはカーネルに移ろうと思います。どういう順番で進めるかはまだ悩んでいるので,明日のお楽しみです!