前几节课我们完成了页表的实验。我们知道在没有空闲页框的时候会进行页置换,那么怎么知道还剩多少页框?一共还有多少页框也可用?所以,我们需要获取系统物理内存的大小。
如何获取系统物理内存的大小?
BIOS提供的相关中断
基础功能(eax = 0xE801)
- 分别检测低15MB和高16MB - 4GB的内存
- 最大支持4GB内存检测
高级功能(eax = 0xE820)
- 遍历主机上所有的内存范围
- 获取各个内存范围的详细信息
由于我们是在32位系统上做实验,所以基础功能够用了
int 0x15 基础功能
中断参数
- eax = 0xE801
返回值
- cf --> 成功0,失败1
- ax,cx --> 以1KB为单位,表示15MB以下的内存容量
- bx,dx --> 以64KB为单位,表示16MB以上的内存容量
cx,dx寄存器的值分别和ax,bx寄存器的值相同,所以我们只需要用到ax,bx寄存器
int 0x15 基础功能示例
eax左移10位是因为eax存放的是15M以下的内存容量,单位是KB,1KB = 1024字节,我们把所获得的容量大小转换为字节,所以需要左移10位。
ebx里存放的是16M以上的内存容量,单位是64KB,先左移6位,把所获得的容量大小转换为1KB为单位,在左移10位,把所获得的容量大小转换为字节为单位。
%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_32 + DA_C
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DRW
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW
; 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
; get hardware memory infomation
MEM_SIZE times 4 db 0x00
[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_Offset equ DTOS - $$
HELLOWORLD db "Hello, World!", 0
HELLOWORLD_Offset equ HELLOWORLD - $$
Data32SegLen equ $ - DATA32_SEGMENT
[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
;
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
; initalize 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 labelBACK_ENTRY_SEGMENT
; 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 [MEM_SIZE], eax
add [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, 13
mov dl, 33
call PrintString
mov ebp, HELLOWORLD_Offset
mov bx, 0x0c
mov dh, 14
mov dl, 30
call PrintString
jmp $
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push cx
push eax
push dx
push edi
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 edi
pop dx
pop eax
pop cx
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
我们定义了一个MEM_SIZE这个4字节大小的内存来存放物理内存的大小,我们定义了GetMemSize这个函数来获取物理内存大小。
我们通过断点调试,来查看MEM_SIZE这个内存中的值。
在调用GetMemSize函数前,MEM_SIZE = 0, 在调用GetMemSize函数后,MEM_SIZE = 0x1f00000字节 = 31MB。
接下来我们来查看在bochs中设置的物理内存大小
我们在bochs中设置的物理内存大小为32MB,而我们获得到的物理内存大小为31M,并不相等。
为什么 0xE801 获取到的内存容量分两部分表示?
为什么 0xE801 获取到了内存容量少了 1MB?
一些历史原因
80286中的24根地址线最大寻址范围是16MB
当时的ISA设备使用15MB以上的地址作为缓冲区
- 操作系统无法使用15MB - 16MB的物理内存,这段内存用来内存映射
- 80386之后的处理器为了兼容,多余16MB的内存容量单独返回
为了兼容性,在使用 int 0x15 后,ax寄存器最多表示15MB的内存容量,即:最大值为0x3C00。这样,对于80286时代的各种程序就可以完全兼容,正常执行。
由于我们需要获得全部的物理内存大小,而我们现在获取到的是可用的物理内存大小,所以我们需要修改GetMemSize这个函数。
GetMemSize:
push eax
push ebx
push ecx
push edx
mov dword [MEM_SIZE], 0
xor eax, eax
mov eax, 0xE801
int 0x15
jc geterr
mov ecx, 1
shl eax, 10 ; eax = eax * 1024
shl ebx, 6 ; ebx = ebx * 64
shl ebx, 10 ; ebx = ebx * 1024
shl ecx, 20 ; ecx = 1M
add [MEM_SIZE], eax
add [MEM_SIZE], ebx
add [MEM_SIZE], ecx
jmp getok
geterr:
mov dword [MEM_SIZE], 0
getok:
pop edx
pop ecx
pop ebx
pop eax
ret
我们加上了这1M不可使用的内存。然后我们通过断点调试,来查看MEM_SIZE这个内存的值。
调用GetMemSize之前,MEMSIZE = 0,调用GetMemSize之后,MEMSIZE = 0x2000000字节 = 32MB,这和我们在bochs设置的物理内存32MB一样。
小推论
eax = 0xE801所返回的是可实际使用的内存容量
处理器对内存地址空间做了分段处理