天天看點

Linux0.11核心剖析--初始化程式(init)

在核心源代碼的 init/目錄中隻有一個 main.c 檔案。 系統在執行完 boot/目錄中的 head.s 程式後就會将執行權交給 main.c。該程式雖然不長,但卻包括了核心初始化的所有工作。是以在閱讀該程式的代碼時需要參照很多其它程式中的初始化部分。如果能完全了解這裡調用的所有程式,那麼看完這章内容後你應該對linux 核心有了大緻的了解。

從本文開始,我們将接觸大量的 c 程式代碼,是以讀者最好具有一定的 c 語言知識。最好的一本參考書還是 brian w. kernighan 和 dennis m. ritchie 編著的《c 程式設計語言》。在注釋 c 語言程式時,為了與程式中原有的注釋相差別,我們使用'//'作為注釋語句的開始。對于程式中包含的頭檔案( *.h),僅作概要含義的解釋。

1、功能描述

main.c 程式首先利用 setup.s 程式取得的系統參數設定系統的根檔案裝置号以及一些記憶體全局變量。這些記憶體變量指明了主記憶體的開始位址、系統所擁有的記憶體容量和作為高速緩沖區記憶體的末端位址。如果還定義了虛拟盤( ramdisk),則主記憶體将适當減少。整個記憶體的映像示意圖如圖所示:

Linux0.11核心剖析--初始化程式(init)

(系統中記憶體功能劃分示意圖)

圖中,高速緩沖部分還要扣除被顯存和 rom bios 占用的部分。

高速緩沖區是用于磁盤等塊裝置臨時存放資料的地方,以 1k( 1024)位元組為一個資料塊機關。

主記憶體區域的記憶體是由記憶體管理子產品 mm 通過分頁機制進行管理配置設定,以 4k 位元組為一個記憶體頁機關。

核心程式可以自由通路高速緩沖中的資料,但需要通過 mm 才能使用配置設定到的記憶體頁面。然後,核心進行所有方面的硬體初始化工作,包括陷阱門、塊裝置、字元裝置和 tty,包括人工建立第一個任務( task 0)。待所有初始化工作完成就設定中斷允許标志,開啟中斷。在閱讀這些初始化子程式時,最好是跟着被調用的程式深入進去看,如果實在看不下去了,就暫時先放一放,繼續看下一個初始化調用。在有些了解之後再繼續研究沒有看完的地方。在整個核心完成初始化後,核心将執行權切換到了使用者模式,

也即 cpu 從 0 特權級切換到了第 3 特權級。然後系統第一次調用建立程序函數 fork(),建立出一個用于運作

init()的子程序。在該程序(任務)中系統将運作控制台程式。如果控制台環境建立成功,則再生成一個子程序,用于運作 shell

程式/bin/sh。若該子程序退出,父程序傳回,則父程序進入一個死循環内,繼續生成子程序,并在此子程序中再次執行 shell

程式/bin/sh,而父程序則繼續等待。

代碼注釋:

1、cmos 資訊

pc 機的 cmos(complementary metal oxide semiconductor 互補金屬氧化物半導體)記憶體實

際上是由電池供電的 64 或 128 位元組 ram 記憶體塊,是系統時鐘晶片的一部分。有些機器還有更大的記憶體容量。該 64 位元組的 cmos

首先在 ibm pc-xt 機器上用于儲存時鐘和日期資訊。由于這些資訊僅用去 14 位元組,剩餘的位元組就用來存放一些系統配置資料了。 cmos

的位址空間是在基本位址空間之外的。是以其中不包括可執行的代碼。它需要使用在端口70h,71h 使用 in 和 out

指令來通路。為了讀取指定偏移位置的位元組,首先需要使用 out 向端口 70h 發送指定位元組的偏移值,然後使用 in 指令從 71h

端口讀取指定的位元組資訊。這段程式中(行 70)将欲讀取的位元組位址或上了一個 80h 值是沒有必要的。 因為那時的 cmos 記憶體容量還沒有超過

128 位元組,是以或上 80h 的操作是沒有任何作用的。之是以會有這樣的操作是因為當時 linus手頭缺乏有關 cmos 方面的資料, cmos

中時鐘和日期的偏移位址都是他逐漸實驗出來的,也許在他實驗中将偏移位址或上

80h(并且還修改了其它地方)後正好取得了所有正确的結果,是以他的代碼中也就

有了這步不必要的操作。不過從 1.0 版本之後,該操作就被去除了 (可參見 1.0 版核心程式 drivers/block/hd.c第 42 行起的代碼)。下面是 cmos 記憶體資訊的一張簡表。

位址偏移值             内容說明

0x00            目前秒值 (實時鐘)

0x01            報警秒值

0x02            目前分鐘 (實時鐘)

0x03            報警分鐘值

0x04            目前小時值 (實時鐘)

0x05            報警小時值

0x06           一周中的目前天 (實時鐘)

0x07           一月中的當日日期 (實時鐘)

0x08           目前月份 (實時鐘)

0x09           目前年份 (實時鐘)

0x0a rtc          狀态寄存器 a

0x0b rtc          狀态寄存器 b

0x0c rtc          狀态寄存器 c

0x0d rtc          狀态寄存器 d

0x0e post          診斷狀态位元組

0x0f            停機狀态位元組

0x10           磁盤驅動器類型

0x11           保留

0x12           硬碟驅動器類型

0x13           保留

0x14           裝置位元組

0x15           基本記憶體 (低位元組)

0x16           基本記憶體 (高位元組)

0x17           擴充記憶體 (低位元組)

0x18           擴充記憶體 (高位元組)

0x19-0x2d        保留

0x2e           校驗和 (低位元組)

0x2f            校驗和 (高位元組)

0x30           1mb 以上的擴充記憶體 (低位元組)

0x31           1mb 以上的擴充記憶體 (高位元組)

0x32           目前所處世紀值

0x33           資訊标志

0x34-0x3f         保留

2、調用 fork()建立新程序

fork

是一個系統調用函數。 該系統調用複制目前程序,

并在程序表中建立一個與原程序(被稱為父程序)幾乎完全一樣的新表項,并執行同樣的代碼,但該新程序(這裡被稱為子程序)擁有自己的資料空間和環境參數。

在父程序中,調用 fork()傳回的是子程序的程序辨別号 pid,而在子程序中 fork()傳回的将是 0

值,這樣,雖然此時還是在同樣一程式中執行,但已開始叉開,各自執行自己的那段代碼。如果 fork()調用失敗,則會傳回小于 0 的值。如圖所示:

Linux0.11核心剖析--初始化程式(init)

init

程式即是用 fork()調用的傳回值來區分和執行不同的代碼段的。 上面代碼中第 179 和 194

行是子程序的判斷并開始子程序代碼塊的執行(利用 execve()系統調用執行其它程式,這裡執行的是 sh),第 186和 202

行是父程序執行的代碼塊。