获取物理内存容量(上)

前几节课我们完成了页表的实验。我们知道在没有空闲页框的时候会进行页置换,那么怎么知道还剩多少页框?一共还有多少页框也可用?所以,我们需要获取系统物理内存的大小。

如何获取系统物理内存的大小?

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所返回的是可实际使用的内存容量

处理器对内存地址空间做了分段处理 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值