1. 程式人生 > >Linux內核(16) - 高效學習Linux內核

Linux內核(16) - 高效學習Linux內核

pen 數據 豆腐渣 缺少 那種 除了 穩定 內存 ati

世界悲結束了,章魚哥也退役了,連非誠勿擾中的拜金女也突然的少了很多。這本《Linux內核修煉之道》在卓越、當當、china-pub上也已經開賣了,雖然是嚴肅文學,但為了保證流暢性,大部分文字我還都是斟詞灼句,反復的念幾遍才寫上去的,盡量考慮到寫上去的每段話能夠讓讀者產生什麽疑惑,然後也都會緊接著盡量的去進行解釋清楚,中間的很多概念也有反復糾結過怎麽解釋能夠更容易的理解,力求即使對於初學者也可以有很少阻礙的一氣讀完。同時我也把書中一部分自己的感悟抽出來整理了精華版,share出來。當然水平有限,錯漏之處有發現而修訂時遺漏的,也有尚沒有發現的。這本書如果對您有用,乃我之幸事,如果無用,就在此先誠惶誠恐的向大家拜個不是了。

在6月份做過一次《高效學習Linux內核》的presentation,下面是前面的一部分內容及講義,或許對大家有用吧。至於剩余的,因為和之前博客中的部分文章內容差不多,就不貼了。

**********************************************************************

既然有高效,相對的就有低效。學習本身就是一件很玄乎的事情,有些人整天瀟瀟灑灑沒見怎麽用心就能夠獲得很好的成績,而有些人則相反,即使投懸梁錐刺骨也還是成績平平收獲平平。這裏面很大一部分的原因就是學習的方法。

但是學習方法這樣的題目並不好講,因為基於每個人不同的情況,並沒有那樣一個標準的方法存在,所以講起來就很容易成為一場大忽悠。就像我們的任誌強先生前陣子演講賣房子 的方法時,就因為太像一場忽悠,從而被聽眾扔了鞋。

技術分享圖片

接下來我就通過自己的一些感悟,拋磚引玉來介紹一下如何比較高效的去學習 linux 內核。這些話並不局限於某個部分的內容,很像一句句的口號,我們也可以將它們看作內核學習的大字報。

首先是第一句話:把內核當朋友。今年笑來老師有本新書,叫把時間當朋友,告訴我們只有把時間當朋友,才能更好的利用自己的時間做些有益的事情。同樣,我們只有把內核當朋友,把它放在對等的地位上,而不僅僅是一堆死氣沈沈的代碼,我們才能夠更好的認識和理解到它的精髓。

然後是第二句話:先會使用它。意思就是我們在學習內核前首先要會用 linux ,依照一個由上至下循序漸進的過程,在能夠熟練的使用 Linux 操作系統之後再去研究內核中的實現。這也是 linus 本人的觀點。

第三句是依照四個層次進行內核學習。笛卡兒在 17 世紀的某一天,閑極無聊寫了這麽一本書,書名就叫《方法論》,在這本目前來說絕大部分人都不知道的書裏將方法上升到了理論的高度。笛卡兒在他的這本書裏將研究問題的方法歸納為簡單的一句話,就是 “ 復雜問題要簡單化 ” 。就是說要將復雜的問題分解為很多個簡單的小問題,一個個的分開解決。這句話當然可以借鑒運用到內核的學習上,不過需要做些改動,不是分解為多個簡單的小問題,而是將內核學習這麽一件很復雜的事情劃分為由低到高多個不同的層次,每一層次都有自己需要達到的目標和要求。這也是我自己認為比較好的認識學習內核的方法。

第四句是走出心理誤區。 對於學習這種復雜的事情來說,無論是我們在學校的課堂學習,還是這裏說的內核學習,它的效果好與壞,最主要取決於兩個方面:一個是學習的方法,另一個就是學習時的心理。註意,在這兒我無視了智商的差異,智商這玩意兒太玄了,可以將它歸於迷信的範疇。而我們在學習時經常會產生一系列的問題或者說誤區,只有走出這些誤區,在學習中養成一個堅強的心理,我們才能夠真正的做到高效。

第五句是使用 vim+cscope+ctags 瀏覽內核源碼。其實這句話更主要的意思是說我們需要一個好的工具去瀏覽內核的代碼。在 windows 下面,我們或許可以很容易的找到很多比較好的 IDE 可以用來瀏覽代碼,比如 source insight ,它可以很方便的在代碼之間進行關聯閱讀。但是對於 Linux 新人來說,有沒有一個功能類似的瀏覽代碼的工具就成為一個很常見的問題。

第六句是使用 kernel 地圖定位目標代碼。應該說學習內核就是學習內核的源代碼,但是內核代碼千千萬,又到處像個迷宮一樣,不迷路都很難,又怎麽去直面它?這時我們就需要這樣的一幅內核地圖來幫助我們去定位所要分析的目標代碼,並縮小目標代碼的範圍與代碼量。

接下來是第七句話:分析內核源碼,態度決定一切。我們很多人或許有這樣的困惑,也分析瀏覽了很多內核的源碼,可總是覺得分析完瀏覽完腦子裏還是空空的,並沒有感覺到多大的收獲。這個時候我們或許可以去看看是不是自己在分析代碼時的態度出現了問題。我們在分析內核源碼時,只有遵循嚴謹的態度,去理解每一段代碼的實現,多問多想多記,而不是抱著走馬觀花,得過且過的態度,最終必然會有很大的收獲。

最後一句是:以內核源碼為中心,堅持學習資源建設。在我們內核學習的過程中,內核源碼本身就是最好的參考資料,其他任何經典或非經典的書最多只是起到個輔助作用,不能也不應該取代內核代碼在我們學習過程中的主導地位。但是這些輔助的作用也是不可忽視的,我們需要以內核源碼為中心,堅持各種學習資源的長期建設不動搖。

除了這裏的八句話,其他的可能會對大家有幫助的感悟或者方法還有很多 。

技術分享圖片

把內核當朋友,就是要把內核看成一個鮮活的生命體,而不是一堆死氣沈沈的代碼。

具體一點來說,我們在學習與瀏覽內核的實現時,可以將它看成是現實世界的映射。內核是由現實中的人寫出來的,因此不管是有意還是無意,都會不可避免的包含了一些自己的現實感情,我們研究內核時可以體會下這種脈絡,這種隱藏在代碼背後的哲學。比如,我們可以認為內核是個大世界,一個個進程就是這個世界中的一個個生命體,進程管理和調度就是這個大世界中的權力機關,內存是進程的家,內核的目標就是要做到使每個進程都居者有其屋。

既然要把內核看成是一個鮮活的個體,那麽我們認識它的第一件事就是了解它的一些基本信息,就像我們人與人之間互相認識首先也是通過個人的基本信息一樣。

首先從名字開始, kernel 在字典中主要有兩種定義,一種是 “ 軟的,一個堅果可食用的部分 ” ,對 Linux kernel 來說,當然適用的是第二種定義: “ 某個東西的核心部分 ” 。所以從廣義上來說, linux kernel 就是 linux 操作系統裏最為核心的部分,而從狹義上來說,它不過就是 Linus 那群人人寫的那點兒代碼。

當然,這點兒代碼是相當復雜的,單單從代碼量上來說,早已經突破了千萬級。從結構上來說,也早就不是一個人窮自己一己之力就能夠全部理解的了。所以,現在強調第二句話:學內核切忌求大求全,選擇一點研究的足夠深入就很不容易了。

下面介紹介紹 kernel 的年齡, kernel 又不是一個懷春的少女,所以它的年齡並不需要保密,從 1991 年誕生開始,在去年剛剛舉行了它自己的成人禮,進入了成熟發展期。

就像我們人有自己的青春期、中年期等一樣, kernel 相應的也有很多不同的版本號,不過不同的是,我們的青春期一去就不復返了, kernel 不同的版本號卻是共存的。

很多年以來,內核的版本都是以 X.Y.Z 這 3 個數字的形式分配的,中間的偶數 Y 代表穩定版,奇數 Y 代表了不穩定的開發版。所謂的穩定版本是指內核的特性都已經固定,代碼運行穩定可靠,不會再增加新的特性,要改進也只是修改代碼中的錯誤。而不穩定版本是指相對於上一個穩定版本增加了新的特性,還處於發展之中,代碼的運行不大可靠。

對於目前來說, 2.6 內核的發布已經持續了很長時間,那麽什麽時候將會推出 2.7 ? Linus 本人的回答是,不會有 2.7 ,他不會再遵循舊的模式,新采用的模式會更好,不值得重復過去。他表示正在考慮新的編號方式,一種基於時間的版本號。比如用 2008.7 取代 2.6.26 ,中間第二個數字代表年, 2008 年就是 2.8 , 2009 年的第一個版本就是 2.9.1 ,之後 2010 年是 3.0 ,等等。

最後不得不提到是那些眼花繚亂的發行版,內核與發行版的關系就類似那種雙生花,彼此互相依賴互相扶持共同成長。沒有那些發行版,內核就只能是束之高閣的一個貌似好看的玩具,並不能真正的走進我們的工作生活,而沒有內核,那些發行版就缺少了存在的地基,就只能是個豆腐渣工程。

技術分享圖片

現在我們了解了內核這個朋友的外表,這個時候我們不能像非誠勿擾中的那些拜金女一樣只關心外在的信息,我們還要接著了解內核的內涵,也就是內核的體系結構以及內核是如何工作的。

首先看第一張圖,它向我們傳遞了這樣的信息 —— 內核將應用程序和硬件分離開來。內核一方面負責與計算機硬件進行交互,實現對硬件的控制,調度對硬件資源的訪問,另一方面為用戶應用程序提供一個高級的執行環境和訪問硬件的虛擬接口。

提供硬件的兼容性是內核的設計目標之一,幾乎所有的硬件,只要不是為其他操作系統所定制的,都可以得到 Linux 的支持。

與硬件兼容性相關的是可移植性,也就是在不同的硬件平臺上運行 Linux 的能力。從最初只支持標準 IBM 兼容機上的 Intel X86 架構到現在可以支持 ARM 、 MIPS 、 PowerPC 等幾乎所有硬件平臺,如此廣泛的平臺支持之所以能夠成功,部分原因在於,內核清晰地劃分為了體系相關部分和體系無關部分。因此也就有了第二張圖。

體系無關部分通常會定義與體系相關部分的接口,這樣,內核向新的體系結構移植的過程就變成確認這些接口的特性並將它們加以實現的過程。

同時,用戶應用程序和內核之間的聯系,一般是通過它和內核的中間層 —— 標準 C 庫來實現,而標準 C 庫函數本身,則是建立在內核提供的系統調用基礎之上。通過標準 C 庫,以及內核體系無關部分與體系相關部分的接口,用戶應用程序和部分內核都成為可移植的。

因此更為準確的是第三張圖。其中,進程管理部分實現了一個進程世界的抽象,這個進程世界類似於我們的人類世界,只不過我們的世界裏的個體是人,而在進程世界裏則是一個一個的進程,我們人與人之間通過書信、手機、網絡等交通往來,而各個進程之間則是通過不同方式的進程間通信,我們所有人都在分享同一個地球,而所有進程都在分享一個或多個 CPU 。

在這個進程的世界裏,內存是重要的資源之一,就好似我們的土地。因此,管理內存的策略與方式,也就是內存管理是決定系統性能的一個關鍵因素。

技術分享圖片

了解了內核的體系結構,我們再來看看內核是如何工作的。

首先,內核通過系統調用來使得運行在它上面的應用程序可用。系統調用是內核和應用程序之間的橋梁,比如圖中的針對文件操作的 open() , close() , read() , write() ,針對進程操作的 fork() , wait() ,還有針對網絡操作的 socket() 等等,它們提供了對硬件的抽象,所以有時也被稱為 linux 虛擬機。

內核提供的最接近實際用戶的明顯抽象是文件系統,我們很容易能夠利用 open() 等幾個系統調用編寫一段程序打開一個文件並將它的內容拷貝到標準輸出。內核通過這些系統調用為用戶提供了一個文件的 " 錯覺 " ,而實際上它不過是一堆數據有了個名字,這樣一來就不必去與硬件底層的堆棧、分區和指針等交涉,這也就是我們經常所說的抽象 (abstraction) ,將底層的東西以更易懂的方式表達出來。

文件系統是內核提供的比較明顯的一種抽象,我們可以說它是位於臺前的,而相對還有一些是位於幕後的,比如進程調度。在 linux 上,任何 一個時間,都可能有好幾個進程或者程序等待著運行。內核的時間調度給每個進程分配 CPU 時間,所以就一段時間內來說,我們會有種錯覺:電腦同一時間運行好幾個程序。

再比如另外一個位於幕後的內存管理,幕後到應用開發者都不易察覺的地步。每個程序運行得都好像它有個自己的地址空間來調用一樣,實際上它跟其他進程一樣共享計算機的物理存儲。內存管理的另外一個方面是防止一個進程訪問其他進程的地址空間 —— 對於多進程操作系統來說這是很必要的一個防範措施。

當然位於臺前幕後的還有其他的角色,比如網絡等。

我們現在再來簡單看下它的物理組成。早期版本的內核是整體式的,也就是說所有的部分都靜態地連接成一個很大的執行文件。

Linux內核(16) - 高效學習Linux內核