前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实战分页机制实现 -- 通过实际内存大小动态调整页表个数

实战分页机制实现 -- 通过实际内存大小动态调整页表个数

作者头像
用户3147702
发布2022-06-27 14:23:14
7920
发布2022-06-27 14:23:14
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

上一篇文章中,我们详细讲解了 32 位保护模式下的分页机制,最终,我们将 4GB 的内存区域划分为了连续的 1023 个分页,页表保存在 4MB 的空间中。 详解操作系统分页机制与实战 但是我们的内存大小到底是多少呢?如果内存总共只要 8MB,那上面的分页程序执行完,光是页表就占用了 4MB,空间已经所剩无几,可见,按需使用内存,合理规划页表的大小是非常重要的,而这一切的前提是必须要搞清楚内存总共有多少。 本文我们就来通过一个程序获取计算机的内存信息。

2. 通过 BIOS 中断获取内存信息

我们曾经通过 BIOS 的 10H 硬件中断实现向显示器输出一行文字。 计算机是如何启动的?如何制作自己的操作系统

通过 15H 号硬件终端,我们可以获取系统多个不同方面的信息,这其中就包括内存信息的获取。

2.1. 原理

用于获取内存信息的 10H 中断将内存信息拼装为一个 20 个字节的数据结构 — 地址范围描述符结构,写入到指定的内存中。 每一次中断生成一个描述符结构,用来表示一段连续可用的内存,经过若干次中断调用,即可获取整个内存中若干段的可用内存。

2.2. 地址范围描述符 ARDS

ARDS 是 Address Range Descriptor Structure 的简称,也就是地址范围描述符。 他描述了内存中连续可用的一段,共有 20 个字节:

ARDS 结构

偏移

名称

说明

0

BaseAddrLow

初始物理地址低 32 位

4

BaseAddrHigh

初始物理地址高 32 位

8

LengthLow

内存段长度字节数低 32 位

12

LengthHigh

内存段长度字节数高 32 位

16

Type

地址范围类型,位 1 表示可以被 OS 使用,其他值表示 OS 不可用

2.3. 准备工作

利用 BIOS INT 15H 获取内存信息前,需要填充以下寄存器:

  • EAX — 设置为 0E820h,表示获取内存信息
  • EBX — 设置为 0
  • ES:DI — 信息写入的内存区域首地址
  • ECX — 内存区域大小字节数,通常系统需要写入的数据是 20 字节,如果 ECX 值小于 20,那么 BIOS 会写入 ECX 字节,但有些实现中 BIOS 没有考虑 ECX 的值,总是写入 20 字节
  • EDX — 0534D4150h,用于校验的固定值,中断执行后会被填充到 EAX

2.4. INT 15H 中断完成后寄存器的值

  • EFLAGS — EFLAGS 的 CF 位表示中断执行是否出错,位 0 表示没有出错,为 1 表示出错
  • EAX — 0534D4150h
  • ECX — BIOS 实际写入字节数
  • EBX — 剩余需要写入的地址描述符数量

3. 获取内存信息

下面,我们就在实地址模式下通过 INT 15H 获取内存信息保存在内存上,然后到保护模式下,通过 8025 彩色字符模式打印出内存的信息。

3.1. 分配 ARDS 存储空间

我们需要在数据段中开辟出一块内存用来存储若干个 ARDS 结构,同时为了能够在保护模式下使用,需要创建一个存储偏移的指针。 与之对应的,我们还需要一个变量,用于存储 ARDS 结构的个数,以及保护模式下使用的段内偏移指针变量。

代码语言:javascript
复制
_MemChkBuf:    times    256    db 0    ; ARDS 缓冲区
_dwMCRNumber:            dd 0    ; ARDS 个数

MemChkBuf            equ    _MemChkBuf - $$
dwMCRNumber            equ    _dwMCRNumber - $$

3.2. 在实模式下循环获取 ARDS

代码语言:javascript
复制
    ; 循环获取 ARDS
    xor ebx, ebx                ; 清零 EBX
    mov    di, _MemChkBuf          ; DI 寄存器中保存写入地址偏移
.loop:
    mov    eax, 0E820h             ; 初始化 EAX 为固定值
    mov    ecx, 20                 ; ARDS 字节数
    mov    edx, 0534D4150h         ; 初始化 EDX 为固定值
    int    15h                     ; 触发 15H 中断
    jc    LABEL_MEM_CHK_FAIL      ; EFLAGS 寄存器 CF 位为 1 则跳转,表示失败
    add    di, 20                  ; DI 寄存器指向下一个待写入位置偏移
    inc    dword [_dwMCRNumber]    ; 计数变量 + 1
    cmp    ebx, 0                  ; 比较 EBX 判断是否完成 ARDS 获取
    jne    .loop
    jmp    LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
    mov    dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:

4. 打印 ARDS 并获取最大连续内存

4.1. 内存变量分配

为了打印 ARDS 并获取最大连续内存,除了上面我们已经定义并填充的内存缓冲区,我们还需要定义连续内存大小值的存储变量。 同时,需要一个 ARDS 结构变量来进行比较。

代码语言:javascript
复制
_dwMemSize:             dd 0    ; 最大连续内存大小
_ARDStruct:
    _dwBaseAddrLow:        dd    0
    _dwBaseAddrHigh:    dd    0
    _dwLengthLow:        dd    0
    _dwLengthHigh:        dd    0
    _dwType:        dd    0

dwMemSize           equ    _dwMemSize    - $$
ARDStruct           equ    _ARDStruct    - $$        ; 用于存储 ARDS 结构的临时变量
    dwBaseAddrLow    equ    _dwBaseAddrLow    - $$
    dwBaseAddrHigh    equ    _dwBaseAddrHigh    - $$
    dwLengthLow        equ    _dwLengthLow    - $$
    dwLengthHigh    equ    _dwLengthHigh    - $$
    dwType            equ    _dwType        - $$

RAMSize                db    "RAM size:", 0
OffsetMemSize        equ    RAMSize - $$

4.2. 打印函数定义

随着我们的程序越来越长,我们必须进行函数的封装和拆分,甚至进行文件的拆分,来让我们的程序可读性更强。

4.2.1. 打印字符串

代码语言:javascript
复制
; ------------------------------------------------------------------------
; 显示一个字符串
;
; params:
;   db property
;   dw display_index
;   dw string_offset
; ------------------------------------------------------------------------
DisplayString:
    push ebp
    mov    ebp, esp
    push eax
    push edi
    push esi

    mov    ah, [ebp + 16]
    mov    edi, [ebp + 12]
    mov    esi, [ebp + 8]
    cld
.loop_label:
    lodsb
    test al, al
    jz .over_print
    mov    [gs:edi], ax
    add    edi, 2
    jmp .loop_label

.over_print:
    pop esi
    pop edi
    pop eax
    pop    ebp
    ret

4.2.2. 打印一个字节中的数字

代码语言:javascript
复制
; ------------------------------------------------------------------------
; 显示一个字节中的数字
;
; params:
;   db property
;   dw display_index
;   db number
;
; return:
;   eax display_index
; ------------------------------------------------------------------------
DisplayByteNumber:
    push ebp
    mov    ebp, esp
    push edx
    push edi
    push esi

    mov    ah, [ebp + 16]
    mov    edi, [ebp + 12]
    mov    al, [ebp + 8]

    mov    dl, al
    shr    al, 4
    mov    ecx, 2
.begin:
    and    al, 01111b
    cmp    al, 9
    ja    .letter
    add    al, '0'
    jmp    .number
.letter:
    sub    al, 0Ah
    add    al, 'A'
.number:
    mov    [gs:edi], ax
    add    edi, 2

    mov    al, dl
    loop .begin

    mov    eax, edi

    pop esi
    pop edi
    pop edx
    pop ebp
    ret

4.2.3. 打印一个 dword 中的数字

在 32 位系统中,打印一个 32 位的数字是最为常用的功能,也是我们本次程序中所必须使用的。

代码语言:javascript
复制
; ------------------------------------------------------------------------
; 显示一个整形数
;
; params:
;   db property
;   dw display_index
;   db number
;
; return:
;   eax display_index
; ------------------------------------------------------------------------
DisplayInt:
    push ebp
    mov    ebp, esp
    push edx
    push edi
    push esi
    push ebx
    push ecx

    mov    ah, [ebp + 16]
    mov    edi, [ebp + 12]

    mov ecx, 4
    mov ebx, 24
    push ah
.loop:
    mov    eax, [ebp + 8]
    shr    eax, ebx

    push edi
    push eax
    call DisplayByteNumber
    add esp, 8
    add edi, 2
    sub ebx, 8
    loop .loop

    mov    al, 'h'
    push edi
    mov    [gs:edi], ax
    add    edi, 4
    mv eax, edi

    pop ecx
    pop ebx
    pop esi
    pop edi
    pop edx
    pop ebp
    ret

可以看到,他通过循环调用 DisplayByteNumber 实现了对32位数字的打印。

4.3. 显示内存信息并获取最大连续内存

代码语言:javascript
复制
; ---------------------- 显示内存信息 ---------------------------
DispMemSize:
    push    esi
    push    edi
    push    ebx
    push    ecx
    push    edx

    mov    esi, MemChkBuf      ; esi 指向 ARDS 缓冲器首地址
    mov    ecx, [dwMCRNumber]  ; 循环次数复制
    mov ebx,  (80 * 3) * 2
    push ebx

.dispmemsizeloop:
    mov    edx, 5              ; 依次打印 ARDS 中的 5 个成员
    mov    edi, ARDStruct
.printloop:
    mov eax, 0Fh
    push eax
    mov    eax, ebx
    push eax
    push dword [esi]
    call DisplayInt
    add esp, 12
    mov ebx, eax
    add ebx, 4

    add    esi, 4
    dec    edx
    cmp    edx, 0
    jnz    .printloop

    pop ebx
    add ebx, 160
    push ebx

    cmp    dword [dwType], 1   ; dwType 不为 1 表示操作系统不可用
    jne .finishgetmem
    mov    eax, [dwBaseAddrLow]
    add    eax, [dwLengthLow]  ; 获取当前结构对应的连续内存长度
    cmp    eax, [dwMemSize]    ; 与已获取到的最大内存大小比较,如果大于则更新
    jb .finishgetmem
    mov    [dwMemSize], eax
.finishgetmem:
    loop    .dispmemsizeloop

    pop ebx
    add ebx, 160
    mov eax, 0Fh
    push eax
    mov    eax, ebx
    push eax
    push OffsetMemSize
    call DisplayString
    add    esp, 12

    mov eax, 0Fh
    push eax
    mov    eax, ebx
    push eax
    push dword [dwMemSize]
    call DisplayInt
    add    esp, 12

    pop    edx
    pop    ecx
    pop    ebx
    pop    edi
    pop    esi
    ret

5. 改造分页机制

接下来,我们就要对上一篇文章中的分页机制进行改造,实现在有限的最大连续内存中分配我们的页目录表和页表。

5.1. 变量分配

我们需要动态计算页表个数,因此需要一个变量来存储页表个数。

代码语言:javascript
复制
_PageTableNumber        dd 0    ; 页表个数
PageTableNumber            equ    _PageTableNumber - $$

5.2. 启动分页机制

下面,我们就让我们的程序通过上面计算出的最大可用连续内存来动态决定页表个数,分配可用内存。

代码语言:javascript
复制
; ---------------------- 分页机制启动 ---------------------------
SetupPaging:
    ; 根据内存大小计算应初始化多少PDE以及多少页表
    xor    edx, edx
    mov    eax, [dwMemSize]
    mov    ebx, 400000h            ; 一个页表对应的内存 4MB
    div    ebx
    mov    ecx, eax                ; ecx 保存页表个数
    test    edx, edx
    jz    .no_remainder
    inc    ecx                     ; 余数不为 0 则增加一个页表
.no_remainder:
    mov    [PageTableNumber], ecx    ; 暂存页表个数

    ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

    ; 初始化页目录
    mov    ax, SelectorFlatRW
    mov    es, ax
    mov    edi, PageDirBase    ; 此段首地址为 PageDirBase
    xor    eax, eax
    mov    eax, PageTblBase0 | 7   ; 用户级,存在于内存,可读写
.filter_pde:
    stosd
    add    eax, 4096        ; 为了简化, 所有页表在内存中是连续的.
    loop    .filter_pde

    ; 再初始化所有页表
    mov    eax, [PageTableNumber]    ; 页表个数
    mov    ebx, 1024                ; 每个页表 1024 个 PTE
    mul    ebx
    mov    ecx, eax                ; PTE 个数 = 页表个数 * 1024
    mov    edi, PageTblBase0
    xor    eax, eax
    mov    eax, 7                  ; 用户级、存在于内存、可读写
.filter_pte:
    stosd
    add    eax, 4096                ; 每一页指向 4K 的空间
    loop    .filter_pte

    ; 设置页目录表起始地址
    mov    eax, PageDirBase
    mov    cr3, eax

    ; 开启分页机制
    mov    eax, cr0
    or    eax, 80000000h
    mov    cr0, eax

    ret

6. 运行效果

经过一系列的工作,我们终于完成了我们的程序,让我们的“操作系统”可以获取实际可用的连续内存大小,并在其中分配页表来启动我们的程序,那接下来就让我们执行看看:

7. 附录 — 完整代码

8.1. 函数封装 — lib.asm

代码语言:javascript
复制
; ------------------------------------------------------------------------
; 显示一个字节中的数字
;
; params:
;   db property
;   dw display_index
;   db number
;
; return:
;   eax display_index
; ------------------------------------------------------------------------
DisplayByteNumber:
    push ebp
    mov    ebp, esp
    push edx
    push edi
    push esi

    mov    ah, [ebp + 16]
    mov    edi, [ebp + 12]
    mov    al, [ebp + 8]

    mov    dl, al
    shr    al, 4
    mov    ecx, 2
.begin:
    and    al, 01111b
    cmp    al, 9
    ja    .letter
    add    al, '0'
    jmp    .number
.letter:
    sub    al, 0Ah
    add    al, 'A'
.number:
    mov    [gs:edi], ax
    add    edi, 2

    mov    al, dl
    loop .begin

    mov    eax, edi

    pop esi
    pop edi
    pop edx
    pop ebp
    ret

; ------------------------------------------------------------------------
; 显示一个整形数
;
; params:
;   db property
;   dw display_index
;   db number
;
; return:
;   eax display_index
; ------------------------------------------------------------------------
DisplayInt:
    push ebp
    mov    ebp, esp
    push edx
    push edi
    push esi
    push ebx
    push ecx

    mov    ah, [ebp + 16]
    mov    edi, [ebp + 12]

    mov ecx, 4
    mov ebx, 24
    push ah
.loop:
    mov    eax, [ebp + 8]
    shr    eax, ebx

    push edi
    push eax
    call DisplayByteNumber
    add esp, 8
    add edi, 2
    sub ebx, 8
    loop .loop

    mov    al, 'h'
    push edi
    mov    [gs:edi], ax
    add    edi, 4
    mv eax, edi

    pop ecx
    pop ebx
    pop esi
    pop edi
    pop edx
    pop ebp
    ret

; ------------------------------------------------------------------------
; 显示一个字符串
;
; params:
;   db property
;   dw display_index
;   dw string_offset
; ------------------------------------------------------------------------
DisplayString:
    push ebp
    mov    ebp, esp
    push eax
    push edi
    push esi

    mov    ah, [ebp + 16]
    mov    edi, [ebp + 12]
    mov    esi, [ebp + 8]
    cld
.loop_label:
    lodsb
    test al, al
    jz .over_print
    mov    [gs:edi], ax
    add    edi, 2
    jmp .loop_label

.over_print:
    pop esi
    pop edi
    pop eax
    pop    ebp
    ret

8.2. 主程序 — protect.asm

代码语言:javascript
复制
; ---------------- 内存段描述符宏 -------------
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
    dw    %2 & 0FFFFh                            ; 段界限1
    dw    %1 & 0FFFFh                            ; 段基址1
    db    (%1 >> 16) & 0FFh                    ; 段基址2
    dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 属性1 + 段界限2 + 属性2
    db    (%1 >> 24) & 0FFh                    ; 段基址3
%endmacro

PageDirBase        equ    200000h    ; 页目录开始地址: 2M
PageTblBase        equ    201000h    ; 页表开始地址: 2M+4K

; ------------ DOS 加载初始内存地址 -----------
org    0100h
    jmp    LABEL_BEGIN

; ------------------- GDT ---------------------
[SECTION .gdt]
; GDT
;                              段基址,           段界限,        属性
LABEL_GDT:             Descriptor           0,                0, 0            ; 空描述符
LABEL_DESC_NORMAL:   Descriptor           0,           0ffffh, 92h            ; Normal 描述符
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase,              4095, 92h            ; Page Directory,可读写
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase,              1023, 8092h         ; Page Tables,段界限为 1023 * 4096 字节
LABEL_DESC_CODE32:   Descriptor              0, SegCode32Len - 1, 4098h          ; 非一致代码段
LABEL_DESC_CODE16:   Descriptor              0,           0ffffh, 98h          ; 非一致代码段, 用于跳回 16 BITS 模式
LABEL_DESC_DATA:     Descriptor              0,        DataLen-1, 92h           ; 可读写数据段,界限 64KB
LABEL_DESC_STACK:    Descriptor           0,       TopOfStack, 4093h          ; 32 位全局堆栈段,可读写数据段,且栈指针默认使用 esp 寄存器
LABEL_DESC_VIDEO:    Descriptor     0B8000h,           0ffffh, 92h            ; 显存首地址
; ------------------ END OF GDT ----------------

GdtLen        equ    $ - LABEL_GDT    ; GDT长度
GdtPtr        dw    GdtLen - 1        ; GDT界限
            dd    0                ; GDT基地址

; ------------------ GDT 选择子 -----------------
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorPageDir        equ    LABEL_DESC_PAGE_DIR    - LABEL_GDT
SelectorPageTbl        equ    LABEL_DESC_PAGE_TBL    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
; --------------- END OF 段选择子 ----------------

[SECTION .data1]     ; 数据段
ALIGN    32
[BITS    32]
LABEL_DATA:
SPValueInRealMode    dw    0
BootMessage:        db  "Hello World my OS, techlog.cn!", 0
OffsetBootMessage    equ    BootMessage - $$
RAMSize                db    "RAM size:", 0
OffsetMemSize        equ    RAMSize - $$

_MemChkBuf:    times    256    db 0    ; ARDS 缓冲区
_dwMCRNumber:            dd 0    ; ARDS 个数
_PageTableNumber        dd 0    ; 页表个数
_dwMemSize:             dd 0    ; 最大连续内存大小
_ARDStruct:
    _dwBaseAddrLow:        dd    0
    _dwBaseAddrHigh:    dd    0
    _dwLengthLow:        dd    0
    _dwLengthHigh:        dd    0
    _dwType:        dd    0

MemChkBuf            equ    _MemChkBuf - $$
dwMCRNumber            equ    _dwMCRNumber - $$
PageTableNumber        equ    _PageTableNumber - $$
dwMemSize            equ    _dwMemSize    - $$
ARDStruct           equ    _ARDStruct    - $$        ; 用于存储 ARDS 结构的临时变量
    dwBaseAddrLow    equ    _dwBaseAddrLow    - $$
    dwBaseAddrHigh    equ    _dwBaseAddrHigh    - $$
    dwLengthLow        equ    _dwLengthLow    - $$
    dwLengthHigh    equ    _dwLengthHigh    - $$
    dwType            equ    _dwType        - $$

DataLen                equ    $ - LABEL_DATA

; 全局堆栈段
[SECTION .gs]
ALIGN    32
[BITS    32]
LABEL_STACK:
    times 512 db 0
TopOfStack    equ    $ - LABEL_STACK - 1

[SECTION .s16]
[BITS    16]
LABEL_BEGIN:
    ; 初始化段基址寄存器
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0100h

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [SPValueInRealMode], sp

    ; 循环获取 ARDS
    xor ebx, ebx                ; 清零 EBX
    mov    di, _MemChkBuf          ; DI 寄存器中保存写入地址偏移
.loop:
    mov    eax, 0E820h             ; 初始化 EAX 为固定值
    mov    ecx, 20                 ; ARDS 字节数
    mov    edx, 0534D4150h         ; 初始化 EDX 为固定值
    int    15h                     ; 触发 15H 中断
    jc    LABEL_MEM_CHK_FAIL      ; EFLAGS 寄存器 CF 位为 1 则跳转,表示失败
    add    di, 20                  ; DI 寄存器指向下一个待写入位置偏移
    inc    dword [_dwMCRNumber]    ; 计数变量 + 1
    cmp    ebx, 0                  ; 比较 EBX 判断是否完成 ARDS 获取
    jne    .loop
    jmp    LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
    mov    dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:

    ; 初始化 16 位代码段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE16 + 4], al
    mov    byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化非一致代码段描述符
    xor    eax, eax
    mov    ax, cs
    shl    eax, 4
    add    eax, LABEL_SEG_CODE32    ; 计算非一致代码段基地址物理地址
    mov    word [LABEL_DESC_CODE32 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE32 + 4], al
    mov    byte [LABEL_DESC_CODE32 + 7], ah

    ; 初始化数据段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_DATA
    mov    word [LABEL_DESC_DATA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_DATA + 4], al
    mov    byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆栈段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_STACK
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 7], ah

    ; 准备加载 GDTR
    xor    eax, eax                ; 清空 eax 寄存器
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_GDT            ; 计算出 GDT 基地址的物理地址
    mov    dword [GdtPtr + 2], eax    ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 GDTR
    lgdt    [GdtPtr]

    ; 关闭硬件中断
    cli

    ; 打开 A20 地址总线
    in    al, 92h
    or    al, 00000010b
    out    92h, al

    ; 置位 PE 标志位,打开保护模式
    mov    eax, cr0
    or    eax, 1
    mov    cr0, eax

    ; 跳转进入保护模式
    jmp    dword SelectorCode32:0    ; 执行这一句会把 SelectorCode32 装入 cs,
                                ; 并跳转到 Code32Selector:0  处

; 从保护模式跳回到实模式
LABEL_REAL_ENTRY:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax

    mov    sp, [SPValueInRealMode]

    ; 关闭 A20 地址线
    in    al, 92h
    and    al, 0fdh
    out    92h, al

    ; 打开硬件中断
    sti

    ; 触发 BIOS int 21h 中断,回到实地址模式
    mov    ax, 4c00h
    int    21h

[SECTION .s32]    ; 32 位代码段. 由实模式跳入.
[BITS    32]

LABEL_SEG_CODE32:
    mov    ax, SelectorData
    mov    ds, ax                    ; 数据段选择子

    mov    ax, SelectorVideo
    mov    gs, ax                    ; 赋值视频段选择子

    mov    ax, SelectorStack
    mov    ss, ax                    ; 堆栈段选择子

    mov    esp, TopOfStack

    mov eax, 0Fh
    push eax
    mov    eax, 80 * 2 * 2
    push eax
    push OffsetBootMessage
    call DisplayString
    add esp 12

    call    DispMemSize         ; 显示内存信息
    call    SetupPaging         ; 启动分页机制

    jmp SelectorCode16:0

; ---------------------- 显示内存信息 ---------------------------
DispMemSize:
    push    esi
    push    edi
    push    ebx
    push    ecx
    push    edx

    mov    esi, MemChkBuf      ; esi 指向 ARDS 缓冲器首地址
    mov    ecx, [dwMCRNumber]  ; 循环次数复制
    mov ebx,  (80 * 3) * 2
    push ebx

.dispmemsizeloop:
    mov    edx, 5              ; 依次打印 ARDS 中的 5 个成员
    mov    edi, ARDStruct
.printloop:
    mov eax, 0Fh
    push eax
    mov    eax, ebx
    push eax
    push dword [esi]
    call DisplayInt
    add esp, 12
    mov ebx, eax
    add ebx, 4

    add    esi, 4
    dec    edx
    cmp    edx, 0
    jnz    .printloop

    pop ebx
    add ebx, 160
    push ebx

    cmp    dword [dwType], 1   ; dwType 不为 1 表示操作系统不可用
    jne .finishgetmem
    mov    eax, [dwBaseAddrLow]
    add    eax, [dwLengthLow]  ; 获取当前结构对应的连续内存长度
    cmp    eax, [dwMemSize]    ; 与已获取到的最大内存大小比较,如果大于则更新
    jb .finishgetmem
    mov    [dwMemSize], eax
.finishgetmem:
    loop    .dispmemsizeloop

    pop ebx
    add ebx, 160
    mov eax, 0Fh
    push eax
    mov    eax, ebx
    push eax
    push OffsetMemSize
    call DisplayString
    add    esp, 12

    mov eax, 0Fh
    push eax
    mov    eax, ebx
    push eax
    push dword [dwMemSize]
    call DisplayInt
    add    esp, 12

    pop    edx
    pop    ecx
    pop    ebx
    pop    edi
    pop    esi
    ret

; ---------------------- 分页机制启动 ---------------------------
SetupPaging:
    ; 根据内存大小计算应初始化多少PDE以及多少页表
    xor    edx, edx
    mov    eax, [dwMemSize]
    mov    ebx, 400000h            ; 一个页表对应的内存 4MB
    div    ebx
    mov    ecx, eax                ; ecx 保存页表个数
    test    edx, edx
    jz    .no_remainder
    inc    ecx                     ; 余数不为 0 则增加一个页表
.no_remainder:
    mov    [PageTableNumber], ecx    ; 暂存页表个数

    ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.

    ; 初始化页目录
    mov    ax, SelectorFlatRW
    mov    es, ax
    mov    edi, PageDirBase    ; 此段首地址为 PageDirBase
    xor    eax, eax
    mov    eax, PageTblBase0 | 7   ; 用户级,存在于内存,可读写
.filter_pde:
    stosd
    add    eax, 4096        ; 为了简化, 所有页表在内存中是连续的.
    loop    .filter_pde

    ; 再初始化所有页表
    mov    eax, [PageTableNumber]    ; 页表个数
    mov    ebx, 1024                ; 每个页表 1024 个 PTE
    mul    ebx
    mov    ecx, eax                ; PTE 个数 = 页表个数 * 1024
    mov    edi, PageTblBase0
    xor    eax, eax
    mov    eax, 7                  ; 用户级、存在于内存、可读写
.filter_pte:
    stosd
    add    eax, 4096                ; 每一页指向 4K 的空间
    loop    .filter_pte

    ; 设置页目录表起始地址
    mov    eax, PageDirBase
    mov    cr3, eax

    ; 开启分页机制
    mov    eax, cr0
    or    eax, 80000000h
    mov    cr0, eax

    ret

%include    "lib.asm"    ; 库函数
SegCode32Len        equ    $ - LABEL_SEG_CODE32

; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN    32
[BITS    16]
LABEL_SEG_CODE16:
    ; 跳回实模式:
    mov    ax, SelectorNormal
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax

    mov    eax, cr0
    and    eax, 7FFFFFFEh        ; PE=0, PG=0
    mov    cr0, eax

LABEL_GO_BACK_TO_REAL:
    jmp word 0:LABEL_REAL_ENTRY    ; 段地址会在程序开始处被设置成正确的值

Code16Len    equ    $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]

9. 参考资料

http://www.uruk.org/orig-grub/mem64mb.html。 https://en.wikipedia.org/wiki/BIOS\_interrupt\_call。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 通过 BIOS 中断获取内存信息
    • 2.1. 原理
      • 2.2. 地址范围描述符 ARDS
        • 2.3. 准备工作
          • 2.4. INT 15H 中断完成后寄存器的值
          • 3. 获取内存信息
            • 3.1. 分配 ARDS 存储空间
              • 3.2. 在实模式下循环获取 ARDS
              • 4. 打印 ARDS 并获取最大连续内存
                • 4.1. 内存变量分配
                  • 4.2. 打印函数定义
                    • 4.2.1. 打印字符串
                    • 4.2.2. 打印一个字节中的数字
                    • 4.2.3. 打印一个 dword 中的数字
                  • 4.3. 显示内存信息并获取最大连续内存
                  • 5. 改造分页机制
                    • 5.1. 变量分配
                      • 5.2. 启动分页机制
                      • 6. 运行效果
                      • 7. 附录 — 完整代码
                        • 8.1. 函数封装 — lib.asm
                          • 8.2. 主程序 — protect.asm
                          • 9. 参考资料
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档