《操作系統真象還原》讀書筆記 第2章

0x1 計算機啓動過程

爲什麼程序要載入內存
CPU的硬件電路被設計成只能運行處於內存中的程序,是因爲內存內存運速快,且容量大。其次,操作系統可以存儲在軟盤上,也可以存儲在硬盤上,甚至U盤
什麼是載入內存
所謂載入內存,大致上分爲兩部分:
1)程序被加載器(軟件或硬件加載到某個區域)。
2)CPU的cs:ip寄存器被指向這個程序的起始地址。
操作系統在加載程序時,是需要某個加載器來將用戶程序存儲到內存中的。加載器本質上就是一堆函數組成的模塊。
從按下主機電源鍵後,第一個運行的軟件是BIOS。

0x2 軟件接力第一棒,BIOS

BIOS全稱叫Base Input & Output System,即基本輸入輸出系統。

0x2.1 實模式下的1MB內存分部

Intel8086有20條地址線,故其可以訪問1MB的內存空間,範圍是0x00000到0xFFFFF。其中0x00000~0x9FFFF的640K是對應到DRAM也就是我們的物理內存,而0xF0000到0xFFFFF這64K內存是ROM。這裏面存儲的就是我們的BIOS代碼。BIOS的工作主要是檢測、初始化硬件。
具體初始化的方法是硬件自己提供了一些初始化的功能調用,BIOS直接調用即可。除此之外BIOS還創建了中斷向量表,這樣就可以通過“int中斷號”來實現相關的硬件調用。BIOS建立的這些功能就是對硬件的IO操作,也就是輸入輸出,但由於就64KB大小的空間,不可能把所有硬件IO操作實現的面面俱到,BIOS只挑一些重要的、保證計算機能運行的那些硬件的基本IO操作,就行了。這就是BIOS被稱爲基本輸入輸出系統的原因。

0x2.2 CPU眼中,我們插在主板上的物理內存在它眼裏並不是全部的內存

地址總線的寬度決定了可以訪問的內存空間大小,如16位的機器地址總線爲20位,其地址範圍是1MB。32位地址總線寬度是32位,其地址範圍是4GB。但是以上範圍是地址總線可以觸及到的邊界,是指計算機在尋址上可以達到的疆域。雖然地址總線有如此大的範圍可以訪問,但是並不是說計算機尋址範圍必須是物理內存(內存條)。
在計算機中,並不是只有插在主板上的內存條需要通過地址總線訪問,還有一些外設同樣需要地址總線訪問。若把全部地址總線都指向物理內存,那其他設備就無法訪問了。所以計算機設計者只好在地址總線上提前預留出一些地址空間給外設使用。留夠了後,剩餘的地址總線再指向DRAM,也就是插在主板上的內存條。
物理內存多大都沒有用,主要是看地址總線的寬度。還要看地址總線的設計是不是全部用於訪問DRAM。所以說,地址總線是決定我們訪問哪裏、訪問什麼,以及訪問範圍的關鍵。我們平時用的32位,上面的內存條並不是全部都用到了,按理說內存條大小超過4GB就沒有意義了,超過了地址總線的部分就是浪費。這就是爲什麼安裝了4GB內存,電腦卻只顯示3.8GB左右的原因。
總之,表示地址的那串數字是地址總線的輸入,相當於器參數,和內存條沒關係。CPU能訪問這塊地址是地址總線給做的映射,相當於給改地址分配了一個存儲單元,而存儲單元要麼落在某個rom中,要麼落在某個外設內存中,要麼落在物理內存條上。可以想象成CPU給地址總線提供數字,在地質總線看來,這串數字就是地址。地址分配電路根據此地址的範圍,決定在那個存儲介質分配一個存儲單元,最後將此地址與存儲儲單元對應起來。

0x2.3 BIOS是如何甦醒的

BIOS是計算機上的第一個軟件,他不可能自己加載自己。它是通過硬件ROM(只讀存儲器)加載的。
ROM只讀存儲是不可擦除的,他不像動態隨機訪問存儲器DRAM那樣,掉電後,裏面的數據就會丟失。這種存儲介質是用來存儲一成不變的數據的,當數據寫進去後便無法更改。
BIOS代碼所作的工作也一成不變的,在正常情況下,其本身是不需要修改的,平時聽說的那些主板壞了要刷新BIOS的情況屬於例外。因爲BIOS的不變性,所以BIOS被寫入ROM硬件中。ROM也是內存,也需要地址總線進行訪問。此ROM被映射在地段1MB內存的頂部,即地址0xF0000~0xFFFFF處。只要訪問此處的地址便是訪問了BIOS,這個映射是硬件完成的。
BIOS本身是個程序,程序需要執行,就需要一個入口地址,此入口地址是0xFFFF0。
在開機的接電的一瞬間,CPU的cs:ip寄存器被強制初始化位0xF000:0xFFF0。由於開機的時候處於實模式,所以組合成的地址爲0xFFFF0,也就是BIOS的入口。可以理解爲,CPU一加電就指向了BIOS程序的入口點。
但是我們發現0xFFFF0到0xFFFFF之間只有16個字節,根本存儲不了多少數據,所以此處代碼只能是個跳轉指令才能解釋通。

0x2.4 爲什麼是0x7c00

BIOS最後一項工作校驗啓動盤中位於0盤0道1扇區的內容。而該扇區的內容就是MBR。此扇區的魔數0x55和0xaa,BIOS便認爲此扇區中確實存在可執行的程序,便加載到物理地址0x7c00,隨後跳轉到此地執行。BIOS跳轉到0x7c00是用jmp 0:0x7c00實現的,這是jmp指令絕對遠跳轉移用法,段寄存器cs會被替換,這裏的段基址是0,即cs由之前的0xf000變成了0。
如果此扇區最後2個不是0x55和0xaa,即使裏面有可執行代碼也於事無補。
爲什麼是0盤0道1扇區的內容
作者推測是爲了快速找到MBR內容,方便快速加載程序到指定的0x7c00。
爲什麼是物理地址0x7c00不是其他地址或好看的地址
0x7c00作用就是魔數,最早出現在IBM PC 5150的ROM BIOS INT 19中斷處理程序中。通電開機後,調用BIOS中斷0x19,即call int 19h。在此中斷處理函數中,BIOS要檢測這臺計算機有多少硬盤或軟盤,如果檢測到了任何可用磁盤,BIOS就把它第一扇區加載到0x7c00。
之所以x86手冊找不到,是因爲它屬於BIOS中的規範。所以肯定是由IBM PC 5150 BIOS開發團隊規定的數。
MBR不是隨便放在哪裏都行的,首先不能覆蓋已有的數據,其次,不能過早地被其他數據覆蓋。通常,MBR的任務是加載某個程序(這個程序一般是內核加載器,很少有直接加載內核的)到指定位置,並將控制權交給他。所謂移交控制權就是jmp過去而已。之後MBR就沒有用了,被覆蓋也沒關係。這裏所說的過早覆蓋,是指不能讓mbr破壞自己,比如被加載的程序,如內核加載器,其放置的位置若是MBR自己所在的範圍,這就破壞了自己。
8086CPU要求物理地址0x0~0x3FF存放中斷向量表,所以此處不能動了。
按DOS 1.0要求的最小內存32KB 來說,MBR希望有儘可能多的預留空間,這樣也是保全自己的做法,免得被過早覆蓋。所以MBR只能放在32KB的末尾。
MBR本身也是程序,是程序就要用到棧,棧也是在內存中的,MBR雖然本身只有512字節,但還要爲其所用的棧分配點空間,所以其實際作用的內存空間要大於512字節,估計1KB內存就夠用了。
結合以上三點,選擇32KB最後1KB最爲合適。32KB換算爲16進製爲0x8000,減去1KB(0x400),等於0x7c00。這就是0x7c00的由來。

0x2.5 MBR實現開始

首先我們要明確MBR的大小是512字節,最後兩個字節是磁盤魔數0x55和0xaa,即510和511字節處。按照起始偏移爲0開始計算。bochs模擬的是x86平臺,所以是小端字節序,故最後兩個字節內容是0xaa55

0x2.5.1 神奇好用的 $ 和$ $,令人迷惑的section

$和 $ $是編譯器NASM預留的關鍵子,用來表示當前行和本section的地址,起到了標號的作用,它是NASM提供的,並不是CPU原生支持的,相當於僞指令一樣,對於CPU來說是假的。
僞指令這是相對於CPU可識別的指令來說的,它(僞指令)知識編譯器定義的,CPU中並不存在這個指令,硬讓CPU執行僞指令,CPU會拋出“UD(未定義的操作碼)”異常。僞指令是編譯器爲了開發人員寫代碼方便而提供的一些符號,這些符號在編譯時,會由編譯器轉換成CPU可識別的東西,如指令地址等。
$屬於“隱式地”藏在本行代碼前的標號,也就是編譯器給當前安排的地址,看不到卻又無處不在 $在每行都有。或者說只有“顯示地” 用了 $的地方,nasm編譯器纔會將該地址公佈出來。

code_start:
	jmp $
//等同於jmp code_start

$ $指代本section的起始地址,此地址同樣是編譯器安排的地址,默認情況下,它們的值是相對於文本文件開頭的偏移量。至於實際安排的是多少,還要看程序員是否在section中添加了vstart。這個關鍵字可以影響編譯器安排地址的行爲,如果該section用了vstart=xxxx修飾, $ $的值則是此section的虛擬起始地址xxxx。 $的值是以xxxx爲起始地址的順延。如果用了vstart關鍵字,想獲得本section載文件中的真實偏移量(真實地址)該怎麼做?nasm提供了這個方法。

section.節名.start

如果沒有定義section,nasm默認全部代碼同爲一個section,起始地址爲0。
接下來複制下作者的代碼

;主引導程序
;-----------------------------------------------------
SECTION MBR vstart=0x7c00
	mov ax,cs				;因爲BIOS執行完畢後cs:ip爲0x0:0x7c00,所以用cs初始化各寄存器
	mov ds,ax				;ds、es、ss、fs不能給立即數初始化,需要用ax寄存器初始化
	mov es,ax
	mov ss,ax
	mov fs,ax
	mov sp,0x7c00			;初始化堆棧指針,因爲目前0x7c00以下的內存暫時可用
;清屏利用0x06號功能,上卷全部行,則可清屏
;-----------------------------------------------------
;INT 0x10	功能號:0x06		功能描述:上卷窗口
;-----------------------------------------------------
;輸入:
;AH	功能號= 0x06
;AL = 上卷行數(如果爲0,表示全部)
;BH = 上卷行屬性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;無返回值:
	mov	ax,0x600			;上卷行數:全部	功能號:06
	mov bx,0x700			;上卷屬性
	mov cx,0				;左上角:(00)
	mov dx,0x184f			;右下角:(80,25)
							;VGA文本模式中,一行只能容納80字節,共25;下標從0開始,所以0x18=240x4f=79
	int 0x10				;int 0x10

;;;;;;;;	下面這三行代碼獲取光標位置		;;;;;;;;
;.get_cursor獲取當前光標位置,在光標位置處打印字符。
	mov ah,3				;輸入:3號子功能是獲取光標位置,需要存入ah寄存器
	mov bh,0				;bh寄存器存儲的是待獲取光標的頁號
	
	int 0x10				;輸出:ch=光標開始行,cl=光標結束行
							;dh=光標所在行號,dl=光標所在列號
;;;;;;;;	獲取光標位置結束		;;;;;;;;

;;;;;;;;	打印字符串		;;;;;;;;
	;還是用10h中斷,不過這次調用13號子功能打印字符串
	mov	ax,message
	mov bp,ax				;es:bp爲串首地址,es此時同cs一致,
							;開頭時已經爲sreg初始化
	
	;光標位置要用到dx寄存器中內容,cx中光標位置可忽略
	mov cx,5				;cx爲串長度,不包括結束符0的字符個數
	mov ax,0x1301			;子功能號13時顯示字符及屬性,要存入ah寄存器,
							;al設置寫字符串方式al=01:顯示字符串,光標跟隨移動
	mov bx,0x2				;bh存儲要顯示的頁號,此處時第0頁,
							;bl中式字符屬性,屬性黑底綠字(bl = 02h)
	int 0x10				;執行BIOS 0x10 號中斷
;;;;;;;;	打字字符串結束		;;;;;;;;

	jmp $					;使用程序懸停在此
	
	message db "1 MBR"
	times 510-($-$$) db 0
	db 0x55,0xaa

將上述代碼進行編譯nasm -o mbr.bin mbr.S,編譯的文件大小是512字節
在這裏插入圖片描述
接下來根據Linux的dd指令,將我們編譯好的二進制文件寫入上一章創建的空引導磁盤文件中dd if=/root/mbr.bin of=/root/Downloads/bochs/hd60M.img bs=512 count=1 conv=notrunc
在這裏插入圖片描述
接下來啓動bochs驗證程序是否能夠輸出我們想要的結果
在這裏插入圖片描述

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