我们这节课BIOS提供的(int 0x15)号中断的高级功能(eax = 0xE820),遍历主机上所有的内存范围(内存地址段),获取各个内存范围的详细信息
int 0x15 进阶版功能
中断参数
- eax = 0xE820 (固定值)
- edx = 0x534D4150 (固定值)
- ebx --> 初始参数必须为0,终止标志
- ecx --> ARDS 结构体大小 (20字节)
- es : di --> ARDS 结构体数组 (每个元素占用20字节)
地址范围描述结构 (Address Range Descriptor Structure)
int 0x15 进阶功能示例
%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 ; int mem_size = 0
MEM_ARDS_NUM times 4 db 0x00 ; int men_addr_num = 0
MEM_ARDS times 20 * 64 db 0x00 ; ARDS mem_adrs[64] = {0}
[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 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
; 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
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
; return
; eax --> 0 : successd 1 : failed
InitSysMemBuf:
push edi
push edx
push ebx
push ecx
mov edi, MEM_ARDS
mov ebx, 0
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
memerr:
mov eax, 1
memok:
pop ecx
pop ebx
pop edx
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, 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
第33行我们定义了MEM_ARDS_NUM,MEM_ARDS_NUM表示主机上有多少个段。第34行我们定义了MEM_ARDS,MEM_ARDS为ARDS数组的起始地址,大小我们定义为64个ARDS,每个ARDS为20个字节。
157 - 193行,我们定义了InitSysMemBuf,这个函数利用BIOS提供的 int 0x15 的高级功能来获取主机上所有段的段信息。
第57行,我们通过调用InitSysMemBuf这个函数来获取主机上的所有段信息。
下面我们来进行断点调试
InitSysMemBuf函数调用前,MEM_ARDS_NUM = 0;InitSysMemBuf函数调用后,eax = 0,说明这个函数没有调用出错,MEM_ARDS_NUM = 6,表明这个主机上一共有6个段。
下面我们来查看这6个段的具体信息
在查看前6个段的段信息时,它的值不为0。在查看第7个段的段信息,它的值为0,则又说明了这个我们一个查到了6个段。
ARDS记录中并没有包含物理内存容量的信息,那么获取这些记录有什么意义?
ARDS结构体中的TYPE成员
1 - AddressRangeMemory
- 这段内存可以被操作系统使用
2 - AddressRangeReserved
- 内存使用中或被保留,操作系统不可用
其他值 - 未定义
- 保留,可用作 AddressRangeMemory 处理
在32位系统中
ARDS结构体中的 BaseAddrHigh 和 LengthHigh 均为0
物理内存容量需要通过属性为1的内存段计算
计算方式为:max{BaseAddrLow + LengthLow}
- BaseAddrLow + LengthLow 是一段内存的地址上限
根据ARDS记录计算物理内存
我们修改了InitSysMemBuf这个函数,如下
; return
; eax --> 0 : successd 1 : failed
InitSysMemBuf:
push edi
push edx
push ebx
push ecx
mov edi, MEM_ARDS
mov ebx, 0
doloop:
mov eax, 0xE820
mov edx, 0x534D4150
mov ecx, 20
int 0x15
jc memerr
mov eax, [edi + 16]
cmp eax, 1
jne next
mov eax, [edi]
add eax, [edi + 8]
cmp dword [MEM_SIZE], eax
jnb next
mov dword [MEM_SIZE], eax
next:
add edi, 20
inc dword [MEM_ARDS_NUM]
cmp ebx, 0
jne doloop
mov eax, 0
jmp memok
memerr:
mov eax, 1
memok:
pop ecx
pop ebx
pop edx
pop edi
ret
TYPE为1表示这段物理内存可被操作系统使用,我们取这段物理内存的最大值,放入MEM_SIZE中。
下面,我们来进行断点调试,查看MEM_SIZE的值
在调用 InitSysMemBuf 之后,MEM_SIZE = 0x01FF0000字节,并不等于我们设置的32M,我们来查看ARDS中的值,来判断我们实现的算法是否存在错误
我们发现TYPE为1的最大物理内存为 0x01FF0000字节,和我们得到的大小是一样的,这样就证明了我们实现的算法并没有出错。
这里有一个TYPE为3的一个段,大小为0x10000,我们获取到的物理内存加上这个大小就为32M。我们猜想:我们要获得的物理内存大小可能和这个段有关系。
Linux中获取物理内存的策略
在Linux中,获取物理内存是比对了BIOS中断提供的三种获取物理内存的办法。
实验中使用的策略
- 通过 0xE801 计算物理内存大小
- 通过 0xE820 获取各个ARDS 并填入结构体数组
- 根据 ARDS 结构体数组计算物理内存大小
- 选择计算得到的较大内存容量作为最终结果
%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
SYSDAT32_DESC : Descriptor 0, SysDat32SegLen - 1, DA_32 + DA_DR
; 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
SysDat32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
TopOfStack16 equ 0x7c00
[section .sysdat]
SYSDAT32_SEGMENT:
MEM_SIZE times 4 db 0x00 ; int mem_size = 0
MEM_SIZE_OFFSET equ MEM_SIZE - $$
MEM_ARDS_NUM times 4 db 0x00 ; int men_addr_num = 0
MEM_ADDR_NUM_OFFSET equ MEM_ARDS_NUM - $$
MEM_ARDS times 20 * 64 db 0x00 ; ARDS mem_adrs[64] = {0}
MEM_ARDS_OFFSET equ MEM_ARDS - $$
SysDat32SegLen equ $ - SYSDAT32_SEGMENT
[section .d16]
DATA16_SEGMENT:
MEM_ERR_MSG db "[FAILED] memory check error..."
MEM_ERR_MSG_LEN equ $ - MEM_ERR_MSG
Data16SegLen equ $ - DATA16_SEGMENT
[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
; get system memory information
call InitSysMemBuf
cmp eax, 0
jnz CODE16_MEM_ERROR
; 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
mov esi, SYSDAT32_SEGMENT
mov edi, SYSDAT32_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
CODE16_MEM_ERROR:
mov bp, MEM_ERR_MSG
mov cx, MEM_ERR_MSG_LEN
call Print
jmp $
; es:bp --> string address
; cx --> string length
Print:
push ax
push bx
mov dx, 0
mov ax, 0x1301
mov bx, 0x0007
int 0x10
pop bx
pop ax
ret
; 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
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
; return
; eax --> 0 : successd 1 : failed
InitSysMemBuf:
push edi
push edx
push ebx
push ecx
call GetMemSize
mov edi, MEM_ARDS
mov ebx, 0
doloop:
mov eax, 0xE820
mov edx, 0x534D4150
mov ecx, 20
int 0x15
jc memerr
mov eax, [edi + 16]
cmp eax, 1
jne next
mov eax, [edi]
add eax, [edi + 8]
cmp dword [MEM_SIZE], eax
jnb next
mov dword [MEM_SIZE], eax
next:
add edi, 20
inc dword [MEM_ARDS_NUM]
cmp ebx, 0
jne doloop
mov eax, 0
jmp memok
memerr:
mov eax, 1
mov dword [MEM_SIZE], 0
mov dword [MEM_ARDS_NUM], 0
memok:
pop ecx
pop ebx
pop edx
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, 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
我们通过InitSysMemBuf这个函数返回 0xE820 和 0xE801 中较大的物理内存,MEM_SIZE记录了较大的物理内存。
MEM_SIZE 的值为32M。
我们在16位实模式下新增了 InitSysMemBuf 这个函数的出错处理,我们打印一个出错字符串,我们在241行故意令eax = 1, 查看运行结果。
正确的打印出了出错字符串。
下面我们将有关物理内存大小的数据定义为32位保护模式下的只读数据段,因为系统上电后,一开始是工作在实模式下,进行初始化工作,包括获取物理内存,随后就进入了保护模式,系统大部分时间是运行在保护模式下的。这些数据是系统相关的数据,需要保护起来,并将特权级设为0,只有内核才能访问这段数据。
%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
SYSDAT32_DESC : Descriptor 0, SysDat32SegLen - 1, DA_32 + DA_DR
; 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
SysDat32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
TopOfStack16 equ 0x7c00
[section .sysdat]
SYSDAT32_SEGMENT:
MEM_SIZE times 4 db 0x00 ; int mem_size = 0
MEM_SIZE_OFFSET equ MEM_SIZE - $$
MEM_ARDS_NUM times 4 db 0x00 ; int men_addr_num = 0
MEM_ADDR_NUM_OFFSET equ MEM_ARDS_NUM - $$
MEM_ARDS times 20 * 64 db 0x00 ; ARDS mem_adrs[64] = {0}
MEM_ARDS_OFFSET equ MEM_ARDS - $$
SysDat32SegLen equ $ - SYSDAT32_SEGMENT
[section .d16]
DATA16_SEGMENT:
MEM_ERR_MSG db "[FAILED] memory check error..."
MEM_ERR_MSG_LEN equ $ - MEM_ERR_MSG
Data16SegLen equ $ - DATA16_SEGMENT
[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
; get system memory information
call InitSysMemBuf
cmp eax, 0
jnz CODE16_MEM_ERROR
; 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
mov esi, SYSDAT32_SEGMENT
mov edi, SYSDAT32_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
CODE16_MEM_ERROR:
mov bp, MEM_ERR_MSG
mov cx, MEM_ERR_MSG_LEN
call Print
jmp $
; es:bp --> string address
; cx --> string length
Print:
push ax
push bx
mov dx, 0
mov ax, 0x1301
mov bx, 0x0007
mov eax, 0
jmp memok
int 0x10
pop bx
pop ax
ret
; 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
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
; return
; eax --> 0 : successd 1 : failed
InitSysMemBuf:
push edi
push edx
push ebx
push ecx
call GetMemSize
mov edi, MEM_ARDS
mov ebx, 0
doloop:
mov eax, 0xE820
mov edx, 0x534D4150
mov ecx, 20
int 0x15
jc memerr
mov eax, [edi + 16]
cmp eax, 1
jne next
mov eax, [edi]
add eax, [edi + 8]
cmp dword [MEM_SIZE], eax
jnb next
mov dword [MEM_SIZE], eax
next:
add edi, 20
inc dword [MEM_ARDS_NUM]
cmp ebx, 0
jne doloop
mov eax, 0
jmp memok
memerr:
mov eax, 1
mov dword [MEM_SIZE], 0
mov dword [MEM_ARDS_NUM], 0
memok:
pop ecx
pop ebx
pop edx
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, 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