在 x86 PC 起動後,它會處在 real mode。此時,我們可以存取 1 MB 以下的 memory。然而,BIOS 也會使用一些 memory。因此,我們必須要知道 BIOS 佔據哪些範圍,才能夠避開它們。本文章將介紹在 real mode 下的 memory map,以及如何透過 BIOS 取得大於 1 MB 的 memory map。
Table of Contents
Memory Map
在一般的 x86 PC 開機之後,它會進入 real mode。此時的 memory map 如下圖。
在 16-bit real mode 下,bootloader 或 kernel 需要 interrupt vector table(IVT)處理 IRQ,所以我們不可以覆寫 IVT 區域。它們也會呼叫 BIOS 的函式,而 BDA 和 EBDA 是 BIOS 函式儲存資料的地方,所以我們也不可以覆寫 BDA 和 EBDA 區域。但是,當 bootloader 或 kernel 將要進入 32-bit protected mode,我們就可以使用 IVT 和 BDA 區域,因為我們不會再使用 BIOS 函式。
雖然進入 32-bit protected mode 後,我們不會再使用 BIOS 函式,但是我們還是不能覆寫 EBDA 區域。因為 System Management Mode(SMM)會使用到 EBDA。另外,在不同的 BIOS 下,EBDA 的大小是不同的。它的結束位址永遠是 0x9FFFF,但開頭位址是變動的。不過,開頭位址總是會大於 0x80000。
Detecting Memory – E820
由於 EBDA 是變動的大小,加上 kernel 也需要知道 RAM 的總大小,因此 BIOS 提供一些方法讓 bootloader 或 kernel 可以詢問可用和不可用的 memory 範圍,也就是 memory map。在 PC 的演進過程中,BIOS 陸陸續續地提供一些函式讓我們可以取得 memory map。我們將只介紹如何使用 E820 方法,其餘的方法請參照 Detecting Memory (x86)。
E820 是指透過 INT 0x15、EAX=0xE820 呼叫 BIOS 函式。每一次呼叫,BIOS 會將一個 Address Range Descriptor 填入到指定的地方。所以,我們必須要持續地呼叫,直到取得所有的 descriptors。
以下是 INT 0x15 E820h 的輸入。第一次呼叫時,我們要將 EBX 設為 0。設置 ES:DI 來告訴 BIOS 將下一個 Address Range Descriptor 寫入到哪裡。根據不同的 BIOS,Address Range Descriptor 可能是 20 bytes 或是 24 bytes,所以最好將 ECX 設定為 24 bytes。最後,將 EDX 設為 0x534D4150,其代表 SMAP。
Register | Content | Description |
---|---|---|
EAX | Function Code | E820h。 |
EBX | Continuation | 上一次的呼叫會填入值到此 register。第一次呼叫時,填入 0。 |
ES:DI | Buffer Pointer | BIOS 會將 Address Range Descriptor 填入到此位址。 |
ECX | Buffer Size | Buffer 的大小。 |
EDX | Signature | ‘SMAP’(0x534D4150)。由 BIOS 用於驗證呼叫者是否在請求 system map 資訊。 |
以下是 INT 0x15 E820h 的輸出。呼叫函式後,如果有錯誤,CF 會被設置。EAX 會被設為 SMAP。如果這是最後一個 descriptor,EBX 會被設為 0。
Register | Content | Description |
---|---|---|
CF | Carry Flag | Carry flag 沒有被設置的話,表示沒有錯誤發生。 |
EAX | Signature | ‘SMAP’(0x534D4150)。 |
ES:DI | Buffer Pointer | 回傳 Address Range Descriptor pointer。 |
ECX | Buffer Size | BIOS 回傳填入到 buffer 的大小。 |
EBX | Continuation | 包含 continuation value 來取得下一個 Address Range Descriptor。回傳 0 的話,表示這是最後一個 descriptor。 |
每一次呼叫 NT 0x15 E820h,BIOS 會將下一個 descriptor 填入 ES:DI 所指的位址。以下是 Address Range Descriptor 的結構。Base address 是 64-bit,它是該 range 的開頭位址。Length 也是 64-bit,它指出該 range 的長度。Extended attribute 的大小是 32 bits。ACPI 3.0 才會有這一個欄位。這也是為何 Address Range Descriptor Structure 可能是 20 bytes 或 24 bytes。
Offset in Bytes | Name | Description |
---|---|---|
0 | BaseAddrLow | Low 32 Bits of Base Address |
4 | BaseAddrHigh | High 32 Bits of Base Address |
8 | LengthLow | Low 32 Bits of Length in Bytes |
12 | LengthHigh | High 32 Bits of Length in Bytes |
16 | Type | Address type of this range |
20 | Extended Attributes | 請參照 Extended Attributes for Address Range Descriptor Structure |
Type 是該 range 的型態,它的大小是 32-bit。
Value | Mnemonic | Usable by OS | Description |
---|---|---|---|
1 | AddressRangeMemory | Yes | 可用的 RAM,且可被 OS 使用。 |
2 | AddressRangeReserved | No | 保留給 system 使用,OS 不可以使用。 |
3 | AddressRangeACPI | Yes | ACPI Reclaim Memory。在讀取 ACPI tables 之後,OS 可以使用。 |
4 | AddressRangeNVS | No | ACPI NVS Memory。保留給 system 使用,OS 不可以使用。 |
5 | AddressRange Unusable | No | 包含已偵測到的壞 memory。OS 不可以使用。 |
以下是在 Bochs 中,呼叫 INT 0x15 E820 後,得到的輸出。
Base Address | Length | Type |
---|---|---|
0x0000 0000 0000 0000 | 0x0000 0000 0009 FC00 | 1 – Free Memory |
0x0000 0000 0009 FC00 | 0x0000 0000 0000 0400 | 2 – Reserved Memory |
0x0000 0000 000E 8000 | 0x0000 0000 0001 8000 | 2 – Reserved Memory |
0x0000 0000 0010 0000 | 0x0000 0000 01F0 0000 | 1 – Free Memory |
0x0000 0000 FFFC 0000 | 0x0000 0000 0004 0000 | 2 – Reserved Memory |
以下是在 QEMU 中,呼叫 INT 0x15 E820 後,得到的輸出。
Base Address | Length | Type |
---|---|---|
0x0000 0000 0000 0000 | 0x0000 0000 0009 FC00 | 1 – Free Memory |
0x0000 0000 0009 FC00 | 0x0000 0000 0000 0400 | 2 – Reserved Memory |
0x0000 0000 000F 0000 | 0x0000 0000 0001 0000 | 2 – Reserved Memory |
0x0000 0000 0010 0000 | 0x0000 0000 07EE 0000 | 1 – Free Memory |
0x0000 0000 07FE 0000 | 0x0000 0000 0002 0000 | 2 – Reserved Memory |
0x0000 0000 FFFC 0000 | 0x0000 0000 0004 0000 | 2 – Reserved Memory |
範例
以下程式碼顯示如何呼叫 INT 0x15 E820 來讀取 memory map。
.code16 .text start: movw $0x8000, %di movl %ebp, %ebp # Count the number of descriptors read xorl %ebx, %ebx # Set the Continuation to 0 for the first call repeat: movl $0x534D4150, %edx # Set the Signature 'SMAP' movl $24, %ecx # Request for 24 bytes movl $0xE820, %eax int $0x15 jc failed # if the call was failed # Verify the signature 'SMAP' movl $0x534D4150, %edx cmpl %eax, %edx jne failed # Test if ebx is 0, meaning this is the last descriptor testl %ebx, %ebx je end addw $24, %di addl $1, %ebp jmp repeat end: jmp . failed: hlt
結語
除了 E820h 之外,還有數種方法可以詢問 memory map。如果有安裝 GRUB 的話,則可以透過 GRUB 來取得 memory map。如果 BIOS 是 UEFI 的話,UEFI 也有提供更好的方式來取得 memory map。E820h 的好處是,大部分的 BIOS 都會提供此方法。
參考
- Memory Map (x86), OSDev.
- Detecting Memory (x86), OSDev.
- System Address Map Interface, ACPI Specification.
- INT 15h, AX=E820h – Query System Address Map.