天天看點

Linux0.11核心剖析--核心體系結構

一個完整可用的作業系統主要由 4 部分組成:硬體、作業系統核心、作業系統服務和使用者應用程式,如下圖所示:

Linux0.11核心剖析--核心體系結構

使用者應用程式是指那些字處理程式、 internet 浏覽器程式或使用者自行編制的各種應用程式;

作業系統服務程式是指那些向使用者所提供的服務被看作是作業系統的部分功能的程式。

在 linux 作業系統上,這些程式包括 x 視窗系統、 shell 指令解釋系統以及那些核心程式設計接口等系統程式;作業系統核心程式即是本書所感興趣的部分,它主要用于對硬體資源的抽象和通路排程。

linux 核心的主要用途就是為了與計算機硬體進行互動,實作對硬體部件的程式設計控制和接口操作,排程對硬體資源的通路,并為計算機上的使用者程式提供一個進階的執行環境和對硬體的虛拟接口。在本文内容中,我們首先基于 linux 0.11 版的核心源代碼,簡明地描述 linux 核心的基本體系結構、主要構成子產品。然後對源代碼中出現的幾個重要資料結構進行說明。最後描述了建構 linux 0.11 核心編譯實驗環境的方法。

目前,作業系統核心的結構模式主要可分為整體式的單核心模式和層次式的微核心模式。而 linux 0.11 核心,則是采用了單核心模式。

單核心模式的主要優點是核心代碼結構緊湊、執行速度快,不足之處主要是層次結構性不強。

單核心模式的系統中,作業系統所提供服務的流程為:應用主程式使用指定的參數值執行系統調用指令(int x80),使 cpu 從使用者态( user

mode)切換到核心态( kernel

model),然後作業系統根據具體的參數值調用特定的系統調用服務程式,而這些服務程式則根據需要再底層的一些支援函數以完成特定的功能。在完成了應用

程式所要求的服務後,作業系統又從核心态切換回使用者态,傳回到應用程式中繼續執行後面的指令。

是以概要地講,單核心模式的核心也可粗略地分為三個層次:調用服務的主程式層、執行系統調用的服務層和支援系統調用的底層函數。如下圖所示:

單核心模式的簡單結構模型

Linux0.11核心剖析--核心體系結構

linux 核心主要由 5 個子產品構成,它們分别是:程序排程子產品、記憶體管理子產品、檔案系統子產品、程序間通信子產品和網絡接口子產品。

程序排程子產品用來負責控制程序對 cpu 資源的使用。所采取的排程政策是各程序能夠公平合理地通路 cpu,同時保證核心能及時地執行硬體操作。

記憶體管理子產品用于確定所有程序能夠安全地共享機器主記憶體區,同時,記憶體管理子產品還支援虛拟記憶體管理方式,使得 linux 支援程序使用比實際記憶體空間更多大的記憶體容量。并可以利用檔案系統把暫時不用的記憶體資料塊會被交換到外部儲存設備上去,當需要時再交換回來。

檔案系統子產品用于支援對外部裝置的驅動和存儲。虛拟檔案系統子產品通過向所有的外部儲存設備提供一個通用的檔案接口,隐藏了各種硬體裝置的不同細節。進而提供并支援與其它作業系統相容的多種檔案系統格式。

程序間通信子產品子系統用于支援多種程序間的資訊交換方式。

網絡接口子產品提供對多種網絡通信标準的通路并支援許多網絡硬體。

這幾個子產品之間的依賴關系見圖 所示。其中的連線代表它們之間的依賴關系,虛線和虛框部分表示 linux 0.11 中還未實作的部分(從 linux 0.95 版才開始逐漸實作虛拟檔案系統,而網絡接口的支援到 0.96版才有)。

linux 核心系統子產品結構及互相依賴關系:

Linux0.11核心剖析--核心體系結構

若從單核心模式結構模型出發,我們還可以根據 linux 0.11 核心源代碼的結構将核心主要子產品繪制成下圖所示的框圖結構:

Linux0.11核心剖析--核心體系結構

  對于 linux 0.11 核心來講, 系統最多可有 64 個程序同時存在。 系統除了第一個程序是“手工”建立以外, 其餘的都是程序使用系統調用 fork 建立的新程序。核心程式使用程序辨別号(process id, pid)來辨別每個程序。程序由可執行的指令代碼、資料和堆棧區組成。程序中的代碼和資料部分分别對應一個執行檔案中的代碼段、資料段。每個程序隻能執行自己的代碼和通路自己的資料及堆棧區。程序之間互相之間的通信需要通過系統調用了進行。對于隻有一個 cpu 的系統,在某一時刻隻能有一個程序正在運作。核心通過排程程式分時排程各個程序運作。

linux 系統中,一個程序可以在核心态( kernel mode)或使用者态( user mode)下執行,是以, linux 核心棧和使用者棧是分開的。使用者棧用于程序在使用者态下臨時儲存調用函數的參數、局部變量等資料。核心棧則含有核心程式執行函數調用時的資訊。核心程式是通過程序表對程序進行管理的,每個程序在程序表中占有一項。在 linux 系統中,程序表項是一個 task 結構。

一個程序在執行時, cpu 的所有寄存器中的值、程序的狀态以及堆棧中的内容被稱為該程序的上下文。當核心需要切換(

switch)至另一個程序時,它就需要儲存目前程序的所有狀态,也即儲存目前程序的上下文,以便在再次執行該程序時,能夠恢複到切換時的狀态執行下去。

在發生中斷時,核心就在被中斷程序的上下文中,在核心态下執行中斷服務例程。但同時會保留所有需要用到的資源,以便中斷服務結束時能恢複被中斷程序的執

行。

一個程序在其生存期内,可處于一組不同的狀态下,稱為程序狀态。見下圖所示:

Linux0.11核心剖析--核心體系結構

程序正在被 cpu 執行時,被稱為處于執行狀态( running)。當程序正在等待系統中的資源而處于等待狀态時,則稱其處于睡眠等待狀态。在

linux

系統中,還分為可中斷的和不可中斷的等待狀态。當系統資源已經可用時,程序就被喚醒而進入準備運作狀态,該狀态稱為就緒态。當程序已停止運作,但其父程序

還沒有詢問其狀态時,則稱該程序處于僵死狀态。當程序被終止時,稱其處于停止狀态。隻有當程序從“核心運作态”轉移到“睡眠狀态”時,核心才會進行程序切

換操作。在核心态下運作的程序不能被其它程序搶占,而且一個程序不能改變另一個程序的狀态。為了避免程序切換時造成核心資料錯誤,核心在執行臨界區代碼時

會禁止一切中斷。

在 linux 0.11 核心中,為了有效地使用系統的實體記憶體,記憶體被劃分成幾個功能區域,見下圖所示:

Linux0.11核心剖析--核心體系結構

其中,linux

核心程式占據在實體記憶體的開始部分,接下來是用于供硬碟或軟碟等塊裝置使用的高速緩沖區部分。當一個程序需要讀取塊裝置中的資料時,系統會首先将資料讀到

高速緩沖區中;當有資料需要寫到塊裝置上去時,系統也是先将資料放到高速緩沖區中,然後由塊裝置驅動程式寫到裝置上。最後部分是供所有程式可以随時申請使

用的主記憶體區部分。核心程式在使用主記憶體區時,也同樣要首先向核心的記憶體管理子產品提出申請,在申請成功後方能使用。對于含有 ram

虛拟盤的系統,主記憶體區頭部還要劃去一部分,共虛拟盤存放資料。

由于計算機系統中所含的實際實體記憶體容量是有限制的。為了能有效地使用這些實體記憶體,linux 采用了 intel cpu 的記憶體分頁管理機制,使用虛拟線性位址與實際實體記憶體位址映射的方法讓所有同時執行的程式共同使用有限的記憶體。

記憶體分頁管理的基本原理是将整個主記憶體區域劃分成 4096 位元組為一頁的記憶體頁面。程式申請使用記憶體時,就以記憶體頁為機關進行配置設定。在使用這種記憶體分頁管理方法時,每個執行中的程序(任務)可以使用比實際記憶體容量大得多的線性位址空間。對

于 intel 80386 系統,其 cpu 可以提供多達 4g 的線性位址空間。對于 linux 0.11 核心,系統設定全局描述符表 gdt

中的段描述符項數最大為 256,其中 2 項空閑、 2 項系統使用,每個程序使用兩項。是以,此時系統可以最多容納(256-4)/2 +

1=127 個任務,并且虛拟位址範圍是 ((256-4)/2)* 64mb 約等于8g。 但 0.11 核心中人工定義最大任務數

nr_tasks = 64 個, 每個程序虛拟位址(或線性位址)範圍是 64m,并且各個程序的虛拟位址起始位置是(任務号-1)*64mb。

是以所使用的虛拟位址空間範圍是 64mb*64 =4g,見下圖所示。 4g 正好與 cpu 的線性位址空間範圍或實體位址空間範圍相同,是以在

0.11 核心中比較容易混淆三種位址概念。

Linux0.11核心剖析--核心體系結構

linux 0.11 中,在進行位址映射時,我們需要厘清 3 種位址之間的變換: a. 程序虛拟位址,是從虛拟位址 0 開始計,最大 64m; b. cpu 的線性位址空間( 0--4g); c. 實際實體記憶體位址。進

程的虛拟位址需要首先通過其局部段描述符變換為 cpu 整個線性位址空間中的位址,然後再使用頁目錄表 pdt(一級頁表)和頁表

pt(二級頁表)映射到實際實體位址頁上。是以兩種變換不能混淆。為了使用實際實體記憶體,每個程序的線性位址通過二級記憶體頁表動态地映射到主記憶體區域的不

同記憶體頁上。是以每個程序最大可用的虛拟記憶體空間是

64mb。每個程序的邏輯位址通過加上任務号*64m,即可轉換為線性位址。不過在注釋中,我們通常将程序中的位址簡單地稱為線性位址。

由于 linux 核心是一種單核心模式的系統,是以,核心中所有的程式幾乎都有緊密的聯系,它們之間的依賴和調用關系非常密切。是以在閱讀一個源代碼檔案時往往需要參閱其它相關的檔案。是以有必要在開始閱讀核心源代碼之前,先熟悉一下源代碼檔案的目錄結構和安排。

這裡我們首先列出 linux

核心完整的源代碼目錄,包括其中的子目錄。然後逐一介紹各個目錄中所包含程式的主要功能,使得整個核心源代碼的安排形式能在我們的頭腦中建立起一個大概的

架構,以便于後面開始的源代碼閱讀工作。當我們使用 tar 指令将 linux-0.11.tar.gz 解開時,核心源代碼檔案被放到了 linux

目錄中。其中的目錄結構為:

Linux0.11核心剖析--核心體系結構

該核心版本的源代碼目錄中含有 14 個子目錄,總共包括 102 個代碼檔案。下面逐個對這些子目錄中的内容進行描述。

1、核心主目錄 linux

linux 目錄是源代碼的主目錄,

在該主目錄中除了包括所有的 14 個子目錄以外,還含有唯一的一個makefile 檔案。該檔案是編譯輔助工具軟體 make 的參數配置檔案。

make 工具軟體的主要用途是通過識别哪些檔案已被修改過,進而自動地決定在一個含有多個源程式檔案的程式系統中哪些檔案需要被重新編譯。是以,

make 工具軟體是程式項目的管理軟體。linux 目錄下的這個 makefile 檔案還嵌套地調用了所有子目錄中包含的 makefile

檔案,這樣,當 linux 目錄(包括子目錄)下的任何檔案被修改過時, make 都會對其進行重新編譯。是以為了編譯整個核心所有的源代碼檔案,隻要在 linux 目錄下運作一次 make 軟體即可。

2、引導啟動程式目錄 boot

boot 目錄中含有 3

個彙編語言檔案,是核心源代碼檔案中最先被編譯的程式。這 3

個程式完成的主要功能是當計算機加電時引導核心啟動,将核心代碼加載到記憶體中,并做一些進入 32 位保護運作方式前的系統初始化工作。 其中

bootsect.s 和 setup.s 程式需要使用 as86 軟體來編譯, 使用的是 as86 的彙編語言格式(與微軟的類似),而

head.s 需要用 gnu as 來編譯,使用的是 at&t

格式的彙編語言。這兩種彙編語言在下一章的代碼注釋裡以及代碼清單後面的說明中會有簡單的介紹。bootsect.s

程式是磁盤引導塊程式,編譯後會駐留在磁盤的第一個扇區中(引導扇區, 0 磁道(柱面),0 磁頭,第 1 個扇區)。在 pc 機加電 rom

bios 自檢後,将被 bios 加載到記憶體 0x7c00 處進行執行。setup.s 程式主要用于讀取機器的硬體配置參數,并把核心子產品

system 移動到适當的記憶體位置處。head.s 程式會被編譯連接配接在 system

子產品的最前部分,主要進行硬體裝置的探測設定和記憶體管理頁面的初始設定工作。

3、檔案系統目錄 fs

是檔案系統實作程式的目錄,共包含 17 個 c

語言程式。這些程式之間的主要引用關系所示圖中每個方框代表一個檔案,從上到下按基本按引用關系放置。其中各檔案名均略去了字尾.c,虛框中是的程式檔案

不屬于檔案系統,帶箭頭的線條表示引用關系,粗線條表示有互相引用關系。

Linux0.11核心剖析--核心體系結構

由圖可以看出,該目錄中的程式可以劃分成四個部分:高速緩沖區管理、低層檔案操作、檔案資料通路和檔案高層函數,對于檔案系統,我們可以将它看成是記憶體高速緩沖區的擴充部分。所有對檔案系統中資料的通路,都需要首先讀取到高速緩沖區中。本目錄中的程式主要用來管理高速緩沖區中緩沖塊的使用配置設定和塊裝置上的檔案系統。

4、頭檔案主目錄 include

頭檔案目錄中總共有 32 個.h 頭檔案。其中主目錄下有 13 個, asm 子目錄中有 4 個, linux 子目錄中有10 個, sys 子目錄中有 5 個:

體系結構相關頭檔案子目錄 include/asm

這些頭檔案主要定義了一些與 cpu 體系結構密切相關的資料結構、宏函數和變量。共 4 個檔案。

linux 核心專用頭檔案子目錄 include/linux

系統專用資料結構子目錄 include/sys

5、核心初始化程式目錄 init

該目錄中僅包含一個檔案

main.c。用于執行核心所有的初始化工作,然後移到使用者模式建立新程序,并在控制台裝置上運作 shell

程式。程式首先根據機器記憶體的多少對緩沖區記憶體容量進行配置設定,如果還設定了要使用虛拟盤,則在緩沖區記憶體後面也為它留下空間。之後就進行所有硬體的初始化

工作,包括人工建立第一個任務( task 0),并設定了中斷允許标志。在執行從核心态移到使用者态之後,系統第一次調用建立程序函數

fork(),建立出一個用于運作 init()的程序,在該子程序中,系統将進行控制台環境設定,并且在生成一個子程序用來運作 shell程式。

6、核心程式主目錄 kernel

linux/kernel 目錄中共包含 12 個代碼檔案和一個 makefile 檔案,另外還有 3 個子目錄。由于這些檔案中代碼之間調用關系複雜,是以這裡就不詳細列出各檔案之間的引用關系圖,但仍然可以進行大概分類,如圖所示:

Linux0.11核心剖析--核心體系結構

塊裝置驅動程式子目錄 kernel/blk_dev

常情況下,使用者是通過檔案系統來通路裝置的,是以裝置驅動程式為檔案系統實作了調用接口。在使用塊裝置時,由于其資料吞吐量大,為了能夠高效率地使用塊設

備上的資料,在使用者程序與塊裝置之間使用了高速緩沖機制。在通路塊裝置上的資料時,系統首先以資料塊的形式把塊裝置上的資料讀入到高速緩沖區中,然後再提

供給使用者。 blk_dev 子目錄共包含 4 個 c 檔案和 1 個頭檔案。頭檔案 blk.h 由于是塊裝置程式專用的,是以與 c

檔案放在一起。這幾個檔案之間的大緻關系,如圖所示:

Linux0.11核心剖析--核心體系結構

你将看到該函數在許多通路塊裝置資料的地方被調用,尤其是在高速緩沖區處理檔案 fs/buffer.c 中。

字元裝置驅動程式子目錄 kernel/chr_dev

字元裝置程式子目錄共含有 4 個 c 語言程式和 2 個彙程式設計式檔案。 這些檔案實作了對串行端口 rs-232、串行終端、鍵盤和控制台終端裝置的驅動。下圖(圖 2.12)是這些檔案之間的大緻調用層次關系:

Linux0.11核心剖析--核心體系結構

協處理器仿真和操作程式子目錄 kernel/math

子目錄中目前僅有一個 c 程式 math_emulate.c。其中的 math_emulate()函數是中斷 int7 的中斷處理程式調用的 c

函數。當機器中沒有數學協處理器,而 cpu

卻又執行了協處理器的指令時,就會引發該中斷。是以,使用該中斷就可以用軟體來仿真協處理器的功能。本書所讨論的核心版本還沒有包含有關協處理器的仿真代

碼。本程式中隻是列印一條出錯資訊,并向使用者程式發送一個協處理器錯誤信号 sigfpe。

7、核心庫函數目錄 lib

核心庫函數主要用于使用者程式設計調用,是編譯系統标準庫的接口函數之一。其中共有 12 個 c 語言檔案,除了一個由 tytso 編制的 malloc.c 程式較長以外,其它的程式很短,有的隻有一二行代碼。

8、記憶體管理程式目錄 mm

該目錄包括 2 個代碼檔案。主要用于管理程式對主記憶體區的使用,實作了程序邏輯位址到線性位址以及線性位址到主記憶體區中實體記憶體位址的映射,通過記憶體的分頁管理機制,在程序的虛拟記憶體頁與主記憶體區的實體記憶體頁之間建立了對應關系。

page.s 檔案包括記憶體頁面異常中斷( int 14)處理程式,主要用于處理程式由于缺頁而引起的頁異常中斷和通路非法位址而引起的頁保護。

memory.c 程式包括對記憶體進行初始化的函數 mem_init(),由 page.s 的記憶體進行中斷過程調用的do_no_page()和 do_wp_page()函數。在建立新程序而執行複制程序操作時,即使用該檔案中的記憶體處理函數來配置設定管理記憶體空間。

9、編譯核心工具程式目錄 tools

該目錄下的 build.c 程式用于将 linux 各個目錄中被分别編譯生成的目标代碼連接配接合并成一個可運作的核心映象檔案 image。具體的功能後面詳細介紹

在 linux 系統中,核心為應用程式提供了兩方面的接口。其一是系統調用接口,也即中斷調用 int 0x80;另一方面是通過核心庫函數,與

核心進行資訊交流。核心庫函數是基本 c 函數庫 libc 的組成部分。許多的系統調用是作為基本 c

語言函數庫的一部分實作的。系統調用主要是提供給系統軟體直接使用或用于庫函數的實作。而一般使用者開發的程式則是通過調用象 libc

等庫中的函數來通路核心資源。通過調用這些庫中的程式,應用程式代碼能夠完成各種常用工作,例如,打開和關閉對檔案或裝置的通路、進行科學計算、出錯處理

以及通路組和使用者辨別号 id

等系統資訊。系統調用是核心與外界接口的最高層。在核心中,每個系統調用都有一個序列号(在include/linux/unistd.h

頭檔案中定義),并常以宏的形式實作。

繼續閱讀