SmlOS二-系统启动前期处理
正式进入内核
前面已经说明了我们的bootloader如何加载磁盘文件,并跳转到内核代码。
也就是会最终跳转到OsHead.nas所生成的代码位置。
我们会用尽量少的汇编语言,但是有些必须要汇编实现的内容,我们都会放到这里,为正式进入c实现的系统内核做准备。
初始化显示相关设置
VBE简要介绍
有一个组织叫 VESA(视频电子标准协会)
他们制定了显卡相关显示相关标准VBE (VESA BIOS EXTENSION),定了一些显卡相关的标准,比如标准应用程序接口,图形接口,显示模式,刷新率等
我们首先要检测机器是否支持VBE,防止在不兼容的机器上运行失败。
当然,一般来讲现代的机器都是支持的。
检测操作:
;确认VBE是否存在
MOV AX,0x9000
MOV ES,AX
MOV DI,0 ; ES:DI 存放VBE信息
MOV AX,0x4f00
INT 0x10
CMP AX,0x004f
JNE scrn320
; 检测VBE版本
MOV AX,[ES:DI+4]
CMP AX,0x0200
JB scrn320 ; if (AX < 0x0200) goto scrn320
我们设定AX,DI,AX 寄存器内容,然后执行 INT 0x10后,如果支持VBE,那么机器会在AX寄存器中写入0x004f
检测如果不支持VBE,使用200*320的屏幕分辨率。
如果支持,就继续检测VBE版本。
因为即使支持VBE,也不一定支持指定对应屏幕模式,所以如果不支持,继续低分辨率模式。
; 取得画面模式信息
MOV CX,VBEMODE
MOV AX,0x4f01
INT 0x10
CMP AX,0x004f
JNE scrn320
这里取得画面模式信息,这里VBEMODE是个宏定义。目前设置为0x105(即1024*768)的分辨率。
这里是常见的VMEMODE值:
VBEMODE | 分辨率 |
---|---|
0x103 | 800*600 |
0x105 | 1024*768 |
0x107 | 1280*1024 |
调用成功后 AX=0x004f
而取得的画面模式也会被写入内存ES:DI开始
的256字节,有以下6个重要信息。
地址 | 说明 | 其他 |
---|---|---|
WORD ES:DI+0x00 | 模式属性 | |
WORD ES:DI+0x12 | x分辨率 | |
WORD ES:DI+0x14 | y分辨率 | |
BYTE ES:DI+0x19 | 颜色数 | 我们是8,代表8位色 |
BYTE ES:DI+0x1b | 颜色的指定方法 | 我们是4,代表使用调色板 |
DWORD ES:DI+0x28 | VARM 显存地址 |
下面来确认其中信息,在这里需要检测颜色数,调色板模式和属性模式。
; 画面模式信息确认
CMP BYTE [ES:DI+0x19],8
JNE scrn320
CMP BYTE [ES:DI+0x1b],4
JNE scrn320
MOV AX,[ES:DI+0x00]
AND AX,0x0080
JZ scrn320 ; 模式属性的bit7是0 所以放弃
如果全部都支持,进行画面模式切换。
;画面模式切换
MOV BX,VBEMODE+0x4000
MOV AX,0x4f02
INT 0x10
MOV BYTE [VMODE],8 ; 记录下画面模式
MOV AX,[ES:DI+0x12]
MOV [SCRNX],AX
MOV AX,[ES:DI+0x14]
MOV [SCRNY],AX
MOV EAX,[ES:DI+0x28]
MOV [VRAM],EAX
JMP keystatus
在这里调用0x10进行切换。
将分辨率信息,画面模式,VRAM(显存地址)地址等信息记录到我们自己设定的内存位置,留做以后备用。
keystatus:
MOV AH,0X02
INT 0X16
MOV [LEDS],AL ;保存键盘led灯状态
我们还额外保存led灯信息,也是备用。
进入保护模式
这可能是这里最重要的知识点了,这里做个简要介绍。
保护模式(Protected Mode)
是从80386开始支持,区别于实模式(Real Mode)
前面说了,实模式最大内存只支持1M,保护模式则支持4G
而且相比于实模式增加了内存保护和虚拟内存分页系统,增加了段间保护机制
为了进入保护模式,首先先关闭所有中断,防止后续操作的时候产生中断导致出错。
关中断核心代码如下:
MOV AL,0XFF
OUT 0X21,AL ;屏蔽主pic 中断
NOP ;连续out,有可能出错,nop一下
OUT 0Xa1,AL ;屏蔽从pic中断
CLI ;禁止CPU级别中断
pic是什么
这里简要介绍下pic相关。
PIC(可编程中断控制器)
PIC分主PIC和从PIC,主PIC与CPU直接相连。
这是从网上找的PIC的图片。
可以看到从PIC连接到主PIC。
主PIC通过数据总线与CPU相连。
对于CPU来说,中断控制器输入外部设备,所以我们使用out指令与之通讯。
当然,PIC是很古老的设备,现在一般都是APIC,不过这些设备都支持向前兼容,所以我们还是可以把APIC当成PIC用。
A20总线
前面说了实模式只能访问1M左右的内存,是因为只有20根地址总线,可以访问的地址是2^20=1M
而我们现在的及训觉一般都有32位地址总线,能支持4G内存空间
我们打开A20总线,这时候可以访问1MB以上地址。
核心操作如下:
CALL waitkbdout ;清空i8042的输入缓冲区
MOV AL,0XD1
OUT 0X64,AL
CALL waitkbdout
MOV AL,0xdf ;开启A20总线
OUT 0X60,AL
CALL waitkbdout
而waitkbdout是是等待A20Gate命令处理被完成的汇编函数
然后进入保护模式,不过进入保护模式之前需要设置好保护模式相关参数,才能进入。
[INSTRSET "i486p"] ;使用486指令集
LGDT [GDTR0] ; 加载GDTR寄存器,临时设置
MOV EAX,CR0
AND EAX,0X7fffffff ;禁止分页模式
OR EAX,0X00000001 ;开启保护模式
MOV CR0,EAX
...
;---------------------------------------------
GDT0:
RESB 8 ;空描述符
DW 0xffff,0x0000,0x9200,0x00cf ;32位可读写数据段描述符 段限长 4G-1,段基址为0
;也就是说该段可寻址0~4G-1 DPL = 0 内核数据段
DW 0xffff,0x0000,0x9a28,0x0047 ;32位可读可执行代码段描述符 段限长512KB,段基址为0x280000
;DPL = 0 内核代码段
DW 0
GDTR0:
DW 8*3-1 ; GDT限长
DD GDT0 ; GDT基址
ALIGNB 16
这里先加载GDTR寄存器,但这里只是临时设置,后面会再次更改,这里只是为了进保护模式的需要。
简要介绍GDT和IDT。
GDT(全局段号记录表)
使用GDT可以支持段的分段机制,这里必须指定段的大小,起始地址,管理属性。
IDT(中断记录表)
中断表用于注册相应中断处理程序的执行地址。
CR0寄存器是CPU的控制寄存器,保护模式的开启就是通过设置CR0寄存器的值来实现的。
更改cr0,就正式进入了保护模式。
JMP protectMode ;切换到保护模式,指令解释变化,必须立马跳转
进入保护模式之后,首先对段寄存器进行初始化设置。
;进入保护模式
protectMode:
MOV AX,1*8 ; 重新设置段寄存器
MOV DS,AX ; DS, ES, FS, GS, SS都指向GDT中的内核数据段描述符
MOV ES,AX
MOV FS,AX
MOV GS,AX
MOV SS,AX
在切换到保护模式之后,必须立马执行跳转,这里为什么要Jmp,是因为切换到保护模式之后,CPU的解释模式发生的变化,开始在执行前一条指令的时候解释下一条或多条指令。
所以模式切换后,必须马上跳转。
复制C语言实现的系统内核代码
然后进行复制操作,复制内核代码(即用之后用C语言实现的系统内核)
这里指定的内核代码段的内存基地址是0x00280000。
首先复制内核512K到基地址,当然512K远远大于内核文件的大小,所以512k已经够用了。
后面一段是将整个在boot.nas时读取的所有扇区数据全部复制到1M(0x00100000)之后的地方去。
MOV ESI,bootpack ;源地址
MOV EDI,BOTPAK ;目的地址
MOV ECX,512*1024/4 ;复制的大小,512K
CALL memcpy
;----------------------------------------------
;将代码复制到0x00100000
MOV ESI,0x7c00
MOV EDI,DSKCAC
MOV ECX,512/4
CALL memcpy
;----------------------------------------------
;将从映像中读入的数据从0x8200处复制到0x00100200处
MOV ESI,DSKCAC0+512
MOV EDI,DSKCAC+512
MOV ECX,0
MOV CL,BYTE [CYLS]
IMUL ECX,512*18*2/4
SUB ECX,512/4
CALL memcpy
...
; 将DS:ESI复制到ES:EDI地址
memcpy:
MOV EAX,[ESI]
ADD ESI,4
MOV [EDI],EAX
ADD EDI,4
SUB ECX,1
JNZ memcpy
RET
一切复制完毕之后,正式启动c实现的内核。
;内核的启动
...
JZ skip ; 没有要传送的数据时
...
skip:
MOV ESP,[EBX+12] ; 栈初始值 ESP = 0x310000
JMP DWORD 2*8:0x0000001b
在用C语言生成的内核文件中,文件头部并非代码段的头部,需要文件头部信息,计算出内核文件代码段的大小,复制到指定的内存区块,然后再跳转到指定的内核基址。
并在最后指定了内核栈,即指定ESP的值,这里是0x310000。
最后在跳转到第二个段,也就是我们在临时gdt中指定的段,并且偏移0x0000001b开始执行。
也就是内存地址0x280000 + 1b的位置。
至此,跳入C语言的内核代码区。
发表回复
要发表评论,您必须先登录。