5.1 获取物理内存容量
5.1.1 学习linux获取内存的方法
BIOS中断0x15的3个子功能:BIOS中断是实模式下的用法,不能在保护模式下使用。若全部失败了就只好让机器挂起了。运行成功再启动保护模式。
- EAX=0xE820:遍历主机上全部内存
- AX=0xE801:分别检测出64MB和16MB~4GB的内存,最大支持4GB
- AH=0x88:最多检测出64MB内存,实际内存超过该容量也按64MB返回
5.1.2 利用BIOS中断0x15子功能0xe820获取内存
返回地址消息,通过地址范围描述符
Type字段的含义:
BIOS中断0x15中的0xe820子功能需要的参数。
因为是在32位下工作,所以只用到BaseAddrLow+lengthLow是一片内存区域上限。
操作步骤
- 填写“调用前输入”中列出的寄存器。
- 执行中断,调用 int 0x15
- 在CF位为0的情况下,“返回后输出",中对应寄存器会有对应的结果。
5.1.3 利用BIOS中断0x15子功能0xe801获取内存
最大只能识别4GB,对于32位也算可以了。
- 低于15MB的内存以1KB为单位,数量在AX,CX中记录。
因此实际内存=AX*1024。AX,CX最大值为0x3c00 - 16MB~4GB以64KB为单位,数量在BX,DX中记录。
因此实际内存=AX*64 *1024。
至于15~16MB的部分是留给ISA设备的。没有入统计。
操作步骤
- 将AX寄存器写入0xE801
- 执行中断int 0x15
- 在CF=0的情况下,”返回后输出“中对应的寄存器会有对应结果。
5.1.4 利用BIOS中断0x15子功能0x88获取内存
即使内存容量大于64MB,也只会显示63MB。因为此中断只会显示1MB之上的内存,所以记得手动加上1MB。
- 将AX寄存器写入0x88
- 执行中断调用int 0x15
- 在CF位为0的情况下,”返回后输出“中对应的寄存器便会有对应的结果。
5.1.5 实战内存容量检测
更改的内容:将第44行jmp到load的代码改为:
jmp LOADER_BASE_ADDR+0x300
%include "boot.inc"
section mbr vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
;清屏
mov ax,0600h
mov bx,0700h
mov cx,0
mov dx,184fh
int 10h
;输出字符串:MBR
mov byte[gs:0x00],'1'
mov byte[gs:0x01],0xA4
mov byte[gs:0x02],' '
mov byte[gs:0x03],0xA4
mov byte[gs:0x04],'M'
mov byte[gs:0x05],0xA4
mov byte[gs:0x06],'B'
mov byte[gs:0x07],0xA4
mov byte[gs:0x08],'R'
mov byte[gs:0x09],0xa4
mov eax,LOADER_START_SECTOR ;其实栓去lba地址
mov bx ,LOADER_BASE_ADDR ;写入的地址
mov cx ,4 ;这里的cx就是读取扇区数
call rd_disk_m_16
mov byte[gs:160],'r'
mov byte[gs:162],'d'
jmp LOADER_BASE_ADDR+0x300
;===读取硬盘n个扇区===
rd_disk_m_16:
mov esi, eax
mov di,cx
;第一步,读取扇区
mov dx,0x1f2
mov al,cl
out dx,al
mov eax,esi
;第二步,将LBA地址存入0x1f3~0x1f6
;lba地址7~0写入端口0x1f3
mov dx,0x1f3
out dx,al
;lba地址15-8写入端口0x1f4
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;lba地址32~16写入端口0x1f5
shr eax,cl
mov dx,0x1f5
out dx,al
;lba14~27
shr eax,cl
and al,0x0f
or al,0xe0 ;设置7-4位1110,表示lba模式。
mov dx,0x1f6
out dx,al
;第3步:向0x1f7端口写入命令,0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第4步:检测硬盘状态
.not_ready:
nop
in al,dx
and al,0x88
cmp al,0x08
jnz .not_ready
;第5步:从0x1f0端口读数据
mov ax,di
mov dx,256
mul dx
mov cx,ax
;di为要读取的扇区数,一个扇区512字节,每读取一个字;共需要di*512/2次,所以是256
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [bx],ax
add bx,2
loop .go_on_read
ret
times 510-($-$$) db 0
db 0x55,0xaa
让调整数据结构为了让total_mem_bytes在0xb00位置上。
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
;------------------------------
;构建gdt及其内部的描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESK: dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k=0x7
dd DESC_VIDEO_HIGH4 ;此时dp1为0
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
times 60 dq 0
SELECTOR_CODE equ (0x0001<<3)+TI_GDT+RPL0
SELECTOR_DATA equ (0x0002<<3)+TI_GDT+RPL0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0
total_mem_bytes dd 0;存放内存大小
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
;------- 记录 ARDS结构体数 ----------
ards_buf times 244 db 0
ards_nr dw 0
;------------------------------
mov byte[gs:480],'G'
mov byte[gs:482],'D'
mov byte[gs:484],'T'
loader_start: ;打印字符串
mov byte[gs:640],'L'
mov byte[gs:642],'O'
mov byte[gs:644],'A'
mov byte[gs:646],'D'
mov byte[gs:648],'E'
mov byte[gs:650],'R'
mov byte[gs:652],'_'
mov byte[gs:654],'S'
mov byte[gs:656],'T'
mov byte[gs:658],'A'
mov byte[gs:660],'R'
mov byte[gs:662],'T'
; mov sp,LOADER_BASE_ADDR
; mov bp,loadermsg
; mov cx,17
; mov ax,0x1301
; mov bx,0x001f
; mov dx,0x1800
; int 0x10
;--- 获取内存布局 ---
xor ebx, ebx
mov edx, 0x534d4150
mov di, ards_buf
.e820_mem_get_loop:
mov eax, 0x0000e820
mov ecx,20
int 0x15
jc .e820_failed_so_try_e801
add di,cx
inc word [ards_nr]
cmp ebx, 0
jnz .e820_mem_get_loop
mov cx, [ards_nr]
mov ebx, ards_buf
xor edx,edx
.find_max_mem_area:
mov eax,[ebx]
add eax,[ebx+8]
add ebx, 20
cmp edx,eax
jge .next_ards
mov edx,eax
.next_ards:
loop .find_max_mem_area
jmp .mem_get_ok
;------ int 15h ax=E801和------
.e820_failed_so_try_e801:
mov ax,0xe801
int 0x15
jc .e801_failed_so_try88
mov cx,0x400
mul cx
shl edx,16
and eax,0x0000FFFF
or edx,eax
add edx, 0x100000
mov esi,edx
xor eax,eax
mov ax,bx
mov ecx,0x10000
mul ecx
add esi,eax
mov edx,esi
jmp .mem_get_ok
;------ int 15h ah=0x88 ------
.e801_failed_so_try88:
mov ah,0x88
int 0x15
jc .error_hlt
and eax,0x0000FFFF
mov cx,0x400
mul cx
shl edx,16
or edx,eax
add edx,0x100000
.mem_get_ok:
mov [total_mem_bytes],edx
;---准备进入保护模式---
;---打开A20---
in al,0x92
or al,0000_0010B
out 0x92,al
;---加载GDT---
lgdt [gdt_ptr]
;---cr0第0个位置1
mov eax,cr0
or eax,0x00000001
mov cr0,eax
jmp dword SELECTOR_CODE:p_mode_start ;刷新流水线
.error_hlt
hlt
[bits 32]
p_mode_start:
mov ax,SELECTOR_DATA
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,SELECTOR_VIDEO
mov gs,ax
mov byte [gs:800], 'P'
jmp $
按照书中的调试方法进行调试。
得到结果:
更新:
loader.asm
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
;jmp loader_start
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
times 60 dq 0 ;预留60个描述符的位子
;选择子
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
total_mem_bytes dd 0 ;4 保存内存容量
;gdt指针
gdt_ptr dw GDT_LIMIT ;6
dd GDT_BASE
ards_buf times 244 db 0 ;244 ards结构体缓存
ards_nr dw 0 ;2 ards结构体数量,一共 4+6+244+2 = 256字节
loader_start:
;==== 读取内存数
;int 15h eax,ebx,es:di,ecx,edx
xor ebx,ebx ;ARDS后续值,第一次为0
mov edx,0x534d4150 ;固定为签名标记
mov di,ards_buf ;ards结构缓冲区
.e820_mem_get_loop:
mov eax,0x0000e820
mov ecx,20 ;ards 地址范围描述符结构是20字节
int 0x15
jc .e820_failed_so_try_e801 ;若cf为1,则发生错误,常识0xe801
add di,cx ;cx是读到的结构缓冲区中数据的字节数
inc word [ards_nr]
cmp ebx,0 ;如果是0说明这是最后一个ARDS结构
jnz .e820_mem_get_loop
;找出(base_add_low + length_low)的最大值,也就是内存容量
mov cx,[ards_nr]
mov ebx,ards_buf
xor edx,edx
.find_max_mem_area:
mov eax,[ebx]
add eax,[ebx+8]
add ebx,20 ;ebx存放 ARDS缓存内容地址
cmp edx,eax ;比较 edx 与 eax大小
jge .next_ards
mov edx,eax
.next_ards:
loop .find_max_mem_area
jmp .mem_get_ok
;0x15 0xe801
.e820_failed_so_try_e801:
mov ax,0xe801
int 0x15
jc .e801_failed_so_try88
;1 先算出低15MB的内存
mov cx,0x400;ax KB单位2^10 ,0x400-> 1024
mul cx
shl edx,16
and eax,0x0000FFFF
or edx,eax
add edx, 0x100000 ;补上1MB
mov esi,edx ;先放在esi中
;2 算>16MB的内存 bx 64KB为单位
xor eax,eax
mov ax,bx
mov ecx,0x10000 ;将64KB单位转为byte
mul ecx
add esi,eax ;<=15 + >16
jmp .mem_get_ok
.e801_failed_so_try88:
mov ah,0x88
int 0x15
jc .error_hlt
and eax,0x0000FFFF ;ax*1024+1MB
mov cx,0x400
mul cx ;ax*1024->dx,ax
shl edx,16
or edx,eax
add edx,0x100000 ;+1MB
.mem_get_ok:
mov [total_mem_bytes],edx
.error_hlt:
mov byte [gs:160],'2'
mov byte [gs:162],' '
mov byte [gs:164],'0'
mov byte [gs:166],'x'
mov byte [gs:168],'1'
mov byte [gs:170],'5'
;====
;1 打开 A20
in al,0x92
or al,0x02
out 0x92,al
;2 加载 GDT
lgdt [gdt_ptr]
;3 cr0 pe置1
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp dword SELECTOR_CODE:p_mode_start
[bits 32]
p_mode_start:
mov ax,SELECTOR_DATA
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,SELECTOR_VIDEO
mov gs,ax
mov byte [gs:320],'3'
mov byte [gs:322],' '
mov byte [gs:324],'P'
mov byte [gs:326],'r'
mov byte [gs:328],'o'
mov byte [gs:330],'t'
mov byte [gs:332],'e'
mov byte [gs:334],'c'
mov byte [gs:336],'t'
mov byte [gs:338],'i'
mov byte [gs:340],'o'
mov byte [gs:342],'n'
mov byte [gs:344],' '
mov byte [gs:346],'m'
mov byte [gs:348],'o'
mov byte [gs:350],'d'
mov byte [gs:352],'e'
mov byte [gs:354],'.'
jmp $
mbr.asm
%include "boot.inc"
section MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax
;使用 int 0x10 功能号 0x06 清屏
mov ah,0x06
mov al,0x00 ;上卷行数 0->全部
mov bl,0x00
mov bh,0x07
mov cx,0x0000 ;左上( 0, 0)
mov dx,0x184f ;右下(80,25)
int 0x10
; ---------- 在显存中输入信息 ---------
; 输出背景为 黄 色,前景 红 色,并且跳动的字符串 "HELLO WORLD"
mov bx,0
mov cx,14
mov si,0
print:
mov al,[s+si]
mov [gs:bx],al
inc bx
mov byte [gs:bx],0x24
inc bx
add si,1
loop print
mov eax, LOADER_START_SECTOR
mov bx, LOADER_BASE_ADDR
mov cx, 4
call rd_disk_m_16
jmp LOADER_BASE_ADDR+0x300
rd_disk_m_16:
mov esi,eax
mov di,cx
;指定扇区数0x1f2 8bit
mov dx,0x1f2
mov al,cl
out dx,al
mov eax,esi
;LBA 0-7 8bit
mov dx,0x1f3
out dx,al
;LBA 8-15 8bit
mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al
;LBA 16-23
shr eax,cl
mov dx,0x1f5
out dx,al
;LBA Device | 24-27
shr eax,cl
and al,0000_1111b
or al,1110_0000b ;LBA, DEV主盘
mov dx,0x1f6
out dx,al
;写入命令
mov dx,0x1f7
mov al,0x20
out dx,al
;检测硬盘状态
.not_ready:
nop ;相当于个停顿
in al,dx
and al,1000_1000b ;忙/准备好
cmp al,0000_1000b
jnz .not_ready
;读数据
mov ax,di
mov dx,256 ;512/2
mul dx
mov cx,ax ;一共ax个字节
mov dx,0x1f0
.read_data:
in ax,dx
mov [bx],ax
add bx,2
loop .read_data
ret
s db "1 MBR_start!!!"
times 510 - ($-$$) db 0
db 0x55, 0xaa
运行结果:
2024/2/23记录
loader.asm
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
loader_start:
call def_print_loader
call def_read_card_num
call def_open_A20
call def_loader_GDT_set_PE
jmp dword SELECTOR_CODE:p_mode_start
LOADER_STACK_TOP equ LOADER_BASE_ADDR
GDT_BASE:
dd 0x00000000
dd 0x00000000
CODE_DESC:
dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC:
dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC:
dd 0x80000007
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
times 60 dq 0;预留60个描述符的空位
;选择子
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
;gdt 指针
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
;===读取内存数
total_mem_bytes dd 0;4 保存内存容量
ards_buf times 244 db 0
ards_nr dw 0
def_read_card_num:
xor ebx,ebx
mov edx,0x534d4150
mov di,ards_buf
.e820_mem_get_loop:
mov eax,0x0000e820
mov ecx,20
int 0x15
jc .e820_failed_so_try_e801
add di,cx
inc word[ards_nr]
cmp ebx,0
jnz .e820_mem_get_loop
;找出(base_add_low + length_low)的最大值,也就是内存容量
mov cx,[ards_nr]
mov ebx,ards_buf
xor edx,edx
.find_max_mem_area:
mov eax,[ebx]
add eax,[ebx+8]
add ebx,20
cmp edx,eax
jge .next_ards
mov edx,eax
.next_ards:
loop .find_max_mem_area
jmp .mem_get_ok
;0x15 0xe801
.e820_failed_so_try_e801:
mov ax,0xe801
int 0x15
jc .e801_failed_so_try88
;1 先算出低15MB的内存
mov cx,0x400 ;ax KB单位2^10, 0x400->1024
mul cx
shl edx,16
and eax,0x0000FFFF
or edx,eax
add edx,0x100000
mov esi,edx
;2 算 > 16MB 的内存 bx 64KB位单位
xor eax,eax
mov ax,bx
mov ecx,0x10000;将64KB单位转为byte
mul ecx
add esi,eax ; <=15 + >16
jmp .mem_get_ok
.e801_failed_so_try88:
mov ah,0x88
int 0x15
jc .error_hlt
and eax,0x0000FFFF
mov cx,0x400
mul cx
shl edx,16
or edx,eax
add edx,0x10000 ;+1MB
.mem_get_ok:
mov [total_mem_bytes], edx
.error_hlt:
mov byte [gs:480],'['
mov byte [gs:482],'4'
mov byte [gs:484],']'
mov byte [gs:486],' '
mov al,[total_mem_bytes+3]
add al,'0'
mov byte [gs:488],'0'
mov byte [gs:490],al
mov al,[total_mem_bytes+2]
add al,'0'
mov byte [gs:492],'0'
mov byte [gs:494],al
mov al,[total_mem_bytes+1]
add al,'0'
mov byte [gs:496],'0'
mov byte [gs:498],al
mov al,[total_mem_bytes+0]
add al,'0'
mov byte [gs:500],'0'
mov byte [gs:502],al
ret
def_print_loader:
mov bx,160
mov cx,loader_str_len
mov si,0
print_loader_loop:
mov al, [loader_str + si]
mov [gs:bx],al
inc bx
mov al, [loader_str_color]
mov [gs:bx],al
inc bx
add si,1
loop print_loader_loop
ret
loader_str db '[2] This is loader code'
loader_str_len equ $-loader_str
loader_str_color db 0000_0111b
def_open_A20:
in al,0x92
or al,0x02
out 0x92,al
ret
def_loader_GDT_set_PE:
lgdt[gdt_ptr]
mov eax,cr0
or eax,0x01
mov cr0,eax
ret
[bits 32]
p_mode_start:
mov ax,SELECTOR_DATA
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,SELECTOR_VIDEO
mov gs,ax
mov byte [gs:320],'['
mov byte [gs:322],'3'
mov byte [gs:324],']'
mov byte [gs:326],' '
mov byte [gs:328],'3'
mov byte [gs:330],'2'
mov byte [gs:332],' '
mov byte [gs:334],'i'
mov byte [gs:336],'n'
mov byte [gs:338],'t'
mov byte [gs:340],'o'
jmp $
运行截图: