通过BIOS的0x15中断获取物理内存容量

以前嫌麻烦留着没看,今天补了一下,发现其实只是一些api的调用罢了,这里做个笔记。

对于我们用户来来说,获取物理内存的容量一般用内核调用即可,比如Linux 2.6中使用的detect_memory函数调用。其本质就是使用BIOS的中断0x15检测的。

这里稍微解释一下BIOS(可跳过),BIOS,基本输入输出系统,PC启动加载的第一个软件,介于操作系统与硬件的抽象层,可用于检测,加载引导程序或者操作系统。显然,PC的启动流程应该是 BIOS -> MBR -> loader -> kernel。

简要说明

开始正文。中断0x15有三个子功能,子功能号放到EAX或者AX中,其简要说明:

  • EAX = 0xE820 遍历所有内存
  • AX = 0xE801 分别检测低 15 MB 和 16 MB ~ 4 GB的内存,最大支持 4 GB。
  • AX = 0x88 最简单的子功能,只能检测64MB的内存,若超过也只返回 64 MB。

三分之二的功能对于我们当今的64位系统基本是废的,可见BIOS被淘汰也是历史必然的趋势,虽然现在还活着。

0xE820

0xE820的功能最强,当然也最复杂,它需要多次调用,每次调用都返回一种类型的内存,直到检测完毕。其结果主要通过ARDS结构存储在内存中。ARDS全称地址范围描述符
ARDS结构如下

字节偏移量描述
0基地址低32位
4基地址高32位
8内存长度的低32位,单位字节
12内存长度的低32位,单位字节
16类型
因为我学的的是32位系统,所以没有直接说64位…… 而类型分为操作系统可使用(1),不可使用(2),保留(其他)。

下面是使用前使用说明

寄存器用途说明
EAX子功能号,这里位 0xE820
EBXARDS后续值,必须初始化0,表示下一个待返回的ADRS
ES:DIARDS写入的内存
ECXARDS 的大小,这里是20字节
EDX固定为0x534d4150 ,”SMAP”的ASCII码,意义不明
使用后说明
寄存器用途说明
CF位CF为1,调用出错,否则正常
EAX固定为 SMAP的ASCII码
ES:DI同输入
ECX同输入
EBX更新后的ARDS后续值

0xE801

0x801是稍微适中一点的方法,但是最多只能识别4GB,对于32位系统一般是足够的。
调用结果放在两个寄存器中,低于15KB的内存以1KB位单位,存储在AX和CX中,高于16KB的内存以64KB位单位,存储在BX和DX中。
稍微计算一下便可得出,AX和CX的最大值应为 0x3c00 ,BX和DX的最大值应为 0x10000。

提一下“消失”的 15MB ~ 16MB 去哪了,仍然是历史遗留问题,80286的寻址空间是16MB,当时有些ISA设备需要用到 这1MB空间作为缓存,然后就空出来了。为了兼容,就变成现在这样了。

以下是 0xE801 的使用步骤:

  • AX寄存器写入 0xE801
  • 调用中断0x15
  • CF为0的情况下,输出 $ AX \times 1024 + BX \times 64 \times 1024 $,最后单位是byte。

0x88

最简单的检测法,不详说,只能检测到 物理内存 1MB 之上的内存,故最多先是63MB。
其使用步骤为

  • 在 ax 中写入0x88
  • 调用中断0x15
  • CF位为0的情况下,单位大小为1KB,输出对应结果 $ ax \times 1024 + 1 MB $ 字节

下面是一段三个方法的汇编程序

    total_mem_bytes dd 0

    gdt_ptr  dw  GDT_LIMIT
    dd  GDT_BASE

    ards_buf times 244 db 0
    ards_nr dw 0 

loader_start:

    xor ebx, ebx
    mov edx, 0x534d4150
    mov di, ards_buf
.e820_mem_get_loop:
    mov eax, 0x0000e820
    mov ecx, 20
    int 0x15
    jc .e820_failed_so_try_e801
    add di, cx
    inc word [ards_nr]
    cmp ebx, 0
    jnz .e820_mem_get_loop

    mov cx, [ards_nr]
    mov ebx, ards_buf
    xor edx, edx 

.find_max_mem_area:
    mov eax, [ebx]
    add eax, [ebx+8]
    add ebx, 20
    cmp edx, eax
    jge .next_ards
    mov edx, eax
    .next_ards:
    loop .find_max_mem_area
    jmp .mem_get_ok

.e820_failed_so_try_e801:
    mov ax,0xe801
    int 0x15
    jc .e801_failed_so_try88

    mov cx,0x400
    mul cx
    shl edx,16
    and eax,0x0000FFFF
    or edx,eax
    add edx, 0x100000
    mov esi,edx

    xor eax,eax
    mov ax,bx
    mov ecx, 0x10000
    mul ecx
    add esi,eax
    mov edx,esi
    jmp .mem_get_ok

.e801_failed_so_try88:
    mov  ah, 0x88
    int  0x15
    jc .error_hlt
    and eax,0x0000FFFF

    mov cx, 0x400 
    mul cx
    shl edx, 16
    or edx, eax
    add edx,0x100000

.mem_get_ok:
    mov [total_mem_bytes], edx    ;存入total_mem_bytes处。

....

.error_hlt
    hlt