MBR(Main Boot Record)及保护模式

本文讲解了section和vstart的开发用途,阐述了从实模式到保护模式的内存管理升级,涉及GDT、GDTR和选择子在内存段定义中的关键作用,以及loader在引导内核中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        首先说一下个人对section和vstart的理解。

        关键字section是为了方便开发人员管理代码的手段,并没有对程序中的地址产生任何影响。section中数据的地址依然是相对于整个文件的顺延,仅仅是在逻辑 上让开发人员梳理程序之用。

        拿上图举例,section data在地址上是section code的顺延,即section data的地址为0x10,而section data内的指令地址可以从逻辑上理解为从0开始的偏移地址,这样可以方便程序员去管理,这些指令的实际地址相当于section data的地址+前面指令的偏移地址,如var2 dw 0x99的实际地址是section data的地址0x10 + var1 dd 0x4 指令的偏移地址0x04,即0x14。

        vstart只是是告诉编译器以新的数字作为后面数据的地址的起始值,它本身没改变数据本身在文件中的地址(相对于文件开头的偏移),vstart 只是按照开发人员的意愿安排新的起始地址,不再以文件开头 0 为起始,其地址若 超过文件大小则不会落在文件内,所以是虚拟的。

        用 vstart 的时机是:我预先知道我的程序将来被加载到某地址处。程序只有加载到非 0 地址时 vstart 才是有用的,程序默认起始地址是 0

        书接上回,MBR 受限于 512 字节,在那么小的空间中,没法为内核准备好环境,更没法将内核成 功加载到内存并运行。所以我们要在另一个程序中完成初始化环境及加载内核的任务,这个程序我们称之 为 loader,即加载器。由于 MBR 是占据了硬盘的第 0 扇区,第 1 扇区是空闲的,可以用,但离得太近,所以把 loader 放到第 2 扇区。MBR 从第 2 扇区中把它读出来。放到哪里呢?原则上是在实模式的内存布局里任意可用区域即可(0x5000x7BFF 0x7E009FBFF),由于loader 加载到内存后不能被覆盖,并且要尽量把 loader 放在低处,多留出一些空间给内核。于是将 loader 的加载地址选为 0x900(个人喜好)

        loader 是要经过实模式到保护模式的过渡,并最终在保护模式下加载内核
这里说一下为什么要换成保护模式,也就是实模式的缺点:
(1)实模式下操作系统和用户程序属于同一特权级;
(2)用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址;
(3)用户程序可以自由修改段基址;
以上三个原因属于安全缺陷。
(4)访问超过 64KB 的内存区域时要切换段基址;
(5)一次只能运行一个程序,无法充分利用计算机资源;
(6)共 20 条地址线,最大可用内存为 1MB;
        到了保护模式下,内存段(如数据段、代码段等)不再是简单地用段寄存器加载一下段基址就能用了,段的信息增加了很多,需要提前把段定义好才能使用。全局描述符表(Global Descriptor Table,GDT)是保护模式下内存段的登记表,这是不同于实模式的 显著特征之一。

        一个段描述符只用来定义(描述)一个内存段。代码段要占用一个段描述符、数据段和栈段等,多个内存段也要各自占用一个段描述符,这些描述符放在哪里呢?答案是放在全局描述符表GDT(Global Descriptor Table)。需要注意,GDT中的第0个段描述符不可用。原因是定义在 GDT 中的段描述符是要用选择子来访问的,如果使用的选择子忘记初始化,选择子的值便会是 0,这便会访问到第 0 个段描述符。
全局描述符表位于内存中,需要用专门的寄存器指向它后,CPU 才知道它在哪里。这个专门的寄存器便是 GDTR, 即 GDT Register,专门用来存储 GDT 的内存地址及大小。对此寄存器的访问,只能通过专门的指令:lgdt。

        在实模式下,段中存储的是段基地址,即内存段的起始地址。而在保护模式下时,由于段基址已经存入了段描述符中,所以段寄存器中再存放段基址是没有意义的(因为段描述符里已经有段基址的字段了),在段寄存器中存入的是一个叫作选择子的东西—selector。

        总结一下,实模式下存在诸多缺陷,为了弥补这些缺陷,在保护模式中引入了段描述符的概念,段描述符中记录了包括段基址、段界限以及特权级在内的诸多信息。段描述符放在全局描述符表GDT中,而GDT又存放在内存中,需要由lgdt指令去访问对应GDT的寄存器GDTR,GDTR中存放了GDT的相关信息。在实模式里段中存储的是段基地址,而保护模式中的段基地址已经在段描述符中被描述,所以保护模式中的段中存放的是选择子,可以理解为是一个索引值,用此索引值在段描述符表中索引相应的段描述符,这样,便在段描述符中得到了内存段的起始地址和段界限值等相关信息。

进入保护模式需要三个步骤。(1)打开 A20(2)加载 gd(3)将 cr0 的 pe 位置 1。

运行结果:

boot.inc:

;;--------------------loader and kernel--------------------------------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

;-------------------- gdt描述符属性 --------------------------------
DESC_G_4K equ 1_00000000000000000000000b ;第23位G 表示4K或者1MB位 段界限的单位值 此时为1则为4k 
DESC_D_32 equ 1_0000000000000000000000b  ;第22位D/B位 表示地址值用32位EIP寄存器 操作数与指令码32位
DESC_L    equ 0_000000000000000000000b   ;第21位 设置成0表示不设置成64位代码段 忽略
DESC_AVL  equ 0_00000000000000000000b    ;第20位 是软件可用的 操作系统额外提供的 可不设置

DESC_LIMIT_CODE2  equ  1111_0000000000000000b   ;第16-19位 段界限的最后四位 全部初始化为1 因为最大段界限*粒度必须等于0xffffffff
DESC_LIMIT_DATA2  equ  DESC_LIMIT_CODE2         ;相同的值  数据段与代码段段界限相同
DESC_LIMIT_VIDEO2 equ	0000_0000000000000000b	  ;第16-19位 显存区描述符VIDEO2 书上后面的0少打了一位 这里的全是0为高位 低位即可表示段基址
   
DESC_P            equ 	1_000000000000000b	  ;第15位  P present判断段是否存在于内存  
DESC_DPL_0        equ  00_0000000000000b         ;第13-14位 这两位更是重量级 Privilege Level 0-3
DESC_DPL_1        equ  01_0000000000000b	  ;0为操作系统 权力最高 3为用户段 用于保护
DESC_DPL_2        equ  10_0000000000000b
DESC_DPL_3        equ  11_0000000000000b

DESC_S_sys        equ  0_000000000000b           ;第12位为0 则表示系统段 为1则表示数据段
DESC_S_CODE       equ  1_000000000000b           ;第12位与type字段结合 判断是否为系统段还是数据段
DESC_S_DATA       equ  DESC_S_CODE


DESC_TYPE_CODE    equ  1000_00000000b            ;第9-11位表示该段状态 1000 可执行 不允许可读 已访问位0
;x=1 e=0 w=0 a=0
DESC_TYPE_DATA    equ  0010_00000000b            ;第9-11位type段   0010  可写  
;x=0 e=0 w=1 a=0


;代码段描述符高位4字节初始化 (0x00共8位 <<24 共32位初始化0) 
;4KB为单位 Data段32位操作数 初始化的部分段界限 最高权限操作系统代码段 P存在表示 状态 
DESC_CODE_HIGH4   equ  (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0X00

;数据段描述符高位4字节初始化
DESC_DATA_HIGH4   equ  (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0X00

;显存段描述符高位4字节初始化
DESC_VIDEO_HIGH4   equ (0x00<<24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0X0B  ;

;-------------------- 选择子属性 --------------------------------
;第0-1位 RPL 特权级比较是否允许访问  第2位TI 0表示GDT 1表示LDT    第3-15位索引值
RPL0    equ 00b
RPL1    equ 01b
RPL2    equ 10b
RPL3    equ 11b
TI_GDT  equ 000b
TI_LDT  equ 100b

loader.S:

%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
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_ptr dw GDT_LIMIT
	dd GDT_BASE

loadermsg db 'this vinux belongs to tw'
loader_start:	
	mov sp,LOADER_BASE_ADDR
	mov bp,loadermsg
	mov cx,24
	;mov ax,cs
	;mov es,ax
	mov ax,0x1301
	mov bx,0x001f
	mov dx,0x1800
	int 0x10

;进入保护模式
in al,0x92
or al,00000010b
out 0x92,al

lgdt[gdt_ptr]

mov eax,cr0
or eax,0x00000001
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:160],'P'

mbr.S:

%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, 0x600
mov bx, 0x700
mov cx, 0
mov dx, 0x184f		;0x18=24,0x4f=79
int 0x10

;输出背景色蓝色,前景色青色,且跳动的“Hi Vinux”字符串    1001 0011 0x93
mov byte [gs:0x00],'H'
mov byte [gs:0x01],0x93

mov byte [gs:0x02],'i'
mov byte [gs:0x03],0x93

mov byte [gs:0x04],' '
mov byte [gs:0x05],0x93

mov byte [gs:0x06],'V'
mov byte [gs:0x07],0x93

mov byte [gs:0x08],'i'
mov byte [gs:0x09],0x93

mov byte [gs:0x0A],'n'
mov byte [gs:0x0B],0x93

mov byte [gs:0x0C],'u'
mov byte [gs:0x0D],0x93

mov byte [gs:0x0E],'x'
mov byte [gs:0x0F],0x93

;为rd_disk_m_16函数用寄存器传参
mov eax, LOADER_START_SECTOR
mov bx, LOADER_BASE_ADDR
mov cx, 4 	;待读入的扇区数
call rd_disk_m_16

jmp LOADER_BASE_ADDR

rd_disk_m_16:
mov esi, eax	;备份eax,即LBA扇区号
mov di, cx	;备份cx,即读入的扇区数


;第一步 设置要读取的扇区数
mov dx, 0x1f2
mov al, cl
out dx, al

mov eax, esi

;第二步 将LBA地址存入0x1f3-0x1f6
mov dx,0x1f3
out dx,al

mov cl,8
shr eax,cl
mov dx,0x1f4
out dx,al

shr eax,cl
mov dx,0x1f5
out dx,al

shr eax,cl
and al,0x0f
or al,0xe0
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位和第7位
	cmp al,0x08	;al-0x08,如果al的第3位为0,则产生借位标志
	jnz .not_ready  ;借位标志不为0时执行

;第五步 从0x1f0端口读数据
mov ax,di
mov dx,256
mul dx
mov cx,ax
mov dx,0x1f0

.go_on_read:
	in ax,dx
	mov [bx],ax
	add bx,2
	loop .go_on_read	;与cx相关联,cx--
	ret

jmp $			;stay in here

times 510-($-$$) db 0
db 0x55,0xaa

参考自《操作系统真象还原》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值