获取物理内存大小

BIOS 提供的内存相关中断(int 0x15)

基础功能(eax = 0xE801)

  • 分别检测低15MB和高 16MB-4GB的内存空间
  • 最大支持 4GB 内存检测

高级功能(eax = 0xE820)

  • 遍历主机上所有的内存范围
  • 获取各个内存范围的详细信息

因此设置中断参数eax=0xE801,调用中断后查看相关寄存器的返回值就可以得到内存容量

  • cf –> 成功 0,出错 1
  • ax,cx –> 以 1KB 为单位, 表示 15MB 以下的内存容量
  • bx,dx –> 以 64 KB 为单位, 表示 16MB 以上的内存容量

汇编小贴士:标志寄存器

image-20220528171611936

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xor eax, eax
mov eax, 0xE801 ; 获取物理内存的大小

int 0x15

jc geterr ; CF位为1说明出错了

;eax 以kB为单位
;ebx y
shl eax, 10 ; 15M以下内存容量
shl ebx, 6 ; 16M以上内存容量
shl ebx, 10

add dword [MEM_SIZE], eax
add dword [MEM_SIZE], ebx

似乎并非真正物理内存?

为什么0xE801获取的内存容量分两部分表示?明明一个32位寄存器可以表示0~4GB大小的任意内存
为什么0xE801获取的内存容量大小少了1MB?

这是由于一些历史原因,80286中的24根地址线最大寻址范围是16MB,80386后为了兼容,在使用int 0x15后,ax寄存器最多表示15MB的内存容量即0x3C00。80386多余16MB的内存容量便要单独返回。

FFF0~FFFF这个区间的内存可以重新映射,比如映射到外设的存储单元,这个时候FFF0~FFFF即便存在也无法使用,即“内存黑洞”。当时ISA设备使用15MB以上的地址作为缓冲区(mmp操作),操作系统无法使用15MB~16MB的部分内存。

因此要加上1MB才是真正大小的物理内存,修正代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mov dword [MEM_SIZE], 0

xor eax, eax
mov eax, 0xE801

int 0x15

jc geterr

shl eax, 10 ; eax = eax * 1024

shl ebx, 6 ; ebx = ebx * 64
shl ebx, 10 ; ebx = ebx * 1024

mov ecx, 1 ; ecx修正1M
shl ecx, 20

add dword [MEM_SIZE], eax
add dword [MEM_SIZE], ebx
add dword [MEM_SIZE], ecx

反汇编ndisasm -o 0x9000 loader > loader.txt 查看loader.txt

image-20220529165120559

因此设置两个断点在调用函数前后,查看MEM_SIZE即0x9049处内存具体值,可以看到是32MB内存大小

1
2
3
4
5
6
7
8
9
10
11
<bochs:5> x /1wx 0x9049
[bochs]:
0x0000000000009049 <bogus+ 0>: 0x00000000
<bochs:6> c
(0) Breakpoint 2, 0x000000000000905e in ?? ()
Next at t=15475924
(0) [0x00000000905e] 0000:905e (unk. ctxt): mov esi, 0x0000913c ; 66be3c910000
<bochs:7> x /1wx 0x9049
[bochs]:
0x0000000000009049 <bogus+ 0>: 0x02000000

结论

  • eax=0xE801返回的是可实际使用的内存容量
  • 处理器对内存地址空间做了分段处理

int 0x15进阶功能

BIOS 提供的内存相关中断(int 0x15)

基础功能(eax = 0xE801)

  • 分别检测低15MB和高 16MB-4GB的内存空间
  • 最大支持 4GB 内存检测

高级功能(eax = 0xE820)

  • 遍历主机上所有的内存范围
  • 获取各个内存范围的详细信息

int 0x15进阶功能中断参数

  • eax = 0xE820 (固定值)
  • edx = 0x534D4150 (固定值)
  • ebx -> 初始参数必须 0, 终止标志
  • ecx ARDS 结构体大小(20 字节)
  • es:di ->ARDS结构体数组,每个元素占用20字节

地址范围描述结构(Address Range Descriptor Structure)

image-20220529165956274

1
2
3
4
5
6
7
8
struct ARDS
{
unsigned int BaseAddrLow;
unsigned int BaseAddrHigh;
unsigned int LengthLow;
unsigned int LengthHigh;
unsigned int Type;
};

ARDS 结构体中的 Type 成员

  1. AddressRangeMemory表示这段内存可以被操作系统使用
  2. AddressRangeReserved表示内存使用中或被保留,操作系统不可使用
  3. 其它值 - 未定义,保留,可当作AddressRangeMemory 处理

通过ARDS结构体数组可以清晰的知道内存空间情况。

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ARDS pARDS[256] = {0};
int count = 0;

es:di = pADRS;
ebx = 0;

do{
//固定参数,每次中断前都需要设置
eax = 0xE820;
edx = 0x534D4150;
ecx = 20;

int 0x15; //每次中断返回ARDS记录,填入es:di中

if(cf == 1){
error();
break;
}

di += 20; //指向下一个ARDS元素
count++;

}while(ebx != 0); //如果所有ARDS记录返回结束,则ebx = 0

获取ARDS记录

在 32 位系统中

  • ARDS结构体中的BaseAddrHigh和LengthHigh均为 0(因为32位系统低位的32位就够了呀,高位32位用不上)
  • 物理内存容量需要通过属性为 1 的内存段计算(说明操作系统可用)
  • 计算方式为: max( BaseAddrLow0 + LengthLow0,BaseAddrLow1 + LengthLow1,BaseAddrLow2 + LengthLow2,...,BaseAddrLowN + LengthLowN )

BaseAddrLow + LengthLow 是一段内存的地址上限
当一片内存可被操作系统使用, 且地址上限最大时, 这个地址上限就是物理内存的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
;   eax -->0:succeed  1:failed
InitSysMemBuf:
push edi
push ebx
push ecx
push edx

; 这里加上就是最终代码了
; call GetMemSize ; 拿到一个物理内存大小

mov edi, MEM_ARDS
mov ebx, 0

doloop:
mov eax, 0xE820
mov edx, 0x534D4150
mov ecx, 20

int 0x15

jc memerr

cmp dword [edi + 16], 1 ; Type字段和1比较
jne next

mov eax, [edi]
add eax, [edi + 8]

cmp dword [MEM_SIZE], eax ; 无需更新,跳转继续比较下一个
jnb next

mov dword [MEM_SIZE], eax ; MEM_SIZE作更新为更大值

next:
add edi, 20
inc dword [MEM_ADRS_NUM]

cmp ebx, 0
jne doloop
jmp memok

memerr:
mov eax, 1
mov dword [MEM_SIZE], 0
mov dword [MEM_ADRS_NUM], 0

memok:
mov eax, 0

pop edx
pop ecx
pop ebx
pop edi

ret

bochs单步调试查看对应代码

发现获得的物理内存的大小是0x01ff0000,并不是0x02000000,然后依次查看MEM_ADRS的内存存储的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<bochs:6> x /1wx 0x9049
[bochs]:
0x0000000000009049 <bogus+ 0>: 0x01ff0000
<bochs:7> x /5wx 0x9051
[bochs]:
0x0000000000009051 <bogus+ 0>: 0x00000000 0x00000000 0x0009f000 0x00000000
0x0000000000009061 <bogus+ 16>: 0x00000001 ;TYPE=1说明是操作系统可用内存大小0x0009f000
<bochs:8> x /5wx 0x9051+20
[bochs]:
0x0000000000009065 <bogus+ 0>: 0x0009f000 0x00000000 0x00001000 0x00000000
0x0000000000009075 <bogus+ 16>: 0x00000002 ;TYPE=2说明是操作系统不可使用这段内存
<bochs:9> x /5wx 0x9051+40
[bochs]:
0x0000000000009079 <bogus+ 0>: 0x000e8000 0x00000000 0x00018000 0x00000000
0x0000000000009089 <bogus+ 16>: 0x00000002 ;TYPE=2说明是操作系统不可使用这段内存
<bochs:10> x /5wx 0x9051+60
[bochs]:
0x000000000000908d <bogus+ 0>: 0x00100000 0x00000000 0x01ef0000 0x00000000
0x000000000000909d <bogus+ 16>: 0x00000001 ;TYPE=1说明是操作系统可用内存大小0x01ff0000
<bochs:11> x /5wx 0x9051+80
[bochs]:
0x00000000000090a1 <bogus+ 0>: 0x01ff0000 0x00000000 0x00010000 0x00000000
0x00000000000090b1 <bogus+ 16>: 0x00000003 ;TYPE=3 这块内存大小0x02000000
<bochs:12> x /5wx 0x9051+100
[bochs]:
0x00000000000090b5 <bogus+ 0>: 0xfffc0000 0x00000000 0x00040000 0x00000000
0x00000000000090c5 <bogus+ 16>: 0x00000002 ;TYPE=2说明是操作系统不可使用这段内存
<bochs:13> x /5wx 0x9051+120
[bochs]:
0x00000000000090c9 <bogus+ 0>: 0x00000000 0x00000000 0x00000000 0x00000000
0x00000000000090d9 <bogus+ 16>: 0x00000000

实验中使用的策略

  1. 通 过 0xE801 计算物理内存大小
  2. 通 过 0xE820 获取各个 ARDS 并填入结构体数组
  3. 根 据 ARDS 结构体数组计算物理内存大小
  4. 选择计算得到的较大内存容量作为最终结果