天天看點

Linux0.11啟動過程

1.         從系統加電起所執行程式的順序為:

ROM BIOS bootsect.S setup.S head.S main.c

2.         ROM BIOS當PC機加電後,80x86結構的CPU将自動進入實模式,并從位址0xFFFF0開始自動執行某些系統的檢測。并在實體位址0處開始初始化中斷向量。此後,他将可啟動裝置的第一個扇區(磁盤引導扇區512位元組)讀入記憶體絕對位址0x7C00處,并跳到這個地方去執行(執行bootsect.S處指令)。

3.         bootsect.S執行期間,他會将自己移動到記憶體絕對位址0x90000開始處并繼續執行。該程式的主要作用是首先把從磁盤第2個扇區開始的4個扇區的setup子產品(有setup.S編譯而成)加載到記憶體緊接着bootsect後面位置處(0x90200),然後利用BIOS中斷0x13取磁盤參數表中目前啟動引導盤的參數,接着在螢幕上顯示“Loading system…”字元串。再者把磁盤上setup子產品後面的system子產品加載到記憶體0x10000開始的地方。随後确定根文檔系統的裝置号,若沒有指定,則根據所儲存的引導盤的每磁道扇區數判斷出盤的類型和種類(是1.44MB A盤嗎?)并儲存其裝置号于root_dev(引導塊的508位址處),最後長跳轉到setup程式的開始處(0x90200)執行setup程式。

4.         setup.S是個作業系統加載程式,他的主要作用是利用ROM BIOS中斷讀取機器系統資料,并将這些資料儲存到0x90000開始的位置(覆寫掉了bootsect程式所在的地方)。這些參數将被核心中相關程式使用,例如字元裝置和驅動程式等。然後setup程式将system子產品從0x10000~0x8ffff(當時認為核心系統子產品system的長度不會超過此值:512KB)整個塊向下移動到記憶體絕對位址0x00000處。接下來加載中斷描述符表寄存器(idtr)和全局描述符表寄存器(gdtr),開啟A20位址線,重新配置兩個中斷控制晶片9259A,将硬體中斷号重新配置為0x20~0x2f。最後配置CPU的控制寄存器CR0(也稱機器狀态字),進而進入32位保護模式運作,并跳轉到位于system子產品最前面部分的head.S程式繼續運作。

5.         head.S程式在被編譯生成目标文檔後會和核心其他程式一起被連結成system子產品,位于system子產品的最前面開始部分,這也就是為什麼稱其為頭部(head)程式的原因。System子產品将被放置在磁盤上setup子產品之後開始的扇區中,即從磁盤上第6個扇區開始放置。這個程式的功能比較單一。首先是加載各個資料段寄存器,重新配置中斷描述符表idt,共256項,并使各個表項均指向一個隻報錯誤的啞中斷子程式ignore_int。(中斷描述符表中每個描述符項也占8位元組)。在配置好中斷描述符表之後,本程式又重新配置了全局段描述符表gdt。接着配置管理記憶體的分頁處理機制,将頁目錄表放在絕對實體位址0開始處(也是本程式所處的實體記憶體位置,是以這段程式将被覆寫),緊随後面放置共可尋址16MB記憶體的4個頁表,并分别配置他們的表項。最後head.S程式利用傳回指令将預先放置在堆棧中的/init/main.c程式的入口位址彈出,去運作main()程式。

6.         核心中的main()函數的執行過程

(1)    儲存根裝置号ROOT_DEV;高速緩存末端位址buffer_memory_end;機器記憶體數mem_end;主記憶體開始位址main_memory_start。

(2)    假如在Makefile文檔中定義了記憶體虛拟盤符号RAMDISK,則初始化虛拟盤,此時記憶體将減小。

(3)    初始化虛拟盤rd_init()

a.   首先将MAJOR_NR設定為1,然後再包含頭文檔blk.h,這樣能夠用來确定裝置操作宏對應的函數。

b. 将塊裝置結構的請求操作指針進行指派blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST。宏DEVICE_REQUEST對應的函數是由上步通過MAJOR_NR值在blk.h頭文檔中進行确定的。是do_rd_request。

c.   初始化虛拟盤在實體記憶體中的起始位址,占用位元組長度,并對整個虛拟盤區清0,最後傳回盤區長度。

(4)    mem_init()主記憶體初始化

a.   該函數對1MB以上記憶體區域以頁面為機關進行管理前的初始化配置工作。一個頁面長度為4K,該函數把1MB以上任何實體記憶體劃分成一個個頁面,并使用頁面映射位元組數組mem_map[]來管理任何這些頁面。每當一個實體記憶體頁面被占用時,就把mem_map[]中對應的位元組值增1,若位元組值為0,則表示對應頁面空閑。若位元組值大于或等于1,則表示對應頁面被占用或被不同程式共享占用。(linux0.11核心最多能管理16MB的實體記憶體,大于16MB的記憶體将棄置不用)。

(5)    trap_init()陷阱門(硬體中斷向量)初始化。

a.   中斷描述符表idt的位置實在head.S中配置的,他被配置在記憶體頁目錄(頁目錄從0x0000開始)和也表之後,全局描述表之前。

b. 首先為Intel CPU保留的中斷号int0~16配置中斷門或陷阱門,并将對應的中斷處理程式位址添加到其中。

c.   将int17~48的中斷門或陷阱門先均配置為reserved,以後各硬體初始化時會重新配置自己的陷阱門。

d. 配置協處理器中斷0x2d陷阱門描述符,并允許其産生中斷請求,配置并口中斷描述符。

e.   當進入Intel CPU保留的中斷處理程式時,該中斷處理程式首先儲存目前CPU寄存器狀态,并在最後恢複寄存器狀态。大部分的CPU保留中斷處理程式都是列印出一些狀态資訊後傳回,隻有少數幾個進行了特别處理(例如頁異常)。

(6)    blk_dev_init()塊裝置初始化。

a.   該函數用來初始化請求數組request[],将任何請求項置為空閑項。

b. 任何一個塊裝置都是通過資料緩沖區進行操作的。每個塊裝置都有自己的裝置号,通過request.dev的值決定确定目前請求屬于哪一個塊裝置。

(7)    chr_dev_init()字元裝置初始化。(這是個空函數)

(8)    tty_init()初始化序列槽終端和控制台終端。

a.   調用rs_init()配置兩個序列槽的中斷門描述符。序列槽1使用的中斷是int 0x24,中斷處理過程指針是rs1_interrupt。序列槽2使用的中斷是int 0x23,中斷處理過程指針是rs2_interrupt,然後在對序列槽1和序列槽2進行初始化。最後允許主8259A響應IRQ3和IRQ4的中斷請求(序列槽中斷請求)。

b. 調用con_init()初始化控制台終端。該函數首先根據setup.S程式取得的系統硬體參數初始化配置幾個本函數專用的靜态全局變量,然後根據顯示卡模式(單色還是彩色顯示)和顯示卡類型(EGA/VGA還是CGA)分别配置顯示記憶體的起始位置連同顯示索引寄存器和顯示數值寄存器端口号。最後配置鍵盤中斷0x21陷阱門描述符,keyboard_interrupt是鍵盤中斷處理過程位址,取消8259A中對鍵盤中斷的屏蔽,允許鍵盤發出IRQ1請求,最後複位鍵盤控制器以允許鍵盤開始正常工作。

(9)    time_init()配置開機啟動時間

a.   該函數首先讀取CMOS實時時鐘資訊作為開機時間,并計算出從1970年1月1日0時起到開機當日經過的秒數,儲存至全局變量startup_time中。

(10)sched_init()排程程式初始化(加載任務0的tr和ldtr)。

a.   在全局描述符表中配置初始任務(任務0)的任務狀态段描述符LDT和局部資料表描述符TSS0,全局描述符gdt是head.S中配置的。

b. 初始化指向每一個任務描述符資料結構(程序描述符)的指針數組,使其都為NULL,數組0元素指向任務0的程序描述符,這個描述符定義在核心位址空間中,而其後任何程序描述符都在新配置設定的一頁記憶體的起始位址處。這頁記憶體的最後的最後剩餘部分用作該程序的核心棧。(使用者棧是在該程序的線性位址空間頂端在使用時動态進行配置設定的)。

c.   然後在将全局描述符任務0以後的描述符全初始化為0。

d. 複位NT标志,當NT置位時,那麼目前中斷任務執行iret指令時就會引起任務轉換。

e.   将任務0的TSS段選擇符加載進寄存器tr,将局部描述符表段選擇符加載到局部描述符表寄存器ldtr中。

f.   初始化8253定時器通道0選擇工作方式3,二進制計數方式。通道0的輸出引腳接在中斷控制主晶片的IRQ0上,他每10毫秒發出一個IRQ0請求。

g. 配置時鐘中斷處理程式句柄(配置時鐘中斷門)在0x20上,修改中斷控制器屏蔽碼,允許時鐘中斷。

h. 配置系統調用中斷門,在0x80上,這個中斷能夠被任何程式所執行,用作系統調用,系統調用中斷處理程式system_call()。

(11)buffer_init()緩沖區管理初始化,建立記憶體連結清單等。

a.   确定緩沖區在記憶體中的起始位置和結束位置。

b. 初始化這段緩沖區,建立空閑緩沖塊循環連結清單,并擷取系統中系統中緩沖塊數目,初始化每一個緩沖頭結構并将其連結位循環連結清單。

c.   初始化核心中緩沖塊散列隊列(哈希表)散列數組,置數組中任何指針位NULL。

(12)hd_init()硬碟初始化

a.   首先将MAJOR_NR設定為3,然後包含頭文檔blk.h,這樣就能夠用來确定裝置操作宏對應的函數。

b. 将塊裝置結構的請求操作指針進行指派,blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST,宏DEVICE_REQUEST對應的函數是由上一步通過MAJOR_NR值在blk.h頭文檔中進行确定的,是do_hd_request()。

c.   配置硬碟中斷描述符,hd_interrupt是其中斷處理過程,修改中斷控制器屏蔽碼允許硬碟控制器發送中斷請求信号。

(13)floppy_init()軟驅初始化

a.   首先設定MAJOR_NR為2,然後包含頭文檔blk.h,這樣就能夠用來确定裝置操作宏對應的函數。

b. 将塊裝置結構的請求操作指針進行指派,blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST,宏DEVICE_REQUEST對應的函數是由上一步通過MAJOR_NR值在blk.h頭文檔中進行确定的,是do_fd_request()。

c.   配置硬碟中斷描述符,修改中斷控制器屏蔽碼允許軟碟控制器發送中斷請求信号。

(14)sti()任何初試化工作都作完了,開啟中斷。

(15)move_to_user_mode()移到使用者模式下執行,該函數利用iret指令實作從核心模式移動到初始任務0中去執行。

(16)調用fork()建立出子程序(任務1),父程序(任務0)則循環執行pause()函數。

a.   pause()函數在前面通過static inline _syscall0(int, pause)進行定義。

b. pause()将目前任務轉換為可中斷等待狀态,然後執行排程函數schedule(),排程函數隻要發現系統中沒有其他任務能夠運作時,就會轉換到任務0,而不依賴于任務0的狀态。

(17)任務1執行init()函數

a.   調用setup(),這個函數隻能被調用一次,再調用則會直接傳回-1。

b. 假如已在include/linux/config.h配置文檔中定義了符号常數HD_TYPE,則取定義好的參數作為硬碟資訊數組hd_info[]中的資料,否則就需要讀取boot/setup.S程式存放在記憶體0x90080處開始硬碟參數表來初始化hd_info[]結構數組,并确定含有的硬碟個數存放在NR_HD中。

c.   配置硬碟分區結構數組hd[],該數組的項0和項5分别表示兩個硬碟的整體參數,而項1-4和6-9分别表示硬碟的4個分區的參數。

d. 調用rd_load()嘗試建立并加載虛拟盤,假如目前根裝置是軟碟并且ramdisk的長度不為0則加載虛拟盤,并将根裝置号修改成虛拟盤裝置号。

e.   調用mount_root()安裝根文檔系統,這個函數首先初始化文檔表數組(共64項,即系統同時隻能打開64個文檔)和終極塊表,将文檔結構中的引用計數配置為0(表示空閑),并把終極塊表中各項結構的裝置字段初始化為0(也表示空閑)。從根裝置上讀取文檔系統終極塊,并取得文檔系統根i節點,對終極塊和i節點進行配置,配置該終極塊的被安裝文檔系統i節點和被安裝到i節點字段為該i節點。再配置目前工作目錄和根目錄i節點,此時目前程序是1号程序(init程序),然後對根文檔系統資源作統計工作,然後顯示出來。

f.   以讀寫通路方式打開裝置/dev/tty0,他對應終端控制台(指的是鍵盤和顯示器,輸入是鍵盤,輸出是顯示器)。由于這是第一次打開文檔操作,是以産生的文檔句柄号肯定是0,該句柄是UNIX類作業系統預設的控制台标準輸入句柄stdin,再把他複制到标準輸出句柄stdout和标準錯誤輸出句柄stderr。

g. 建立一個子程序(任務2),打開/etc/rc文檔,并以他作為标準輸入,然後調用execve()執行/bin/sh程式,由于/etc/rc文檔是标準輸入,這樣shell程式/bin/sh就能夠運作rc文檔中配置的指令了,由于這裡sh的運作方式是非互動方式的,是以再執行完rc文檔後就會立即退出,程序2也随之結束。

h. 父程序再等待程序2結束後,就進入無限循環,首先再建立一個子程序,對于所建立的子程序将關閉任何以前還遺留的句柄(stdin,stdout,stderr),新建立一個會話并配置程序組号。重新打開/dev/tty0作為stdin,并複制到stdout和stderr,再次執行系統解釋程式/bin/sh。父程序則進入無限循環,并調用wait()等待。

i.    現在整個啟動初始化過程結束并運作進入shell和使用者進行互動界面。

繼續閱讀