我们知分页机制的关键是页请求和页置换,并且页置换发生在没有空闲页框的时候,但是现在出现了问题-是否可以知道还剩多少空闲页框?并且还有多少页框可用?--在这里就得获取物理内存的大小了

一.获取物理内存容量

在获取物理内存大小时,BIOS提供了一些操作方法

A.相关中断(int 0x15)

该中断的基础功能是(eax=0xE801)分别检测低15M和高16M-4G的内存,并且支持4GB内存检测,并且高级功能(eax=0xE820)可以遍历主机上所有的内存范围,获取各个内存范围的详细信息
详细介绍下int 0x15的基础功能-中断参数为eax=0xE801,有三个重要的返回值,cf->成功置0,出错置1;ax,cx是以1kb为单位,表示15MB以下的内存容量;bx,dx是以64KB为单位,表示16MB以上的内存容量

B.标志寄存器

操作系统-获取物理内存容量_第1张图片
对int 0x15基础功能进行简单示例介绍
操作系统-获取物理内存容量_第2张图片
15M以下内存容量左移10位(eax1024),16M以上内存容量先左移6位再左移10位(ebx64*1024)

代码实验

%include "inc.asm"

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]
; GDT definition
;                                     段基址,           段界限,       段属性
GDT_ENTRY       :     Descriptor        0,                0,           0
CODE32_DESC     :     Descriptor        0,        Code32SegLen - 1,    DA_C + DA_32
VIDEO_DESC      :     Descriptor     0xB8000,         0x07FFF,         DA_DRWA + DA_32
DATA32_DESC     :     Descriptor        0,        Data32SegLen - 1,    DA_DRW + DA_32
STACK32_DESC    :     Descriptor        0,         TopOfStack32,       DA_DRW + DA_32
; GDT end

GdtLen    equ   $ - GDT_ENTRY

GdtPtr:
          dw   GdtLen - 1
          dd   0

; GDT Selector

Code32Selector   equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector    equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector   equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector  equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]

TopOfStack16    equ 0x7c00

[section .dat]
[bits 32]
DATA32_SEGMENT:
    DTOS               db  "D.T.OS!", 0
    DTOS_OFFSET        equ DTOS - $$
    HELLO_WORLD        db  "Hello World!", 0
    HELLO_WORLD_OFFSET equ HELLO_WORLD - $$

Data32SegLen equ $ - DATA32_SEGMENT

;
MEM_SIZE   times 4   db  0

[section .s16]
[bits 16]
ENTRY_SEGMENT:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, TopOfStack16

    ; get hardware memory infomation
    call GetMemSize

    ; initialize GDT for 32 bits code segment
    mov esi, CODE32_SEGMENT
    mov edi, CODE32_DESC

    call InitDescItem

    mov esi, DATA32_SEGMENT
    mov edi, DATA32_DESC

    call InitDescItem

    mov esi, STACK32_SEGMENT
    mov edi, STACK32_DESC

    call InitDescItem

    ; initialize GDT pointer struct
    mov eax, 0
    mov ax, ds
    shl eax, 4
    add eax, GDT_ENTRY
    mov dword [GdtPtr + 2], eax

    ; 1. load GDT
    lgdt [GdtPtr]

    ; 2. close interrupt
    cli 

    ; 3. open A20
    in al, 0x92
    or al, 00000010b
    out 0x92, al

    ; 4. enter protect mode
    mov eax, cr0
    or eax, 0x01
    mov cr0, eax

    ; 5. jump to 32 bits code
    jmp dword Code32Selector : 0

; esi    --> code segment label
; edi    --> descriptor label
InitDescItem:
    push eax

    mov eax, 0
    mov ax, cs
    shl eax, 4
    add eax, esi
    mov word [edi + 2], ax
    shr eax, 16
    mov byte [edi + 4], al
    mov byte [edi + 7], ah

    pop eax

    ret

;
;
GetMemSize:
    push eax
    push ebx
    push ecx
    push edx

    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;

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

    jmp getok

geterr:
    mov dword [MEM_SIZE], 0

getok:

    pop edx
    pop ecx
    pop ebx
    pop eax

    ret

[section .s32]
[bits 32]
CODE32_SEGMENT:   
    mov ax, VideoSelector
    mov gs, ax

    mov ax, Stack32Selector
    mov ss, ax

    mov eax, TopOfStack32
    mov esp, eax

    mov ax, Data32Selector
    mov ds, ax

    mov ebp, DTOS_OFFSET
    mov bx, 0x0C
    mov dh, 12
    mov dl, 33

    call PrintString

    mov ebp, HELLO_WORLD_OFFSET
    mov bx, 0x0C
    mov dh, 13
    mov dl, 31

    call PrintString

    jmp $

; ds:ebp    --> string address
; bx        --> attribute
; dx        --> dh : row, dl : col
PrintString:
    push ebp
    push eax
    push edi
    push cx
    push dx

print:
    mov cl, [ds:ebp]
    cmp cl, 0
    je end
    mov eax, 80
    mul dh
    add al, dl
    shl eax, 1
    mov edi, eax
    mov ah, bl
    mov al, cl
    mov [gs:edi], ax
    inc ebp
    inc dl
    jmp print

end:
    pop dx
    pop cx
    pop edi
    pop eax
    pop ebp

    ret

Code32SegLen    equ    $ - CODE32_SEGMENT

[section .gs]
[bits 32]
STACK32_SEGMENT:
    times 1024 * 4 db 0

Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32  equ Stack32SegLen - 1

操作系统-获取物理内存容量操作系统-获取物理内存容量_第3张图片操作系统-获取物理内存容量_第4张图片

分析

从上图首先对获取内存函数进行设置,并且对其进行调用,首先运行bochs得到打印结果,发现程序没有出现错误,但是在这里无法对内存进行查看,所以需要反编译,在该段函数中反编译中,由于是在16位进行的,所以不需要打印32反编译结果,在反编译之后,找到函数所对应的代码,并进行断点设置,如下所示
操作系统-获取物理内存容量操作系统-获取物理内存容量_第5张图片操作系统-获取物理内存容量
设置完之后,在bochs进行断点设置进行调试,同时需要找到内存改变的点对应的内存值,分别两次查看对应的内存情况,如下图所示
操作系统-获取物理内存容量_第6张图片
操作系统-获取物理内存容量
操作系统-获取物理内存容量_第7张图片
将得到的内存值通过进制转换为10进制可以得到
操作系统-获取物理内存容量_第8张图片
但是该结果不表示位MB,通过相除1024*1024得到的结果位31MB,但是在配置文件中得到的文件大小位32MB
操作系统-获取物理内存容量_第9张图片操作系统-获取物理内存容量
在这里获取的内存容量用两部分来表示,并且缺少1MB的原因是80286中的24根地址线最大寻址范围为16MB,之前在介绍两端的容量时,中间正好缺少1MB的内存
操作系统-获取物理内存容量_第10张图片
进行修改-添加1MB的内存
操作系统-获取物理内存容量
通过之前相同的设置以及断点设置,得到的值为0x02000000,通过进制转换为33554432,通过计算为32MB.

二.获取物理内存容量

上面介绍的是基础功能,这里介绍的是高级功能操作系统-获取物理内存容量_第11张图片
参数的设置
操作系统-获取物理内存容量_第12张图片
edx是bios所需要的值,不做深究
地址范围描述结果(ARDS)
操作系统-获取物理内存容量_第13张图片操作系统-获取物理内存容量_第14张图片

代码实验

%include "inc.asm"

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]

; GDT definition

;                                     段基址,           段界限,       段属性

GDT_ENTRY       :     Descriptor        0,                0,           0

CODE32_DESC     :     Descriptor        0,        Code32SegLen - 1,    DA_C + DA_32

VIDEO_DESC      :     Descriptor     0xB8000,         0x07FFF,         DA_DRWA + DA_32

DATA32_DESC     :     Descriptor        0,        Data32SegLen - 1,    DA_DRW + DA_32

STACK32_DESC    :     Descriptor        0,         TopOfStack32,       DA_DRW + DA_32

; GDT end

GdtLen    equ   $ - GDT_ENTRY

GdtPtr:

          dw   GdtLen - 1

          dd   0

; GDT Selector

Code32Selector   equ (0x0001 << 3) + SA_TIG + SA_RPL0

VideoSelector    equ (0x0002 << 3) + SA_TIG + SA_RPL0

Data32Selector   equ (0x0003 << 3) + SA_TIG + SA_RPL0

Stack32Selector  equ (0x0004 << 3) + SA_TIG + SA_RPL0

; end of [section .gdt]

TopOfStack16    equ 0x7c00

[section .dat]

[bits 32]

DATA32_SEGMENT:

    DTOS               db  "D.T.OS!", 0

    DTOS_OFFSET        equ DTOS - $$

    HELLO_WORLD        db  "Hello World!", 0

    HELLO_WORLD_OFFSET equ HELLO_WORLD - $$

Data32SegLen equ $ - DATA32_SEGMENT

;

MEM_SIZE              times    4      db  0

MEM_ARDS_NUM          times    4      db  0      ; int mem_ards_num = 0;

MEM_ARDS              times 64 * 20   db  0 

[section .s16]

[bits 16]

ENTRY_SEGMENT:

    mov ax, cs

    mov ds, ax

    mov es, ax

    mov ss, ax

    mov sp, TopOfStack16

    ; get InitSysMemBuf  infomation

    call InitSysMemBuf

    ; initialize GDT for 32 bits code segment

    mov esi, CODE32_SEGMENT

    mov edi, CODE32_DESC

    call InitDescItem

    mov esi, DATA32_SEGMENT

    mov edi, DATA32_DESC

    call InitDescItem

    mov esi, STACK32_SEGMENT

    mov edi, STACK32_DESC

    call InitDescItem

    ; initialize GDT pointer struct

    mov eax, 0

    mov ax, ds

    shl eax, 4

    add eax, GDT_ENTRY

    mov dword [GdtPtr + 2], eax

    ; 1. load GDT

    lgdt [GdtPtr]

    ; 2. close interrupt

    cli 

    ; 3. open A20

    in al, 0x92

    or al, 00000010b

    out 0x92, al

    ; 4. enter protect mode

    mov eax, cr0

    or eax, 0x01

    mov cr0, eax

    ; 5. jump to 32 bits code

    jmp dword Code32Selector : 0

; esi    --> code segment label

; edi    --> descriptor label

InitDescItem:

    push eax

    mov eax, 0

    mov ax, cs

    shl eax, 4

    add eax, esi

    mov word [edi + 2], ax

    shr eax, 16

    mov byte [edi + 4], al

    mov byte [edi + 7], ah

    pop eax

    ret

;

;

GetMemSize:

    push eax

    push ebx

    push ecx

    push edx

    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

    shl ecx, 20   ; ecx = 1MB

    add dword [MEM_SIZE], eax

    add dword [MEM_SIZE], ebx

    add dword [MEM_SIZE], ecx

    jmp getok

geterr:

    mov dword [MEM_SIZE], 0

getok:

    pop edx

    pop ecx

    pop ebx

    pop eax

    ret

; return 

;    eax  --> 0 : succeed      1 : failed

InitSysMemBuf: 

     push edi

     push ebx

     push ecx

     push edx

     mov edi, MEM_ARDS

     mov ebx, 0

memerr:

     mov dword [MEM_ARDS_NUM], 0

     mov eax, 1

doloop:

     mov eax, 0xE820

     mov edx, 0x534D4150

     mov ecx, 20

     int 0x15

     jc memerr

     add edi, 20

     inc dword [MEM_ARDS_NUM]

     cmp ebx, 0

     jne doloop

     mov eax,0

     jmp memok

memok:   

     pop edx

     pop ecx

     pop ebx

     pop edi

     ret

[section .s32]

[bits 32]

CODE32_SEGMENT:   

    mov ax, VideoSelector

    mov gs, ax

    mov ax, Stack32Selector

    mov ss, ax

    mov eax, TopOfStack32

    mov esp, eax

    mov ax, Data32Selector

    mov ds, ax

    mov ebp, DTOS_OFFSET

    mov bx, 0x0C

    mov dh, 12

    mov dl, 33

    call PrintString

    mov ebp, HELLO_WORLD_OFFSET

    mov bx, 0x0C

    mov dh, 13

    mov dl, 31

    call PrintString

    jmp $

; ds:ebp    --> string address

; bx        --> attribute

; dx        --> dh : row, dl : col

PrintString:

    push ebp

    push eax

    push edi

    push cx

    push dx

print:

    mov cl, [ds:ebp]

    cmp cl, 0

    je end

    mov eax, 80

    mul dh

    add al, dl

    shl eax, 1

    mov edi, eax

    mov ah, bl

    mov al, cl

    mov [gs:edi], ax

    inc ebp

    inc dl

    jmp print

end:

    pop dx

    pop cx

    pop edi

    pop eax

    pop ebp

    ret

Code32SegLen    equ    $ - CODE32_SEGMENT

[section .gs]

[bits 32]

STACK32_SEGMENT:

    times 1024 * 4 db 0

Stack32SegLen equ $ - STACK32_SEGMENT

TopOfStack32  equ Stack32SegLen - 1

操作系统-获取物理内存容量_第15张图片

结果分析

首先通反编译找到函数的调用地址,然后对该处设置地址,同时也对下个地址进行设置,同时打上断点,通过书写的函数,找到所需查看的地址,如下图所示
操作系统-获取物理内存容量操作系统-获取物理内存容量操作系统-获取物理内存容量操作系统-获取物理内存容量
首先查看全局函数0x904d的内存,发下为0是我们所需的结果,查看寄存器eax寄存器为0,说明调用成功,再次查看0x904d发现其为6,这次需要查看数组前六个元素,通过数组地址0x9051,发现其有起始地址,长度,类型,通过打印六次之后,所打印的结果为0,说明得到内存信息
操作系统-获取物理内存容量操作系统-获取物理内存容量_第16张图片操作系统-获取物理内存容量_第17张图片