1. 程式人生 > >《Linux 核心完全註釋》閱讀筆記

《Linux 核心完全註釋》閱讀筆記

在閱讀原始碼之前,有必要對Linux核心的體系結構、原始碼的目錄結構有個巨集觀地瞭解,《Linux核心完全註釋》非常詳細地介紹了這方面的內容,所以 這裡僅僅進行概述性的討論,以便讓所有的筆記構成一個整體。
    這裡主要介紹四部分內容。

1. Monolithic kernel vs. Microkernel

    作業系統為使用者提供了利用各種計算機硬體的介面和抽象,為了讓計算機穩定工作,它具有兩個不同的工作空間,一個叫使用者空間,一個是核心空間,後者有更高的 特權級別。核心作為作業系統的核心,處在核心空間,它的體系結構跟作業系統的效能、可擴充套件性和可維護性、安全和穩定性等各方面關係很大。核心體系結構主要 有三類:Monolithic kernel, Microkernel, hybrid kernel.
    資料[1]給出了前兩者的大體結構,見附圖1和2,而hybrid核心則介於兩者之間。
    從附圖中可以看出,Microkernel僅保障基本的程序通訊和I/O控制,而把其他的服務,諸如記憶體管理、程序管理、檔案系統等作為服務“模組”在操 作系統的使用者空間提供;而Monolithic kernel則把這些服務都在核心空間實現,這一設計結構的區別對兩者的諸多方面帶來了影響,而影響較大的是核心大小、效能和可擴充套件性三個方面。
    從核心大小上看,對於Microkernel,因為它僅提供最基本的服務,所以可以很小,而Monolithic kernel把所有重要服務都放在核心裡,所以相對更大。核心大小對儲存裝置的儲存空間、核心除錯、核心移植都有很大影響。
    從核心執行效率上看,因為Microkernel把一些服務移到了使用者空間,所以會增加使用者空間和核心空間之間的context switch,對Microkernel的效能造成了一定的影響;而Monolithic kernel則沒有這樣的問題;不過,因為Microkernel可以小到直接放到處理器的一級快取中,並可以更方便地針對不同的處理器進行優化,所以從 這方面來說,它的效能可能就Monolithic kernel有一些優勢,因而,結合這兩方面,目前Microkernel的效能可能並不會比Monolithic kernel差。
    從可擴充套件性來說,從圖中可以看出,Microkernel的主要服務模組是相互獨立的,僅跟底層的Microkernel相關,而Microkernel 提供的服務是最基本的,介面有限,可以更加方便地新增或者移除各種服務,而且新增新服務後僅需要編譯該服務本身;對於Monolithic Kernel,各個服務之間成層次結構,依賴關係較大,而且都處於核心空間,新增和移除服務會觸動其他部分,而且新增服務後需要重新編譯整個核心,會帶來 一些麻煩。
    實際上,對兩者的比較還有很多值得討論的問題,更多資訊建議仔細閱讀參考資料[1]。  

    Linux-0.11是Monolithic Kernel,因此在閱讀它的原始碼時需要把握這個方面,它提供了程序排程、記憶體管理、程序通訊和檔案系統幾個基本服務。下面進一步介紹Linux- 0.11的體系結構。

2. linux-0.11核心的體系結構
   
    Linux-0.11的核心還沒有實現網路介面,僅實現了程序排程、記憶體管理、程序通訊和檔案系統幾部分,《Linux核心完全註釋》用一個圖描述了這幾 部分的關係,見附圖3。
    如圖所示,程序排程部分作為“核心”模組,用於控制程序對CPU資源的使用。記憶體管理部分用於確保所有程序能夠安全地共享記憶體;檔案系統部分用於支援對外 部裝置的驅動和儲存,虛擬檔案系統部分通過向所有外部儲存裝置提供一個通用的檔案介面,隱藏了各種硬體裝置的不同細節。程序通訊部分用於支援多種程序間的 資訊交換方式。而網路介面部分提供了對多種網路通訊標準的訪問並支援許多網路硬體。需要提到的是,在linux-0.11中,虛擬檔案系統和網路介面部分 並沒有實現。
    《Linux核心完全註釋》對上述各個部分進行了詳細的說明,包括一些原理和實現細節,這裡不再深入討論,而是通過後面的閱讀筆記逐個進行深入分析。

3. linux-0.11核心的原始碼目錄結構
   
    在具體閱讀各部分原始碼之前,我們瞭解一下linux-0.11核心的原始碼目錄結構,核心原始碼的目錄結構基本上對照核心體系結構的各個部分。

$ tree -d linux-0.11
linux-0.11
|-- boot            系統引導彙編程式
|-- fs                檔案系統
|-- include        標頭檔案(*.h)
|   |-- asm         與CPU體系結構相關的部分
|   |-- linux        Linux核心專用部分
|   `-- sys         系統資料結構部分
|-- init              核心初始化程式
|-- kernel         核心程序排程、訊號處理、系統呼叫等程式
|   |-- blk_drv   塊裝置驅動程式
|   |-- chr_drv  字元裝置驅動程式
|   `-- math     數學協處理模擬處理程式
|-- lib              核心庫函式
|-- mm            記憶體管理程式
`-- tools         生成核心Image檔案的工具程式



    需要注意的是,除了那些用於C語言(.c和.h)或者組合語言(.S和.s)寫的原始碼外,還有一些Makefile檔案,這類檔案用於make這個專案 管理工具。關於make工具和Makefile檔案的用法,可以參考資料[5],在具體閱讀原始碼之前,強烈建議你先學會使用make工具。
    下面我們就以核心原始碼主目錄下的Makefile檔案為入口開始閱讀linux-0.11的原始碼。

4. 開始閱讀原始碼:linux-0.11核心原始碼主目錄下的Makefile檔案
   
    linux-0.11核心原始碼主目錄下的Makefile檔案用於管理整個linux-0.11專案,它的主要作用是可以讓我們利用make工具來產生 linux核心的Image檔案。

4.1 make工具簡介

    該Makefile檔案主要包括兩大部分:第一部分是第1到28行,是一些變數;而後面的所有行構成第二部分,是一些"目標:源  動作“段,它告知make工具根據輸入引數執行不同的目標動作。
    兩個比較重要的目標是all和clean,前者是make工具的預設動作,如果沒有任何引數時,就執行它,後者用於刪除各類目標檔案。

    進入linux-0.11的原始碼主目錄,可以執行如下操作:

$ cd linux-0.11
$ make  //直接進入Makefile檔案中的all目標,以便編譯產生linux-0.11核心
$ make clean //清除之前產生的所有目標檔案


   
4.2 利用cscope & ctags輔助原始碼閱讀

    在具體閱讀Makefile檔案之前,我們通過cscope和ctags檔案生成必要的索引,以方便原始碼的閱讀。

$ cscope -Rbq
$ ctags -R



4.3 配置原始碼閱讀器和編輯器

    另外,建議通過資料[6],配置一下vim編輯器,比如讓vim顯示行號,支援語法加亮等。
   
4.4 瞭解一些基本工具

    如果要讀懂每一行,這些工具是有必要了解甚至熟悉的。

// bin86軟體包中用於x86語法的組合語言的彙編和連線工具
as86     彙編工具
ld86      連結工具
// gnu的一些專案開發工具
make    專案管理
gcc        編譯器
as         彙編器
ld          連結器
ar         archives(.a)檔案處理工具
nm        用於列出目標檔案的符號
objcopy  用於複製或者轉換目標檔案格式
strip     用於刪除目標檔案中的某些段
// 檔案操作工作
mv        檔案重新命名
rm        刪除檔案
cp         複製檔案
// 字串操作
grep     字串查詢
sort      字串匹配
// 把檔案系統緩衝區的內容立即寫回到磁碟
sync


   
4.5 開始閱讀

    因為原書對各個部分的討論都已經非常詳細了,這裡僅僅介紹幾個原書可能沒有介紹到的部分。在具體閱讀之前,需要明確的是:這裡閱讀的linux-0.11 核心原始碼是上一節(《Linux核心完全註釋》閱讀筆記——搭建實驗環境)中打過ubuntu 8.04下的所有補丁後的原始碼,而不是最原始的原始碼,否則,下面提到的行號會對不上,從而影響閱讀效果。

    通過vim開啟Makefile檔案,開始閱讀,確保打開了語法加亮(:syn on)、顯示行號(:set number)。

    Makefile的第13行原來有-mcpu這個引數,不過當前gcc版本不再支援該引數,而是使用-march引數。

13 CC      =gcc-3.4 -march=i386 $(RAMDISK)


    上面一行引用了第5行,該行的目的是通過gcc的-D引數定義一個巨集。

 5 RAMDISK =  #-DRAMDISK=512


    通過在vim檔案中連結cscope.out資料庫,並執行":cs find t RAMDISK"容易找到在init/main.c檔案中引用了該巨集,在具體討論ramdsik時,我們再詳細討論它。

123 #ifdef RAMDISK
124         main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
125 #endif


    按下CTRL+T返回Makefile檔案。
   
    Makefile檔案的第23行用於指定linux-0.11核心使用的根檔案系統所在的裝置,作為tools/build工具的第4個引數,在 Makefile檔案的第45行被引用到。

23 ROOT_DEV= #FLOPPY
...
45         tools/build boot/bootsect boot/setup tools/kernel $(ROOT_DEV) > Image


    我們發現,這裡的$(ROOT_DEV)引數為空,說明傳遞給toos/build的引數僅有3個,對應地,因為main函式引數argv的第一個項是程 序名本身,加上3個引數,所以argc為4。通過":cs find f tools/build.c",並找到main函式,我們發現。

 90         if (argc == 5) {
...
102         } else {
103                 major_root = DEFAULT_MAJOR_ROOT;
104                 minor_root = DEFAULT_MINOR_ROOT;
105         }


    到argc為4時,根檔案系統的主裝置號和次裝置號分別對應巨集DEFAULT_MAJOR_ROOT和DEFAULT_MINOR_ROOT。這兩個巨集在 哪裡定義的呢?通過":cs find g DEFAULT_MAJOR_ROOT"和":cs find g DEFAULT_MINOR_ROOT"找到,在tools/build.c的61和62行。

 61 #define DEFAULT_MAJOR_ROOT 3
 62 #define DEFAULT_MINOR_ROOT 1


    通過閱讀原書第14章的表14-3,我們發現,

0x301       /dev/hd1   表示第一個硬碟的第一個分割槽


    所以說這裡指定了linux-0.11核心啟動時的根檔案系統所在的裝置為/dev/hd1,即硬碟的第一個分割槽。

    下面同樣按下CTRL+T直到回到Makefile檔案,閱讀如下部分:

 41 Image: boot/bootsect boot/setup tools/system tools/build
 42         cp -f tools/system system.tmp
 43         strip system.tmp
 44         objcopy -O binary -R .note -R .comment system.tmp tools/kernel
 45         tools/build boot/bootsect boot/setup tools/kernel $(ROOT_DEV) > Image
 46         rm system.tmp
 47         rm tools/kernel -f
 48         sync


    該部分的各行作用如下:
    第41行表示Image目標依賴boot/bootsect boot/setup tools/system tools/build四個子目標,因此make工具會先確保這四個子目標完成以後再執行Image目標,即執行Image所在行之後的所有以一個TAB 鍵開頭的所有行。
    第42行通過cp把核心檔案tools/system複製一份為system.tmp,-f會確保system.tmp是tools/system的最新 拷貝。
    第43行用strip把system.tmp中的所有符號資訊刪除,因為目標檔案中的所有符號在連結以後都已經重定位為記憶體地址,所以符號本身對程式的運 行就沒有了任何影響,可以刪除掉(這裡僅僅對不使用動態連結庫的程式而言,關於動態連結庫的更多細節,可以參考資料[7])。這裡為什麼不刪除 tools/system中的符號資訊呢,因為tools/system需要提供這些符號資訊給gdb偵錯程式。而system.tmp用於產生最終的 Image檔案,僅僅需要執行程式碼就可以。
    第44行利用objcopy把system.tmp檔案中對程式執行沒有影響的內容給刪除掉,生成新的核心檔案tools/kernel。關於這部分,建 議大家熟悉一下工具readelf,objdump等工具的用法,並瞭解一下ELF檔案的格式。
    第45行利用核心自帶的tools/build工具把核心引導和安裝工具boot/bootsect和boot/setup以及核心檔案tools /kernel合併,產生一個Image檔案。
    第46和47行刪除兩個臨時檔案system.tmp和tools/kernel。
    第48行用於重新整理檔案系統緩衝區,把剛才生成的檔案Image和tools/system寫到磁碟,從而產生一個linux核心軟盤映像檔案和包含除錯信 息的tools/system檔案。

    下面通過vim的命令模式,輸入":72",進入Makefile檔案的72行(當然你也可以直接用vim +72 Makefile)。

 72         (cd kernel/math; make)


    這一行的作用的進入kernel/math目錄,並根據該目錄下的makefile檔案,編譯其中的內容。該行在當前make版本下可以直接寫為:

 72         make -C kernel/math


    我們發現,Makefile檔案中有很多這樣的行,目的是為了通過主目錄下的Makefile檔案方便地執行各個子目錄下的Makefile檔案,以便遍 歷所有的原始碼檔案並編譯它們。

    linux-0.11主目錄下的Makefile檔案就介紹到這裡,更多的內容請閱讀原書的第二章的相關部分。


    到這裡,四個部分的內容基介紹完了,不過還有兩個比較重要的部分,包括linux-0.11使用的目標檔案格式和檔案系統型別,得提到一下,因為它們和當 前linux版本支援的目標檔案格式和檔案系統型別有很大差別。linux-0.11使用的目標檔案格式是a.out[3],而當前linux版本支援的 目標檔案格式主要是ELF;linux-0.11使用的檔案系統型別是Minix[4],而當前linux版本支援的主要檔案系統格式有 ext2,ext3等(也支援Minix)。
    另外,在具體閱讀程式碼之前,我們可能還需要對保護模式[1],記憶體管理[2],時鐘與中斷,程序排程和通訊等很多方面都需要有基本地瞭解,建議結合原書和 網路中的資料加強這些方面的瞭解。

參考資料

[0] Monolithic kernel vs. Microkernel
http://www.vmars.tuwien.ac.at/courses/akti12/journal/04ss/article_04ss_Roch.pdf
[1] Protected Mode
http://www.x86.org/articles/pmbasics/tspec_a1_doc.htm
http://www.oldlinux.org/oldlinux/viewthread.php?tid=7297&extra=page%3D1
[2] Memory Management
http://mirror.lzu.edu.cn/os/oldlinux.org/Ref-docs/Linux011-Mem-YuanYi.pdf
[3] a.out
http://en.wikipedia.org/wiki/A.out
[4] MINIX file system
http://en.wikipedia.org/wiki/MINIX_file_system
[5] 跟我一起寫Makefile
[6] 把vim打造成原始碼閱讀器
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1544.html
[7] 動態符號連結的細節
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1551.html