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函數的寫法。

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