前言
今天本來的任務(wù)看書和把之前寫的FragileOS <https://github.com/dejavudwh/FragileOS>
整理一下,但是到現(xiàn)在還在摸魚,書也只看一點。后來整理了一下寫這個系列的思路,原本的目的是對操作系統(tǒng)原理性的學(xué)習(xí)和對之前寫的一個玩具型操作系統(tǒng)的回顧,就是想對操作系統(tǒng)的知識的輪廓能有一個了解,現(xiàn)在想來想減少對之前寫的系統(tǒng)的回顧,畢竟也只有2000多行,但是還是要有對整個思路的展現(xiàn)。然后增加對Linux
0.12源碼的一些學(xué)習(xí)。所以離標題可能比較遠了一點,但是就這樣吧
什么是操作系統(tǒng)
原本這一節(jié)是寫計算機系統(tǒng)和操作系統(tǒng)概述的,但是寫到一半覺得太水就刪了。就總結(jié)幾句,后面用到什么就補什么。計算機系統(tǒng)的概述應(yīng)該屬于計算機組成原理的內(nèi)容,這倆部分也是《操作系統(tǒng):精髓和設(shè)計原理》的第一二章。但是覺得如果對于想學(xué)習(xí)操作系統(tǒng)內(nèi)部的代碼的話,換成匯編的內(nèi)容會更好。
進入正題,操作系統(tǒng)是什么
對于計算機來說最根本的運行方式,就是取指執(zhí)行
對于在屏幕上輸出Hello,World!的過程,首先CPU拿到內(nèi)存中的指令,這些指令是通知CPU把存在某個內(nèi)存中的'H''E''L'等移動到顯存位置,這樣在屏幕上就可以看到這些字符了。這就是計算機最原始的運行方式
而操作系統(tǒng)就是對硬件層面的抽象,讓我們不用在直面硬件,如果想要再次在屏幕輸出字符,只要直接調(diào)用操作系統(tǒng)的write(windows下的好像是這個名
),C語言中的printf下就是一個系統(tǒng)調(diào)用
當(dāng)然操作系統(tǒng)絕對是比想象中的龐大的多,操作系統(tǒng)還對內(nèi)存、終端、磁盤、網(wǎng)絡(luò)和文件等等進行管理,光windows
2000應(yīng)該就有3000多萬行的代碼了。當(dāng)然有簡陋的內(nèi)存、進程管理和文件系統(tǒng)的玩具型內(nèi)核,只要幾千行代碼就可以完成了。
操作系統(tǒng)的啟動
對于X86架構(gòu)的計算機,開機時一共做這幾件事
*
開機時的CS = 0xFFFF, IP = 0x0000
這時候的CPU處理實模式,也就是尋址的方式是CS:IP (實模式和保護模式屬于CPU的工作模式,其中比較大的區(qū)別就是尋址的方式)
*
尋址0xFFFF0
*
檢查硬件設(shè)備,像鍵盤顯示器之類的
*
將磁盤0磁道0扇區(qū)讀入0x7c00處
會從這里讀入512字節(jié),也就是傳說中的引導(dǎo)程序,這里放著計算機執(zhí)行的第一段代碼
*
設(shè)置cs = 0x7c00 ip = 0x0000
FragileOS/boot
這個是我之前寫的FragileOS <https://github.com/dejavudwh/FragileOS>的boot,采用的是Intel匯編格式
主要的邏輯就是:
* 先加載到0x7c00位置
* 進行初始化操作
* 調(diào)用CPU提供的中斷來讀取數(shù)據(jù)
* 讀取完畢后直接跳到內(nèi)核的起始位置,也就是引導(dǎo)結(jié)束了
(部分代碼)
org 0x7c00; ;加載到內(nèi)存0x7c00處 LoadAddr EQU 08000h ;內(nèi)核的內(nèi)存地址 BufferAddr EQU 7E0h
;讀取扇區(qū)的時候進行的緩存 BaseOfStack EQU 07c00h entry: mov ax, 0 ;進行寄存器的初始化操作 mov ss, ax
mov ds, ax mov ax, BufferAddr mov es, ax ;ES:BX 數(shù)據(jù)存儲緩沖區(qū),指示扇區(qū)加載后放置的地址 mov ax, 0
mov ss, ax mov sp, BaseOfStack mov di, ax mov si, ax mov BX, 0 ;ES:BX 數(shù)據(jù)存儲緩沖區(qū)
mov CH, 1 ;CH 用來存儲柱面號 mov DH, 0 ;DH 用來存儲磁頭號 mov CL, 0 ;CL 用來存儲扇區(qū)號 read_floppy:
;每次都把扇區(qū)寫入緩存地址07E00處 cmp byte [load_count], 0
;比較load_count地址處的值,如果=0就跳轉(zhuǎn)到begin_load je begin_load mov bx, 0 inc CL mov AH,
0x02 ;AH = 02 表示要做的是讀盤操作 mov AL, 1 ;AL 表示要練習(xí)讀取幾個扇區(qū) mov DL, 0
;驅(qū)動器編號,一般我們只有一個軟盤驅(qū)動器,所以寫死 int 0x13 ;調(diào)用BIOS中斷實現(xiàn)磁盤讀取功能 jc fin
Linux 0.12/boot
Linux 0.12的boot自然比上面的復(fù)雜的多,Linux采用的boot的匯編是as86格式的,其余的匯編采用的都是AT&T
Linux 0.12下的boot一共有三個文件:
* bootsect
* head
* setup
(代碼太長不全部貼了,有需要的可以私信我)
bootsect
bootsect的主要作用就是把自己移動到0x90000處執(zhí)行,然后再加載setup模塊 (也就是setup.s)
到bootsect的后面,再把system模塊加載到0x10000處,這個也就是內(nèi)核的主要部分
bootsect的開頭是一些常量的定義
SETUPLEN = 4 ! nr of setup-sectors BOOTSEG = 0x07c0 ! original address of
boot-sector INITSEG = 0x9000 ! we move boot here - out of the way SETUPSEG =
0x9020 ! setup starts here SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).
ENDSEG = SYSSEG + SYSSIZE ! where to stop loading
*
_start先設(shè)置好目的地址和源地址
ds:si和es:di
*
然后執(zhí)行rep指令
rep指令是重復(fù)的意思,它以cx寄存器的值為判斷,如果cx的值為0就停止
*
movw指令
開始從[si]處移動cx個字到[di]處,這里也就是一共移動了256個字,512字節(jié)
*
最后跳轉(zhuǎn)到0x9000開始執(zhí)行
_start: mov ax,#BOOTSEG mov ds,ax mov ax,#INITSEG mov es,ax mov cx,#256 sub
si,si sub di,di rep movw jmpi go,INITSEG
* 現(xiàn)在的這些代碼都是在0x90000后的
* 先重新設(shè)置段寄存器和棧指針 go: mov ax,cs mov ds,ax mov es,ax ! put stack at 0x9ff00. mov
ss,ax mov sp,#0xFF00 ! arbitrary value >>512
*
這一部分和我之前的一樣,就是調(diào)用中斷來讀取磁盤內(nèi)容,只是Linux讀取的是在第二扇區(qū)的setup模塊
*
如果失敗就重新設(shè)置驅(qū)動器然后跳回重新讀取
*
成功就跳到ok_load_setup
*
ok_load_setup是設(shè)置根文件系統(tǒng)設(shè)備的,并且讀入SYSTEM模塊 (內(nèi)核的主要部分)到0x10000處,結(jié)尾是跳到SETUP模塊
load_setup: mov dx,#0x0000 ! drive 0, head 0 mov cx,#0x0002 ! sector 2, track
0 mov bx,#0x0200 ! address = 512, in INITSEG mov ax,#0x0200+SETUPLEN ! service
2, nr of sectors int 0x13 ! read it jnc ok_load_setup ! ok - continue mov
dx,#0x0000 mov ax,#0x0000 ! reset the diskette int 0x13 j load_setup
小結(jié)
一個簡單的boot引導(dǎo)程序,顧名思義就是把做一些引導(dǎo)工作的,進行一些初始化設(shè)置再讀入真正的內(nèi)核部分,進入OS。
其實Linux 0.12一個完整的boot應(yīng)該還包括setup.s用來完成OS啟動前最后的設(shè)置 (進入保護模式等)
,head.s則是進入之后的設(shè)置。但是因為這兩部分包含了一些其它重要概念,所以打算再下一篇寫。