四. 获取内存容量

前言

在前面一篇文章中介绍了进入保护模式的方法:
1. 打开A20
2. 加载gdt
3. 将控制寄存器cr0的pe位置1
通过这三步成功的进入到保护模式下,但是在进入保护模式之前,还需要对进行内存的检测工作,启动分页机制等等,最后还要将内核加载到内存当中。这些是之前没有完成的,接下来就要完成这些进入到保护模式之前的准备工作

获取物理内存大小

在linux中,获取内存容量的方法有很多种,比如detect_memory这个函数。不管是通过哪个函数获取到的内存容量,其本质都是通过调用BIOS中断 int 0x15 实现的,0x15中断下有三个子功能
1. eax=0xe820: 遍历主机全部内存
2. ax=0xe801: 分别检测低15MB和16~4GB的内存,最大支持4GB
3. ax=0x88: 最多检测出64MB内存,实际内存超过64MB时也返回64MB

0xE820

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

偏移量 属性名称 描述
0 BaseAddrLow 基地址的低32位
4 BaseAddrHigh 基地址的高32位
8 LengthLow 内存长度的低32位,以字节为单位
12 LengthHigh 内存长度的高32位,以字节为单位
16 Type 本段内存的类型

此结构中每个字段的大小都是4byte,所以此结构大小为20byte,每次int 0x15之后,BIOS就返回这样一个结构的数据。

type字段描述这段内存的类型,也就是表示这段内存的用途。

type值 名称 描述
1 AddressRangeMemory 这段内存可以被操作系统使用
2 AddressRangeReserved 内存使用中或者被系统保留,操作系统不可以使用此内存
其他 未定义 未定义,将来会用到,目前保留,操作系统将其视为AddressRangeReserved

由于我们的kernel运行环境是32位的,所以在ARDS结构属性中,只需要用到低32位属性。BaseAddrLow + LengthLow是一段内存区域的上限。

BIOS中断只是一段函数例程,要按照它的约定对其提供参数。以下是其需要的参数及意义

调用前的输入

int0x15中断调用约定_输入

返回后输出

int0x15中断调用约定_输出

调用步骤如下:
1. 填充输入的参数
2. 执行中断int 0x15
3. 在cf位为0的情况下,从返回的寄存器中获取结构

相应代码如下

;int 0x15 eax=0xe820 edx=0x534d4150
;--------------------------------------
    ;填充输入数据
    xor ebx, ebx    ;将ebx清0
    mov edx, 0x534d4150 
    mov di, ards_buf  ;ards结构缓冲区
  .e820_mem_get_loop:
    mov eax, 0xe820
    mov ecx, 20   ;ards地址范围描述符结构大小位20字节
    int 0x15

    ;获取所有ards内存段
    add di, cx  ;使di增加20字节指向缓冲区中新的ards结构位置
    inc word [ards_nr] ;记录ards数量
    cmp ebx, 0  ;如果ebx为0且cf位不为1,说明adrs全部返回
    jnz .e820_mem_get_loop

    ;在所有ards结构中找出(base_addr_low + length_low)的最大值,即为内存的容量
    mov cx, [ards_nr]
    mov ebx, ards_buf
    xor edx, edx
  .find_max_mem_area:
    mov eax, [ebx]  ;base_addr_low
    add eax, [ebx + 8] ;length_low
    add ebx, 20
    cmp edx, eax
    jge .next_ards
    mov edx, eax
  .next_ards:
    loop .find_max_mem_area
    jmp .mem_get_ok

  .mem_get_ok:
    mov [total_mem_bytes], edx

此段代码中,只是简单的按照格式对int 0x15中断的调用,首先是参数的填充,然后对获取所有的ards内存段,接下来找出这所有内存段中 baseAddrLow+LengthLow 的最大值,该值也就是内存的总容量

0xE801

另一个获取内存容量的方法是BIOS 0x15中断的子功能0xE801,此方法相较于之前的方法较为简单,但是最大只能获取到4GB的内存空间。调用这个功能只需要在ax寄存器中存入子功能号0xe801即可,无需其他的输入数据。

通过此方法获取的内存会分为两组数据分别放到两组寄存器中:

首先是低于15MB的内存,这块内存的大小会存入AX寄存器中,但是存入AX寄存器中的数值的单位是1KB,也就是说 实际的内存大小=AX*1024,AX的最大值为0x3c00

然后是16MB~4GB的内存空间,这块内存的大小会存入BX寄存器中,单位是64KB。所以 内存的实际大小=BX*64*1024

下面是BIOS中断0x15子功能0xE801的说明

mark

相应代码如下

mov ax, 0xe801
int 0x15

;先算出低15MB的内存
mov cx, 0x400 ;将获取到的低15M内存乘1024转化成byte
mul cx
shl edx, 16
and eax, 0xffff ;只取低16位,防止乘法溢出
or edx, eax
add eax, 0x100000
mov esi, edx

;再将16MB以上的空间转化成byte为单位
xor eax, eax
mov ax, bx
mov ecx, 0x10000 ;32位下默认被乘数是eax,将获取到的内存乘以64KB转换成byte
mul ecx
add esi, eax
mov edx, esi 
jmp .mem_get_ok

0x88

该功能算是获取内存最简单的方法了,但功能也是最简单的,最大只能获取64MB的内存空间

调用方法如下。
1. 在ax寄存器中写入子功能号0x88
2. 调用中断0x15
3. cf为0的情况下, ax即为获取到的内存大小,单位1KB,所以实际内存大小=ax*1024 + 1MB

代码如下

mov ah, 0x88
int 0x15
and eax, 0xffff
mov cx, 0x400
mul cx
shl edx, 16
or edx, eax
add edx, 0x100000

猜你喜欢

转载自blog.csdn.net/cw312644167/article/details/80051344