首先说一下个人对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 扇区中把它读出来。放到哪里呢?原则上是在实模式的内存布局里任意可用区域即可(0x500~0x7BFF 和 0x7E00~9FBFF),由于loader 加载到内存后不能被覆盖,并且要尽量把 loader 放在低处,多留出一些空间给内核。于是将 loader 的加载地址选为 0x900(个人喜好)。
总结一下,实模式下存在诸多缺陷,为了弥补这些缺陷,在保护模式中引入了段描述符的概念,段描述符中记录了包括段基址、段界限以及特权级在内的诸多信息。段描述符放在全局描述符表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
参考自《操作系统真象还原》