《操作系统真象还原》第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式

第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式

这里写目录标题

  • 第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式
    • 1.编写MBR
    • 2.GDT介绍
    • 3.段描述符介绍
      • 选择子结构
      • 段描述符结构
    • 4.构建GDT
    • 5.检测最大内存
    • 6.进入保护模式三部曲

1.编写MBR

首先编写mbr.S,将写入磁盘的第一个扇区,用于加载loader。

%include "boot.inc"
SECTION MBR vstart=0x7c00 ;起始地址编译在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
    ;这个时候 ds = es = ss = 0 栈指针指向MBR开始位置
  • 首先告诉编译器将程序的起始地址编译为0x7c00。

  • 接下来用cs寄存器去初始化其他栈寄存器,由于我们是从jmp 0 : 0x7c00 (cs:ip) 指令过来的,所以此时cs为0。

  • 接着初始化栈指针,由于MBR加载到0x7c00以上的位置,所以此时0x7c00一下的内存是安全的。

;清屏
;利用0x06号功能,上卷全部行,即可清屏
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;输入:
;AH 功能号 = 0x06
;AL = 上卷行数 (如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(x,y)位置
;(DL,DH) = 窗口右下角的(x,y)位置
;无返回值
	mov ax,0600h
	mov bx,0700h
	mov cx,0
	mov dx,184fh
;因为VGA文本模式中,一行只能容纳80个字符,共25行
;下标从0开始,所以0x18 = 24, 0x4f = 79
	int 0x10

接着利用BIOS的int10中断进行清屏操作,BIOS的int10号中断负责有关打印的例程。功能号0x06用于上卷行。具体介绍见注释。

  • 接下来将磁盘中的loader加载到内存中,并跳转执行。
	mov eax,LOADER_START_SECTOR 	;起始扇区lba地址
    mov bx,LOADER_BASE_ADDR			;写入的地址
    mov cx,4						;待读入的扇区数
    call rd_disk_m_16				;以下读取程序的起始部分
    
    jmp LOADER_BASE_ADDR + 0x300

;功能:读取硬盘n个扇区 
rd_disk_m_16:		;eax = LBA扇区号 bx = 将数据写入的内存地址 cx = 读入的扇区数
    mov esi,eax		;备份eax
    mov di,cx		;备份cx
;读写硬盘
;第一步:设置要读取的扇区数
    mov dx,0x1f2	
    mov al,cl
    out dx,al
   
    mov eax,esi		;恢复eax
    
;第二步:将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地址23~16位写入端口0x1f5
    shr eax,cl
    mov dx,0x1f5
    out dx,al
    
    shr eax,cl
    and al,0x0f		;LBA第24~27位
    or al,0xe0		;设置7~4位为1110,表示LBA模式
    mov dx,0x1f6
    out dx,al    
    
;第三步:向0x1f7端口写入读命令,0x20
    mov dx,0x1f7
    mov al,0x20
    out dx,al
    
;第四步:检测硬盘状态
.not_ready:
	;同一端口,写时表示写入命令数,读时表示读入硬盘状态
  	nop
  	in al,dx
  	and al,0x88		;第3位为1表示硬盘控制器已准备好数据传输
  					;第7位为1表示硬盘忙
  	cmp al,0x08		
  	jnz .not_ready	;若未准备好,继续等
  	
 ;第五步:从0x1f0端口读数据
  	mov ax,di
  	mov dx,256
  	mul dx
  	mov cx,ax
;di为要读取的扇区数,一个扇区有512字节,每次读入一个字
;共需di*512/2次
  	mov dx,0x1f0

.go_on_read:
	in ax,dx
	mov [bx],ax
	add bx,2
	loop .go_on_read
	ret
  • 最后填充,并以0x55, 0xaa结尾
	;字符串声明 db == define byte dw == define word ascii一个字符占一个字节
    ;预留两个字节 其余空余的全部用0填满 为使检测当前扇区最后两字节为0x55 0xaa 检测是否为有效扇区
    ;510 = 512字节-2预留字节  再减去(当前位置偏移量-段开始位置偏移量)求出来的是剩余空间
    times 510 - ($ - $$) db 0 
    db 0x55,0xaa

2.GDT介绍

GDT是全局描述符表(Global Descriptor Table),用于索引描述符

寄存器GDTR(GDT Register)用来存储GDT的内存地址及大小,GDTR是一个48位寄存器

GDTR第0-15位为GDT界限,第16-47位为GDT内存起始地址

《操作系统真象还原》第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式_第1张图片

对此寄存器的访问只能通过lgdt指令,指令格式为 lgdt 48位内存数据

3.段描述符介绍

进入保护模式后,段的信息增加了很多,需要段描述符来描述一个段的各种属性。在保护模式下,内存访问依旧是“段基址:段内偏移地址”的形式,但段寄存器中是选择子,而不是段基址。

段基址在段描述符中,给出的选择子索引到描述符后,CPU自动从中取出段基址,再加上段内偏移地址便可得到物理地址。

注意:在保护模式下已经是32位地址线和32位寄存器,不需要再将段基址乘以16

选择子结构

其中0-2位为RPL位,表示请求特权级,0为最高特权级

《操作系统真象还原》第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式_第2张图片

段描述符结构

《操作系统真象还原》第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式_第3张图片

  • 段界限表示最高拓展到多少,实际的段界限边界值 = (描述符中段界限 + 1) * (段界限的粒度大小:4KB 或者 1) - 1

  • S表示该段是系统段还是数据段,为0表示系统段(硬件运行相关),为1表示数据段(软件运行相关)

  • DPL (Descriptor Privilege Level) 即描述符特权级,0为最高级别

  • P(Present),即段是否在内存中,存在则为1,不存在则为0。

  • AVL (AVaiLable),保留字段,软件可自行使用。

  • L 字段,用来设置是否为64位代码段,为1表示64位代码段,为0表示32位代码段

  • D/B字段,用来指定有效地址(端内偏移地址)及操作数的大小。对代码段来说,为D位,1表示32位,0表示16位。对栈段来说是B位,用来指定操作数大小,为1表示32位操作数,为0表示16位操作数

  • G表示粒度,为0则表示1字节,为1表示4KB字节

  • Type字段要和S字段搭配使用,具体表示如下图

《操作系统真象还原》第一篇:编写MBR,加载loader,构建GDT,检测内存容量,进入保护模式_第4张图片

4.构建GDT

%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR

;构建gdt以其内部的描述符
	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	;limit = (0xbffff-0xb8000)/4k = 0x7
				dd DESC_VIDEO_HIGH4	;此时dpl为0

	GDT_SIZE equ $ - GDT_BASE
	GDT_LIMIT equ GDT_SIZE - 1

	times 60 dq 0		;此处预留60个描述符空位
	
	SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0  ;相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
	SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	;同上
	SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	;同上
	
	;total_mem_bytes 用于保存内存容量,以字节为单位
	;当前偏移loader.bin文件头0x200字节
	;loader.bin的加载地址是0x900
	;故total_mem_bytes的地址是0xb00

	total_mem_bytes dd 0
	
	;以下是gdt的指针,前2字节是gdt界限,后4字节是gdt起始地址

	gdt_ptr dw GDT_LIMIT
				dd GDT_BASE

首先在GDT中构建代码段,栈段和显存段的段描述符。并预留60个段描述符位置。

注意:GDT的第0个描述符不可用,为了避免选择子忘记初始化而误访问到第0个段描述符。

5.检测最大内存

接下来利用BIOS 0x15中断的e820h子功能检测内存容量

;人工对齐,total_mem_bytes(4) + gdt_ptr(6) + ards_buf(244) + ards_nr(2) = 256 字节
	ards_buf times 244 db 0
	ards_nr dw 0 		;用于记录ARDS结构体数量
	
	loader_start:
	
; int 15h eax = 0000E820h, edx = 534D4150h ('SMAP') 获取内存布局

	xor ebx, ebx			;第一次调用时,ebx要为0
	mov edx, 0x534d4150		;edx只赋值一次,循环体中不会改变
	mov di, ards_buf		;ards结构缓冲区
	
.e820_mem_get_loop:			;循环获取每个ARDS内存范围描述结构
	mov eax, 0x0000e820		;执行int 0x15后, eax会变为0x534d4150
							;所以每次int前都要将eax更新为功能号
	mov ecx, 20				;ARDS地址范围描述符结构大小是20字节
	int 0x15				
	add di, cx				;使di增加20字节指向下一个位置
	inc word [ards_nr]		;记录ARDS的数量
	cmp ebx, 0				;若ebx为0,说明ards全部返回
							;当前已是最后一个
	jnz .e820_mem_get_loop
	
							;在所有ards中,
							;找出base_add_low + length_low 的最大值,即内存的容量
	mov cx, [ards_nr]
							;遍历每一个ARDS结构体,循环次数是ARDS的数量
	mov ebx, ards_buf
	xor edx, edx 			;edx是最大内存的数量,在此先清0
	
.find_max_mem_area:
;无需判断type是否为1,最大的内存块一定是可被使用的
	mov eax, [ebx]			;base_add_low
	add eax, [ebx+8]		;length_low
	add ebx, 20				;指向缓冲区中下一个ARDS结构
	cmp edx, eax
;冒泡排序,找出最大,edx寄存器始终是最大的内存容量
	jge .next_ards
	mov edx, eax
	
.next_ards:
	loop .find_max_mem_area
	jmp .mem_get_ok
	
.mem_get_ok:
	;将内存转为byte后存入total_mem_bytes处
	mov [total_mem_bytes], edx		

6.进入保护模式三部曲

;---准备进入保护模式---
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1

	;第一步:打开A20
	in al,0x92
	or al,0000_0010B
	out 0x92, al

	;第二步:加载GDT
	lgdt [gdt_ptr]
	
	;第三步:cr0第3位置1
	mov eax, cr0
	or eax, 0x00000001
	mov cr0, eax

	jmp dword SELECTOR_CODE:p_mode_start	;刷新流水线
	
[bits 32]
;伪指令,接下来将按照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:160], 'P'

	jmp $
  • 1.打开A20地址总线

  • 2.加载GDT到GDTR中

  • 3.将控制寄存器CR0的PE位(Protection Enable)置1

  • 注意:开启保护模式后,将使用32位指令格式,为了避免之前送上流水线的16位格式指令引发错误,需要刷新流水线。

你可能感兴趣的:(操作系统,系统架构,汇编)