開篇
學習核心,每個人都有自己的學習方法,仁者見仁智者見智。以下是我在學習過程中總結出來的東西,對自身來說,我認為比較有效率,拿出來跟大家交流一下。
核心學習,一偏之見;疏漏難免,懇請指正。
為什麼寫這篇部落格
剛開始學核心的時候,不要執著於一個方面,不要專注於一個子系統就一頭扎到實際的程式碼行中去,因為這樣的話,牽涉的面會很廣,會碰到很多困難,容易產生挫敗感,一個函式體中(假設剛開始的時候正在學習某個方面的某個具體的功能函式)很可能摻雜著其他各個子系統方面設計理念(多是大量相關的資料結構或者全域性變數,用於支撐該子系統的管理工作)下相應的程式碼實現,這個時候看到這些東西,紛繁蕪雜,是沒有頭緒而且很不理解的,會產生很多很多的疑問,(這個時候如果對這些疑問糾纏不清,刨根問底,那麼事實上就是在學習當前子系統的過程中頻繁的去涉足其他子系統,這時候注意力就分散了),而事實上等了解了各個子系統後再回頭看這些東西的話,就簡單多了,而且思路也會比較清晰。所以,要避免 “只見樹木,不見森林”,不要急於深入到底層程式碼中去,不要過早研究底層程式碼。
我在大二的時候剛開始接觸核心,就犯了這個錯誤,一頭扎到記憶體管理裡頭,去看非常底層的實現程式碼,雖然也是建立在記憶體管理的設計思想的基礎上,但是相對來說,比較孤立,因為此時並沒有學習其它子系統,應該說無論是視野還是思想,都比較狹隘,所以程式碼中牽涉到的其它子系統的實現我都直接跳過了,這一點還算聰明,當然也是迫不得已的。
我的學習方法
剛開始,我認為主要的問題在於你知道不知道,而不是理解不理解,某個子系統的實現採用了某種策略、方法,而你在學習中需要做的就是知道有這麼一回事兒,然後才是理解所描述的策略或者方法。
根據自己的學習經驗,剛開始學習核心的時候,我認為要做的是在自己的腦海中建立起核心的大體框架,理解各個子系統的設計理念和構建思想,這些理念和思想會從巨集觀上呈獻給你清晰的脈絡,就像一個去除了枝枝葉葉的大樹的主幹,一目瞭然;當然,肯定還會涉及到具體的實現方法、函式,但是此時接觸到的函式或者方法位於核心實現的較高的層次,是主(要)函式,已經瞭解到這些函式,針對的是哪些設計思想,實現了什麼樣的功能,達成了什麼樣的目的,混個臉熟的說法在這兒也是成立的。至於該主函式所呼叫的其它的輔助性函式就等同於枝枝葉葉了,不必太早就去深究。此時,也就初步建立起了核心子系統框架和程式碼實現之間的關聯,關聯其實很簡單,比如一看到某個函式名字,就想起這個函式是針對哪個子系統的,實現了什麼功能。
我認為此時要看的就是LKD3,這本書算是泛泛而談,主要就是從概念,設計,大的實現方法上描述各個子系統,而對於具體的相關的函式實現的程式碼講解很少涉及(對比於ULK3,此書主要就是關於具體函式程式碼的具體實現的深入分析,當然,你也可以看,但是過早看這本書,會感覺很痛苦,很枯燥無味,基本上都是函式的實現),很少,但不是沒有,這就很好,滿足我們當前的需求,還避免我們過早深入到實際的程式碼中去。而且本書在一些重要的點上還給出了寫程式時的注意事項,算是指導性建議。主要的子系統包括:記憶體管理,程式管理和排程,系統呼叫,中斷和異常,核心同步,時間和定時器管理,虛擬檔案系統,塊I/O層,裝置和模組。(這裡的先後順序其實就是LKD3的目錄的順序)。
我學習的時候是三本書交叉著看的,先看LKD3,專於一個子系統,主要就是了解設計的原理和思想,當然也會碰到對一些主要函式的介紹,但大多就是該函式基於前面介紹的思想和原理完成了什麼樣的功能,該書並沒有就函式本身的實現進行深入剖析。然後再看ULK3和PLKA上看同樣的子系統,但是並不仔細分析底層具體函式的程式碼,只是粗略地、不求甚解地看,甚至不看。因為,有些時候,在其中一本書的某個點上,卡殼了,不是很理解了,在另外的書上你可能就碰到對同一個問題的不同角度的描述,說不準哪句話就能讓你豁然開朗,如醍醐灌頂。我經常碰到這種情況。
並不是說學習過程中對一些函式體的實現完全就忽略掉,只要自己想徹底瞭解其程式碼實現,沒有誰會阻止你。我是在反覆閱讀過程中慢慢深入的。比如VFS中檔案開啟需要對路徑進行分析,需要考慮的細節不少(.././之類的),但是其程式碼實現是很好理解的。再比如,CFS排程中根據shedule latency、佇列中程式個數及其nice值(使用的是動態優先順序)計算出分配給程式的時間片,沒理由不看的,這個太重要了,而且也很有意思。
ULK3也會有設計原理與思想之類的概括性介紹,基本上都位於某個主題的開篇段落。但是更多的是對支援該原理和思想的主要函式實現的具體分析,同樣在首段,一句話綜述函式的功能,然後對函式的實現以1、2、3,或者a、b、c步驟的形式進行講解。我只是有選擇性的看,有時候對照著用source insight開啟的原始碼,確認一下程式碼大體上確實是按書中所描述的步驟實現的,就當是增加感性認識。由於步驟中摻雜著各種針對不同實現目的安全性、有效性檢查,如果不理解就先跳過。這並不妨礙你對函式體功能實現的整體把握。
PLKA介於LKD3和ULK3之間。我覺得PLKA的作者(看照片,真一德國帥小夥,技術如此了得)肯定看過ULK,無論他的本意還是有意,總之PLKA還是跟ULK有所不同,對函式的仔細講解都做補充說明,去掉函式體中邊邊角角的情況,比如一些特殊情況的處理,有效性檢查等,而不妨礙對整個函式體功能的理解,這些他都有所交代,做了宣告;而且,就像LKD3一樣,在某些點上也給出了指導性程式設計建議。作者們甚至對同一個主要函式的講解的著重點都不一樣。這樣的話,對我們學習的人而言,有助於加深理解。另外,我認為很重要的一點就是PLKA針對的2.6.24的核心版本,而ULK是2.6.11,LKD3是2.6.34。在某些方面PLKA比較接近現代的實現。其實作者們之所以分別選擇11或者24,都是因為在版本發行樹中,這兩個版本在某些方面都做了不小的變動,或者說是具有標誌性的轉折點(這些資訊大多是在書中的引言部分介紹的,具體的細節我想不起來了)。
Intel V3,針對X86的CPU,本書自然是系統程式設計的權威。核心部分實現都可以在本書找到其根源。所以,在讀以上三本書某個子系統的時候,不要忘記可以在V3中相應章節找到一些基礎性支撐資訊。
在讀書過程中,會產生相當多的疑問,這一點是確信無疑的。 大到搞不明白一個設計思想,小到不理解某行程式碼的用途。各個方面,各種疑問,你完全可以把不理解的地方都記錄下來(不過,我並沒有這麼做,沒有把疑問全部記下來,只標記了很少一部分我認為很關鍵的幾個問題),專門寫到一張紙上,不對,一個本上,我確信會產生這麼多的疑問,不然核心相關的論壇早就可以關閉了。其實,大部分的問題(其中很多問題都是你知道不知道有這麼一回事的問題)都可以迎刃而解,只要你肯回頭再看,書讀百遍,其義自現。多看幾遍,前前後後的聯絡明白個七七八八是沒有問題的。我也這麼做了,針對某些子系統也看了好幾遍,切身體會。
當你按順序學習這些子系統的時候,前面的章節很可能會引用後面的章節,就像PLKA的作者說的那樣,完全沒有向後引用是不可能的,他能做的只是儘量減少這種引用而又不損害你對當前問題的理解。不理解,沒關係,跳過就行了。後面的章節同樣會有向前章節的引用,不過這個問題就簡單一些了 ,你可以再回頭去看相應的介紹,當時你不太理解的東西,很可能這個時候就知道了它的設計的目的以及具體的應用。不求甚解只是暫時的。比如說,核心各個子系統之間的互動和引用在程式碼中的體現就是實現函式穿插呼叫,比如你在記憶體管理章節學習了的記憶體分配和釋放的函式,而你是瞭解記憶體在先的,在學習驅動或者模組的時候就會碰到這些函式的呼叫,這樣也就比較容易接受,不至於太過茫然;再比如,你瞭解了系統時間和定時器的管理,再回頭看中斷和異常中bottom half的排程實現,你對它的理解就會加深一層。
子系統進行管理工作需要大量的資料結構。子系統之間互動的一種方式就是各個子系統各自的主要資料結構通過指標成員相互引用。學習過程中,參考書上在講解某個子系統的時候會對資料結構中主要成員的用途解釋一下,但肯定不會覆蓋全部(成員比較多的情況,例如task_struct),對其它子系統基於某個功能實現的引用可能解釋了,也可能沒做解釋,還可能說這個變數在何處會做進一步說明。所以,不要糾結於一個不理解的點上,暫且放過,回頭還可以看的。之間的聯絡可以在對各個子系統都有所瞭解之後再建立起來。其實,我仍然在強調先理解概念和框架的重要性。
等我們完成了建立框架這一步,就可以選擇一個比較感興趣的子系統,比如驅動、網路,或者檔案系統之類的。這個時候你再去深入瞭解底層程式碼實現,相較於一開始就鑽研程式碼,更容易一些,而且碰到了不解之處,或者忘記了某個方面的實現,此時你完全可以找到相應的子系統,因為你知道在哪去找,查漏補缺,不僅完成了對當前函式的鑽研,而且可以回顧、溫習以前的內容,融會貫通的時機就在這裡了。
《深入理解linux虛擬記憶體》(2.4核心版本),LDD3,《深入理解linux網路技術內幕》,幾乎每一個子系統都需要一本書的容量去講解,所以說,剛開始學習不宜對某個模組太過深入,等對各個子系統都有所瞭解了,再有針對性的去學習一個特定的子系統。這時候對其它系統的援引都可以讓我們不再感到茫然、複雜,不知所云。
比如,LDD3中的以下所列章節:構造和執行模組,併發和競態,時間、延遲及延緩操作,分配記憶體,中斷處理等,都屬於驅動開發的支撐性子系統,雖說本書對這些子系統都專門開闢一個章節進行講解,但是詳細程度怎麼能比得上PLKA,ULK3,LKD3這三本書,看完這三本書,你會發現讀LDD3這些章節的時候簡直跟喝白開水一樣,太隨意了,因為LDD3的講解比之LKD3更粗略。打好了基礎,PCI、USB、TTY驅動,塊裝置驅動,網路卡驅動,需要了解和學習的東西就比較有針對性了。這些子系統就屬於通用子系統,瞭解之後,基於這些子系統的子系統的開發—驅動(需進一步針對硬體特性)和網路(需進一步理解各種協議)—相對而言,其學習難度大大降低,學習進度大大加快,學習效率大大提升。說著容易做來難。達到這樣一種效果的前提就是:必須得靜下心來,認真讀書,要看得進去,PLKA,ULK3厚得都跟磚頭塊兒一樣,令人望之生畏,如果沒有興趣,沒有熱情,沒有毅力,無論如何都是不行,因為需要時間,需要很長時間。我並不是說必須打好了基礎才可以進行驅動開發,只是說打好了基礎的情況下進行開發會更輕鬆,更有效率,而且自己對核心程式碼的駕馭能力會更強大。這只是我個人見解,我自己的學習方式,僅供參考。
語言
PLKA是個德國人用德語寫的,後來翻譯成英文,又從英文翻譯成中文,我在網上書店裡沒有找到它的紙質英文版,所以就買了中文版的。ULK3和LKD3都是英文版的。大牛們寫的書,遣詞造句真的是簡潔,易懂,看原版對我們學習計算機程式設計的程式設計師來說完全不成問題,最好原汁原味。如果一本書確實翻譯地很好,我們當然可以看中文版的,用母語進行學習,理解速度和學習進度當然是很快的,不作他想。看英文的時候不要腦子裡想著把他翻譯成中文,沒必要。
API感想
“比起知道你所用技術的重要性,成為某一個特別領域的專家是不重要的。知道某一個具體API呼叫一點好處都沒有,當你需要他的時候只要查詢下就好了。”這句話源於我看到的一篇翻譯過來的部落格。我想強調的就是,這句話針應用型程式設計再合適不過,但是核心API就不完全如此。
核心相當複雜,學習起來很不容易,但是當你學習到一定程度,你會發現,如果自己打算寫核心程式碼,到最後要關注的仍然是API介面,只不過這些API絕大部分是跨平臺的,滿足可移植性。核心黑客基本上已經標準化、文件化了這些介面,你所要做的只是呼叫而已。當然,在使用的時候,最好對可移植性這一話題在核心中的編碼約定爛熟於心,這樣才會寫出可移植性的程式碼。就像應用程式一樣,可以使用開發商提供的動態庫API,或者使用開源API。同樣是呼叫API,不同點在於使用核心API要比使用應用API瞭解的東西要多出許多。
當你瞭解了作業系統的實現—這些實現可都是對應用程式的基礎性支撐啊—你再去寫應用程式的時候,應用程式中用到的多執行緒,定時器,同步鎖機制等等等等,使用共享庫API的時候,聯絡到作業系統,從而把對該API的文件描述同自己所瞭解到的這些方面在核心中的相應支撐性實現結合起來進行考慮,這會指導你選擇使用哪一個API介面,選出效率最高的實現方式。對系統程式設計頗有了解的話,對應用程式設計不無益處,甚至可以說是大有好處。
設計實現的本質,知道還是理解
作業系統是介於底層硬體和應用軟體之間的介面,其各個子系統的實現很大程度上依賴於硬體特性。書上介紹這些子系統的設計和實現的時候,我們讀過了,也就知道了,如果再深入考慮一下,為什麼整體架構要按照這種方式組織,為什麼區域性函式要遵循這樣的步驟處理,知其然,知其所以然,如果你知道了某個功能的實現是因為晶片就是這麼設計的,CPU就是這麼做的,那麼你的疑問也就基本上到此為止了。再深究,就是晶片架構方面的設計與實現,對於程式設計師來講,無論是系統還是應用程式設計師,足跡探究到這裡,已經解決了很多疑問,因為我們的工作性質偏軟,而這些東西實在是夠硬。
比如,ULK3中講解的中斷和異常的實現,究其根源,那是因為Intel x86系列就是這麼設計的,去看看Intel V3手冊中相應章節介紹,都可以為ULK3中描述的程式碼實現方式找到註解。還有時間和定時器管理,同樣可以在Intel V3 對APIC的介紹中獲取足夠的資訊,作業系統就是依據這些硬體特性來實現軟體方法定義的。
又是那句話,不是理解不理解的問題,而是知道不知道的問題。有時候,知道了,就理解了。在整個學習過程中,知道,理解,知道,理解,知道……,交叉反覆。為什麼開始和結尾都是知道,而理解只是中間步驟呢?世界上萬事萬物自有其規律,人類只是發現而已,實踐是第一位的,實踐就是知道的過程,實踐產生經驗,經驗的總結就是理論,理論源於實踐,理論才需要理解。我們學習核心,深入研究,搞來搞去,又回到了晶片上,晶片是物質的,晶片的功用基於自然界中物質本有的物理和電子特性。追本溯源,此之謂也。
動手寫程式碼
紙上得來終覺淺,絕知此事要躬行。只看書是絕對不行的,一定要結合課本給出的程式設計建議自己敲程式碼。剛開始就以模組形式測試好了,或者自己編譯一個開發版本的核心。一臺機器的話,使用UML方式除錯,核心控制路走到哪一步,單步除錯看看程式執行過程,比書上的講解更直觀明瞭。一定要動手實際操作。
參考書
UML User Mode Linux
The level of detail sometimes makes it hard to get a sense for the big picture, but it does help somebody trying to figure out how a particular function works.
對程式碼講解的詳細程度有時候很難讓讀者把握住它的主旨大意,但是確實有助於讀者理解一個特定的函式到底是如何工作的。
Indeed, that is perhaps the key feature which differentiates this book. It is very much a “how it works” book, designed to help people understand the code.
事實上,這也正是本書與眾不同的地方。更像一個“如何工作”的書,幫助讀者理解程式碼實現。
It presents kernel functions and data structures, steps the reader through them, but does not, for example, emphasize the rules for using them. UTLK is a study guide, not a programming manual.
本書描述了核心函式和資料結構,引導讀者穿行於其間,但是,並沒有著重強調使用它們的法則。UTLK是一本學習指南,而不是程式設計手冊。
這幾句話對本書的描述非常到位。基於此,作為指導性原則,我們就可以很有效率地使用它了。
看一本技術書籍,書中的序言部分絕對是首先應該翻閱的,其次就是目錄。我發現在閱讀過程中我會頻繁的檢視目錄,甚至是喜歡看目錄。
結尾
興趣的力量是無窮的。興趣能帶來激情,如果工作可以和興趣結合到一起,工作起來才會有熱情,那麼工作就不只是工作了,更是一種享受。
Linux,我的興趣,我的動力,我的方向,我的未來!