第四步 设计我们的LOADER——获取内存容量


查看系列文章点这里: 操作系统真象还原

前言

  我们在 第三步 设计我们的LOADER——跨入保护模式 已经知道了如何进入保护模式,但是我们其实还忘记了一件事,那就是获取物理内存的大小!这是为了后续开启分页机制做准备,具体的到时候再说,咱们先来看看如何获取物理内存大小。


一、获取物理内存的方法

  获取物理内存的方法非常多,我们就采取 Linux 2.6 内核获取内存的方法来获取内存大小,它是在实模式下完成的,其内部实现的本质就是通过 BIOS 中断 0x15 来实现的,接下来我们就来介绍一下这个中断。

  这个中断一个有三个子功能,各自能查询的内存大小上限不同,具体如下所示:

  • 0xe820:遍历主机上所有内存;
  • 0xe801:分别检测低15MB和16MB~4GB的内存,最大支持4GB;
  • 0xe88:最多检测出64MB内存;

  虽然为了兼容,可能上述三个子功能有些怪怪的,但是总的来说,它们在使用上都可以分为三个步骤:

(1)往EAX或AX寄存器中填入功能号,并完成其它相关寄存器初始化;
(2)执行中断调用 int 0x15;
(3)在成功的情况下,获取结果;

1、BIOS中断 0x15 子功能 0xe820

  这个方法是这三种中最强大的,因为它返回的信息是最丰富的,需要专门的描述符来描述这些信息,这种描述符被称为地址范围描述符(ARDS),大小为20字节,具体见下表:

字节偏移量属性名称说明
0BaseAddrLow基地址低32位
4BaseAddrHigh基地址高32位
8LengthLow内存长度低32位
12LengthHigh内存长度高32位
16Type内存段类型(操作系统可用还是不可用)

   那么接下来就来看看如果使用这个中断:

调用或返回 寄存器或状态位 参数用途
调用前输入 EAX 子功能号
EBX BIOS需要用的寄存器,需要我们初始化为0
ES:DI 指向存储返回信息的地址
ECX ARDS的大小
EDX 固定值:0x534d4150,用于返回后校验AEDS
返回后输出 CF位 0表示成功,1表示失败
EAX 固定值:0x534d4150
ES:DI 指向存储返回信息的地址
ECX ARDS的大小
EBX 下一个ARDS的位置,若为0表示这是最后一个ARDS

2、BIOS中断 0x15 子功能 0xe801

  使用方法与0xe820相同,只不过参数更少更简单,如下表:

调用或返回 寄存器或状态位 参数用途
调用前输入 AX 子功能号
返回后输出 CF位 0表示成功,1表示失败
AX 以1KB为单位,显示15MB以下的内存容量
BX 以64KB为单位,显示16MB~4GB以下的内存容量
CX 同AX
DX 同DX

3、BIOS中断 0x15 子功能 0xe88

调用或返回 寄存器或状态位 参数用途
调用前输入 AH 子功能号
返回后输出 CF位 0表示成功,1表示失败
AX 以1KB为单位,显示1MB以上的内存容量,不包括低端1MB

二、实践检验!

  实现很简单,直接上代码,注释很详细,大家肯定能看懂!

%include "boot.inc"
; 加载器加载到内存中的位置
SECTION LOADER vstart=LOADER_BASE_ADDR
; 栈是向下增长,故加载器的起始地址也是栈的起始地址
LOADER_STACK_TOP equ LOADER_BASE_ADDR

; ============================================================
; 构建GDT及其内部描述符
; ============================================================

; 第0个不可段描述符(因为不可用,所以全部初始化为0)
GDT_BASE:
    dd 0x00000000
    dd 0x00000000

; 代码段描述符 -> 段基址为0x0,段大小为4GB
CODE_DESC:
    dd 0x0000FFFF
    dd DESC_CODE_HIGH4

; 数据段和栈段描述符 -> 段基址为0x0,段大小为4GB
DATA_STACK_DESC: 
    dd 0x0000FFFF
    dd DESC_DATA_HIGH4

; 显存段描述符 -> 段基址为0xb8000,段大小为32KB
VIDEO_DESC:
    dd 0x80000007
    dd DESC_VIDEO_HIGH4

; 通过地址差获得GDT的大小 
GDT_SIZE equ $-GDT_BASE
; 减1得到段界限
GDT_LIMIT equ GDT_SIZE - 1

; 预留一定空间方便扩充
times 60 dq 0

; 构建代码段、数据段、显存段的选择子
SELECTOR_CODE   equ (0x0001 << 3) + TI_GDT + RPL_0
SELECTOR_DATA   equ (0x0002 << 3) + TI_GDT + RPL_0
SELECTOR_VIDEO  equ (0x0003 << 3) + TI_GDT + RPL_0

; 用于保存内存容量,以字节为单位,该变量在内存中地址为0xb00
total_mem_bytes dd 0

; 构建DGT的指针,在lgdt加载GDT到gdtr寄存器时使用
gdt_ptr:
    dw GDT_LIMIT
    dd GDT_BASE

; 定义一个缓冲区,存储BIOS返回的ARDS结构数据,244字节是为了使loader_start处起始地址为0x300
ards_buf times 244 db 0

; 用于记录ARDS结构体数量
ards_nr dw 0

loader_start:

    mov byte [gs:0xa0], '2'
    mov byte [gs:0xa1], 0xA4
    mov byte [gs:0xa2], ' '
    mov byte [gs:0xa3], 0xA4
    mov byte [gs:0xa4], 'L'
    mov byte [gs:0xa5], 0xA4
    mov byte [gs:0xa6], 'O'
    mov byte [gs:0xa7], 0xA4
    mov byte [gs:0xa8], 'A'
    mov byte [gs:0xa9], 0xA4
    mov byte [gs:0xaa], 'D'
    mov byte [gs:0xab], 0xA4
    mov byte [gs:0xac], 'E'
    mov byte [gs:0xad], 0xA4
    mov byte [gs:0xae], 'R'
    mov byte [gs:0xaf], 0xA4

; ============================================================
; 获取物理内存容量
; ============================================================

; ------------------------
; 利用BIOS中断0x15子功能0xe820获取内存大小
; ------------------------
    xor ebx, ebx          ;使用说明中要求此寄存器必须置0
    mov edx, 0x534d4150   ;签名标记,固定不变,用于校验
    mov di, ards_buf      ;将ES:DI指向准备接收返回数据的地方,ES已在MBR中赋值

.E820_mem_get_loop:
    mov eax, 0x0000e820   ;指定子功能号,因为每次执行int 0x15后eax寄存器的值会被改变,想要循环查询就要重新制定子功能号
    mov ecx, 20           ;指定ARDS结构大小为20字节
    int 0x15              ;执行0x15中断

    ;若cf位为1则有错误发生,尝试0xe801子功能获取内存
    jc .E820_failed_so_try_E801

    ;无错误发生则准备下一次查询
    add di, cx               ;指向缓冲区中下一个ARDS结构位置
    inc word [ards_nr]       ;ARDS数量+1
    cmp ebx, 0
    jnz .E820_mem_get_loop   ;若ebx为0且cf不为1,说明ARDS全部返回,结束循环

; ---------------------------------
; 在所有ARDS中找出内存容量最大的ARDS
; ---------------------------------
    mov cx, [ards_nr]     ;指定循环次数为ARDS数量
    mov ebx, ards_buf
    xor edx, edx          ;使用edx存储最大容量,故先初始化为0

.find_max_mem_area:
    mov eax, [ebx]        ;base_add_low
    add eax, [ebx+8]      ;length_low
    add ebx, 20           ;指向缓冲区中下一个ARDS结构位置
    cmp edx, eax
    jge .next_ards        ;如果edx中的值大于等于eax中的值则跳转
    mov edx, eax          ;更新最大值

.next_ards:
    loop .find_max_mem_area  ;cx寄存器为0后结束查找最大值过程
    jmp .mem_get_ok

; ----------------------------------------
; 利用BIOS中断0x15子功能0xe801获取内存大小
; ----------------------------------------
.E820_failed_so_try_E801:
    mov ax, 0xe801    ;指定子功能号
    int 0x15

    ;若cf位为1则有错误发生,尝试0xe88子功能获取内存
    jc .E801_failed_so_try_88

    ;先计算低15MB的内存
    mov cx,0x400          ;ax和cx中以KB为单,先转化为字节单位
    mul cx
    shl edx, 16
    and eax, 0x0000FFFF
    or edx, eax
    add edx, 0x100000
    mov esi, edx          ;备份低15MB内容

    ;计算内存在16MB~4GB部分
    xor eax, eax
    mov ax, bx            ;bx和dx中以64KB为单,先转化为字节单位
    mov ecx, 0x10000
    mul ecx               ;32位乘法,被乘数eax,结果高32位存入edx,低32位存入eax

    add esi, eax          ;高32位一定为0,因为最多4GB,只加eax即可
    mov edx, esi          ;与前述方法保持一致,edx存储总内存大小
    jmp .mem_get_ok

; ---------------------------------------
; 利用BIOS中断0x15子功能0xe88获取内存大小
; ---------------------------------------
.E801_failed_so_try_88:
    mov ah, 0x88     ;指定子功能号
    int 0x15

    ;若cf位为1则有错误发生,暂停CPU
    jc .error_hlt

    ;int 0x15后,ax被存入内存大小,单位为KB
    and eax, 0x0000FFFF

    mov cx, 0x400      ;转化单位为字节
    mul cx             ;16位乘法,被乘数ax,结果高16位存入dx,低32位存入ax
    shl edx, 16
    or edx, eax
    add edx, 0x100000  ;0x88子功能只会返回1MB以上的部分,故还要加1

; 将 CPU 暂停,直到下一个外部中断被触发(发生)
.error_hlt:
    hlt

.mem_get_ok:
    mov [total_mem_bytes], edx ;将内存大小存入指定处

    mov byte [gs:0x1e0], '4'
    mov byte [gs:0x1e1], 0xA4
    mov byte [gs:0x1e2], ' '
    mov byte [gs:0x1e3], 0xA4
    mov byte [gs:0x1e4], 'M'
    mov byte [gs:0x1e5], 0xA4
    mov byte [gs:0x1e6], 'E'
    mov byte [gs:0x1e7], 0xA4
    mov byte [gs:0x1e8], 'M'
    mov byte [gs:0x1e9], 0xA4
    mov byte [gs:0x1ea], 'E'
    mov byte [gs:0x1eb], 0xA4
    mov byte [gs:0x1ec], 'R'
    mov byte [gs:0x1ed], 0xA4
    mov byte [gs:0x1ee], 'Y'
    mov byte [gs:0x1ef], 0xA4
    mov byte [gs:0x1f0], ' '
    mov byte [gs:0x1f1], 0xA4
    mov byte [gs:0x1f2], 'G'
    mov byte [gs:0x1f3], 0xA4
    mov byte [gs:0x1f4], 'E'
    mov byte [gs:0x1f5], 0xA4
    mov byte [gs:0x1f6], 'T'
    mov byte [gs:0x1f7], 0xA4
    mov byte [gs:0x1f8], ' '
    mov byte [gs:0x1f9], 0xA4
    mov byte [gs:0x1fa], 'O'
    mov byte [gs:0x1fb], 0xA4
    mov byte [gs:0x1fc], 'K'
    mov byte [gs:0x1fd], 0xA4

; ============================================================
; 准备进入保护模式
; ============================================================

    ;打开A20
    in al, 0x92
    or al, 0000_0010B
    out 0x92, al

    ;加载DGT
    lgdt [gdt_ptr]

    ;cr0第0位置1
    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax

    ;刷新流水线
    jmp dword SELECTOR_CODE:p_mode_start

[bits 32]
p_mode_start:
    ;使用选择子初始化各段寄存器
    mov ax, SELECTOR_DATA
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov esp, LOADER_STACK_TOP
    mov ax, SELECTOR_VIDEO
    mov gs, ax

    mov byte [gs:0x140], '3'
    mov byte [gs:0x141], 0xA4
    mov byte [gs:0x142], ' '
    mov byte [gs:0x143], 0xA4
    mov byte [gs:0x144], 'L'
    mov byte [gs:0x145], 0xA4
    mov byte [gs:0x146], 'O'
    mov byte [gs:0x147], 0xA4
    mov byte [gs:0x148], 'A'
    mov byte [gs:0x149], 0xA4
    mov byte [gs:0x14a], 'D'
    mov byte [gs:0x14b], 0xA4
    mov byte [gs:0x14c], 'E'
    mov byte [gs:0x14d], 0xA4
    mov byte [gs:0x14e], 'R'
    mov byte [gs:0x14f], 0xA4
    mov byte [gs:0x150], ' '
    mov byte [gs:0x151], 0xA4
    mov byte [gs:0x152], 'I'
    mov byte [gs:0x153], 0xA4
    mov byte [gs:0x154], 'N'
    mov byte [gs:0x155], 0xA4
    mov byte [gs:0x156], ' '
    mov byte [gs:0x157], 0xA4
    mov byte [gs:0x158], 'P'
    mov byte [gs:0x159], 0xA4
    mov byte [gs:0x15a], 'R'
    mov byte [gs:0x15b], 0xA4
    mov byte [gs:0x15c], 'O'
    mov byte [gs:0x15d], 0xA4
    mov byte [gs:0x15e], 'T'
    mov byte [gs:0x15f], 0xA4
    mov byte [gs:0x160], 'E'
    mov byte [gs:0x161], 0xA4
    mov byte [gs:0x162], 'C'
    mov byte [gs:0x163], 0xA4
    mov byte [gs:0x164], 'T'
    mov byte [gs:0x165], 0xA4

    jmp $

  唯一需要注意的是,我们在这一步中,刻意设计程序入口loader_start处起始地址为0x300,并更改 MBR 中加载 LOADER 的代码为 “ jmp LOADER_BASE_ADDR + 0x300 ”!

  其它就没什么了,一起来看一下运行结果。

在这里插入图片描述

  使用 xp 指令查看一下 total_mem_bytes 的值,可以看到确实为32MB,这与我们在 bochsrc.disk 中定义的内存大小是相符合的。

在这里插入图片描述


  持续更新中~~

  • 39
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值