007-进入保护模式

       上次讲解到了OS内核的开始以及在进入保护模式之前需要了解一些概念。首先给出这部分内容的完整代码,然后分别来介绍。

BOTPAK	EQU		0x00280000		
DSKCAC	EQU		0x00100000		
DSKCAC0	EQU		0x00008000		

CYLS  EQU 0x0ff0
LEDS  EQU 0x0ff1
VMODE EQU 0x0ff2
SCRNX EQU 0x0ff4
SCRNY EQU 0x0ff6
VRAM  EQU 0x0ff8

	org 0xc200		; 这里的org并不是真的可以决定在内存中的加载位置(由ipl决定),而是为了让编译器可以算出正确的标签所代表的内存位置
	
	mov al, 0x13	; 320×200×8位色
	mov ah, 0x00
	int 0x10		; 0x10号中断,ah=0时为VGA显卡图形模式
	mov byte[VMODE], 8
	mov word[SCRNX], 320
	mov word[SCRNY], 200
	mov dword[VRAM], 0x000a0000
	
	mov ah, 0x02
	int 0x16		; ah=0x02时获取键盘led灯状态,保存在al中
	mov [LEDS], al
	
; PIC关闭一切中断
;	根据AT兼容机的规格,如果要初始化PIC
;	必须在CLI之前进行,否则有时会挂起
;	随后进行PIC的初始化

		MOV		AL,0xff
		OUT		0x21,AL
		NOP						; 如果连续执行OUT指令,有些机种会无法正常运行
		OUT		0xa1,AL

		CLI						; 禁止CPU级别的中断

; 为了让CPU能够访问1MB以上的内存空间,设定A20GATE

		CALL	waitkbdout
		MOV		AL,0xd1
		OUT		0x64,AL
		CALL	waitkbdout
		MOV		AL,0xdf			; enable A20
		OUT		0x60,AL
		CALL	waitkbdout

; 切换到保护模式

[INSTRSET "i486p"]				; 要想使用486指令

		LGDT	[GDTR0]			; 设定临时GDT
		MOV		EAX,CR0
		AND		EAX,0x7fffffff	; 设bit31为0(为了禁止颁)
		OR		EAX,0x00000001	; 设bit0为1(为了切换到保护模式)
		MOV		CR0,EAX
		JMP		pipelineflush
pipelineflush:
		MOV		AX,1*8			;  可读写的段32bit
		MOV		DS,AX
		MOV		ES,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		SS,AX

; bootpack的传送

		MOV		ESI,bootpack	; 传送源
		MOV		EDI,BOTPAK		; 传送目的地
		MOV		ECX,512*1024/4
		CALL	memcpy

; 磁盘数据最终转送到它本来的位置去

; 首先从启动扇区开始

		MOV		ESI,0x7c00		; 传送源
		MOV		EDI,DSKCAC		; 传送目的地
		MOV		ECX,512/4
		CALL	memcpy

; 所有剩下的

		MOV		ESI,DSKCAC0+512	; 传送源
		MOV		EDI,DSKCAC+512	; 传送目的地
		MOV		ECX,0
		MOV		CL,BYTE [CYLS]
		IMUL	ECX,512*18*2/4	; 从柱面数变换为字节数除以4
		SUB		ECX,512/4		; 减去IPL
		CALL	memcpy

; 必须由asmhead来完成的工作,至此全部完毕
;	以后交由bootpack完成

; bootpack的启动

		MOV		EBX,BOTPAK
		MOV		ECX,[EBX+16]
		ADD		ECX,3			; ECX += 3;
		SHR		ECX,2			; ECX /= 4;
		JZ		skip			; 没有要转送的东西时
		MOV		ESI,[EBX+20]	; 转送源
		ADD		ESI,EBX
		MOV		EDI,[EBX+12]	; 转送目的地
		CALL	memcpy
skip:
		MOV		ESP,[EBX+12]	; 栈初始值
		JMP		DWORD 2*8:0x0000001b

waitkbdout:
		IN		 AL,0x64
		AND		 AL,0x02
		IN		 AL,0x60		; 空读(为了清空数据接收缓冲区中的垃圾数据)
		JNZ		waitkbdout		; AND的结果如果不是0,就跳到waitkbdout
		RET

memcpy:
		MOV		EAX,[ESI]
		ADD		ESI,4
		MOV		[EDI],EAX
		ADD		EDI,4
		SUB		ECX,1
		JNZ		memcpy			; 减法运算的结果如果不是0,就转到memcpy
		RET

		ALIGNB	16
GDT0:
		RESB	8				; NULL selector
		DW		0xffff,0x0000,0x9200,0x00cf	; 可以读写的段(segment)32bit
		DW		0xffff,0x0000,0x9a28,0x0047	; 可以执行的段(segment)32bit(bootpack用)

		DW		0
GDTR0:
		DW		8*3-1
		DD		GDT0

		ALIGNB	16
bootpack:

       31-36行做的事情在于,如果在切换模式的(指从实模式切换至保护模式)过程中发生了中断,CPU是不能停下来去处理中断的(因为处理的方式都没有切换过来),所以,在这之前,需要关闭中断。两句out指令用于屏蔽主PIC和从PIC的中断发送,CLI用于停止CPU级别的中断。NOP指令使得CPU空转一个时钟周期,这是为了防止两句OUT连用存在的隐患(例如没有优化到位的竞争冒险)。

       接下来会调用waitkbdout函数,所以先来介绍110-115行的内容,这是在清除缓冲器数据,把键盘缓冲区中的数据转移出来,并且清空,循环读取直至控制器数据为0,跳出函数体。

       40-46行,初始化键盘数据,启动键盘控制电路。向0x60输出0xdf这条指令是为了开启1M以上的内存空间,在x86(以及x86以后)的架构中,计算机刚启动时都必须进入实模式,而为了使用这个模式,就不得不先把1MB以外的内存屏蔽掉,使得此时的状态能够模拟出8086时的工作状态,换句话说就是为了向下兼容,在软件级别上使得其与8086工作模式相同。而这条out指令将会使得A20GATE信号箱转为1状态(也就是禁止其抑制状态),也就开启了大内存的支持。

       50行准备进入保护模式,之后的汇编指令将会被翻译成32位机器码。而由于保护模式和实模式寻址方式不同,要想使得段寄存器有效,就必须使用GDT,52行这里暂时制定一个GDT,实际的GDT将会在以后去完成。设置了CR0之后,便正式进入保护模式。57行这个突兀的jmp指令的目的在于,由于保护模式的机器语言会采用管道机制,也就是会预先解释下面的指令,但是,由于刚刚转换成保护模式,下面的一条语句还是按照以前的方式来解释的,直接去执行会出错,所以专门加一条jmp指令是为了容错,让计算机重新解释一遍后面的语句,防止奇怪的bug。而58到64行就是用来初始化这些32位寄存器的。

       由于接下来会用到内存传送函数,因此先来解释117-124行。这个函数的目的是以4字节为单位进行内存数据的复制,将以ESI数据为首地址的内存数据传送到以EDI数据为首地址的内存中,传送数据的大小是ECX中的值。

       66-71行就是借助了内存复制函数,把将来将要写的操作系统内核的部分加载到内存(在合川先生的教程中把它明明为bootpack,也就是用于启动的程序包,但是由于我们将要写的操作系统比较小,还并不至于用一个专门的bootloader来启动,所以把bootpack和OS写在一起,所以这里的bootpack就可以理解成操作系统内核程序本身,在后面的程序中将不再使用bootpack这个名字,而是使用OSMain这个名字,特此说明)。到90行为止,都是在加载数据。之前给启动区空出来的内存位置,我们把启动区也加载进去,而之前ipl只加载了10个扇区,这里内存够用了,就把剩下的加载完毕。

       97-105行来解析系统文件头,这里要具体说明为什么如此编写有点复杂。简单来说,我们在完成这些设定以后,就想用C语言来作为主要语言进行操作系统的开发,但C语言编译后不像汇编语言,你可以直接指定每一段函数的加载位置,C语言编译后函数的加载位置是由编译器临时决定的,再加之不同编译器以及编译参数对编译内容进行的优化也不尽相同,所以,我们无法指定C语言文件编译后各函数之间的执行顺序。而这里就是在对文件头进行分析,找出合适的执行切入点,我们才可以把我们用C语言的入口函数加载到需要的地方。

       106-108行就跳转到刚才已经加载合适的程序部分,也就是说,接下来就该执行我们将有C语言编写的入口函数了(我个人将其命名为OSMain)。关于当前只是进行大概解释的部分,会在后续的文章中详细介绍。

       当前的程序已经成功的把裸机通过软驱启动运行了ipl加载了程序并且切换至了32位保护模式,取得了4G内存的寻址空间,最后还提供了接口使得我们可以将程序的主逻辑转交至C语言,增加开发效率。下次将会开始使用C语言进行主要开发语言,将会介绍OSMain函数的写法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章