Linux核心源碼分析方法

來源:互聯網
上載者:User

一、核心源碼之我見

Linux核心代碼的龐大令不少人“望而生畏”,也正因為如此,使得人們對Linux的瞭解僅處於泛泛的層次。如果想透析Linux,深入作業系統的本質,閱讀核心源碼是最有效途徑。我們都知道,想成為優秀的程式員,需要大量的實踐和代碼的編寫。編程固然重要,但是往往只編程的人很容易把自己局限在自己的知識領域內。如果要擴充自己知識的廣度,我們需要多接觸其他人編寫的代碼,尤其是水平比我們更高的人編寫的代碼。通過這種途徑,我們可以跳出自己知識圈的束縛,進入他人的知識圈,瞭解更多甚至我們一般短期內無法瞭解到的資訊。Linux核心由無數開源社區的“大神們”精心維護,這些人都可以稱得上一頂一的代碼高手。透過閱讀Linux核心代碼的方式,我們學習到的不光是核心相關的知識,在我看來更具價值的是學習和體會它們的編程技巧以及對電腦的理解。

我也是通過一個項目接觸了Linux核心源碼的分析,從源碼的分析工作中,我受益頗多。除了擷取相關的核心知識外,也改變了我對核心代碼的過往認知:

1.核心源碼的分析並非“高不可攀”。核心源碼分析的難度不在於源碼本身,而在於如何使用更合適的分析代碼的方式和手段。核心的龐大致使我們不能按照分析一般的demo程式那樣從主函數開始按部就班的分析,我們需要一種從中間介入的手段對核心源碼“各個擊破”。這種“按需索取”的方式使得我們可以把握源碼的主線,而非過度糾結於具體的細節。

2.核心的設計是優美的。核心的地位的特殊性決定著核心的執行效率必須足夠高才可以響應目前電腦應用的即時性要求,為此Linux核心使用C語言和彙編的混合編程。但是我們都知道軟體執行效率和軟體的可維護性很多情況下是背道而馳的。如何在保證核心高效的前提下提高核心的可維護性,這需要依賴於核心中那些“優美”的設計。

3.神奇的編程技巧。在一般的應用軟體設計領域,編碼的地位可能不被過度的重視,因為開發人員更注重軟體的良好設計,而編碼僅僅是實現手段問題——就像拿斧子劈柴一樣,不用太多的思考。但是這在核心中並不成立,好的編碼設計帶來的不光是可維護性的提高,甚至是代碼效能的提升。

每個人對核心的了理解都會有所不同,隨著我們對核心理解的不斷加深,對其設計和實現的思想會有更多的思考和體會。因此本文更期望於引導更多徘徊在Linux核心大門之外的人進入Linux的世界,去親自體會核心的神奇與偉大。而我也並非核心源碼方面的專家,這麼做也只是希望分享我自己的分析源碼的經驗和心得,為那些需要的人提供參考和協助,說的“冠冕堂皇”一點,也算是為電腦這個行業,尤其是在作業系統核心方面貢獻自己的一份綿薄之力。閑話少敘(已經羅嗦了很多了,囧~),下面我就來分享一下自己的Linix核心源碼分析方法。

二、核心源碼難不難?

從本質上講,分析Linux核心代碼和看別人的代碼沒有什麼兩樣,因為擺在你面前的一般都不是你自己寫出來的代碼。我們先舉一個簡單的例子,一個陌生人隨便給你一個程式,並要你看完源碼後講解一下程式的功能的設計,我想很多自我感覺編程能力還可以的人肯定覺得這沒什麼,只要我耐心的把他的代碼從頭到尾看完,肯定能找到答案,並且事實確實是如此。那麼現在換一個假設,如果這個人是Linus,給你的就是Linux核心的一個模組的代碼,你還會覺得依然那麼輕鬆嗎?不少人可能會有所猶豫。同樣是陌生人(Linus要是認識你的話當然不算,呵呵~)給你的代碼,為什麼給我們的感覺大相徑庭呢?我覺得有以下原因:

1.Linux核心代碼在“外界”看來多少有些神秘感,而且它很龐大,猛地擺在面前可能感覺無法下手。比如可能來源於一個很細小的原因——找不到main函數。對於簡單的demo程式,我們可以從頭至尾的分析代碼的含義,但是分析核心代碼這招就徹底失效了,因為沒有人能把Linux代碼從頭到尾看上一遍(因為確實沒有必要,用到時看就可以了)。

2.不少人也接觸過大型軟體的代碼,但多數屬於應用型項目,代碼的形式和含義都和自己常接觸的商務邏輯相關。而核心代碼不同,它處理的資訊多數和電腦底層密切相關。比如作業系統、編譯器、彙編、體繫結構等相關的知識的欠缺,也會讓閱讀核心代碼障礙重重。

3.分析核心代碼的方法不夠合理。面對大量的並且複雜的核心代碼,如果不從全域的角度入手,很容易陷入代碼細節的泥淖中。核心代碼雖然龐大,但是它也有它的設計原則和架構,否則維護它對任何人來說都是一個噩夢!如果我們理清代碼模組的整體設計思路,再去分析代碼的實現,可能分析源碼就是一件輕鬆快樂的事情了。

針對這些問題,我個人是這樣理解的。如果沒有接觸過大型軟體項目,可能分析Linux核心代碼是一個很好的積累大型項目經驗的機會(確實,Linux代碼是我目前接觸到的最大的項目了!)。如果你對電腦底層瞭解的不夠透徹,那麼我們可以選擇邊分析邊學習的方式去積累底層的知識。可能剛開始分析代碼的進度會稍顯遲緩,但是隨著知識的不斷積累,我們對Linux核心的“商務邏輯”會逐漸明朗起來。最後一點,如何從全域的角度把握分析的源碼,這也是我想與大家分享的經驗。

三、核心源碼分析方法

第一步:資料搜集

從人認識新事物的角度來講,在探索事物本質之前,必須有一個瞭解新鮮事物的過程,這個過程是的我們對新鮮事物產生一個初步的概念。比如我們想學習鋼琴,那麼我們需要先瞭解彈奏鋼琴需要我們學習基本的樂理、簡譜、五線譜等基礎知識,然後學習鋼琴彈奏的技巧和指法,最後才能真正的開始練習鋼琴。

分析核心代碼也是如此,首先我們需要定位要分析的代碼涉及的內容。是進程同步和調度的代碼,是記憶體管理的代碼,還是裝置管理的代碼,還是系統啟動的代碼等等。核心的龐大決定著我們不能一次性將核心代碼全部分析完成,因此我們需要給自己一個合理的分工。正如演算法設計告訴我們的,要解決一個大問題,首先要解決它所涉及的子問題。

定位好要分析的代碼範圍,我們就可以動用手頭的一切資源,儘可能的全面瞭解該部分代碼的整體結構和大致功能。

 

 

這裡所說的一切資源是指無論是Baidu、Google大型網路搜尋引擎,還是作業系統原理教材和專業書籍,亦或是他人提供的經驗和資料,甚至是Linux源碼提供的文檔、注釋和源碼標識符的名稱(不要小看代碼中的標識符的命名,有時它們能提供關鍵的資訊)。總之這裡的一切資源指的就是你能想到的一切可用資源。當然,我們不太可能通過這種形式的資訊搜集獲得所有的我們想要的資訊,我們只求儘可能全面即可。因為資訊搜集的越全面,之後分析代碼的過程能使用的資訊就更多,分析過程的困難就會越小。

這裡舉一個簡單的例子,假定我們要分析Linux的變頻機制實現的代碼。目前為止我們僅僅是知道這個名詞而已,透過字面含義我們可以大致猜測它應該和CPU的頻率調節相關。通過資訊搜集,我們應該能得到如下的相關的資訊:

1.CPUFreq機制。

2.performance、powersave、userspace、ondemand、conservative調頻策略。

3./driver/cpufreq/。

4./documention/cpufreq。

5.P state和C state。

……

分析Linux核心代碼如果能搜集到這些資訊,應該說是非常“幸運”了。畢竟有關Linux核心的資料確實不如.NET和JQuery那麼豐富,不過這相比於十數年前,沒有強大的搜尋引擎,沒有相關的研究資料的時期應該稱得上是“大豐收”時代了!我們通過簡單的“搜尋”(可能會花費一到兩天的時間吧),甚至找到了這部分代碼所在的源碼檔案目錄,不得不說這樣的資訊簡直是“價值連城”!

第二步:源碼定位

從資料搜集中,我們“有幸”找到了源碼相關的源碼目錄。但是這並非意味著我們的確就是分析這個目錄下的原始碼。有時我們找到的目錄有可能是分散的,也有時我們找到的目錄下有很多和具體機器相關的代碼,而我們更關心的是待分析代碼的主要機制,而非與機器相關的特化代碼(這樣更有助於我們理解核心的本質)。因此,我們需要對資料中涉及代碼檔案的資料進行仔細甄選。當然,這一步也不太可能一次性完成,誰也不能保證一次就能選擇出所有待分析的源碼檔案而且一個不漏。但是我們也不必擔心,只要我們能抓住大多數模組相關的核心源檔案,通過後期對代碼的具體分析,就很自然的把它們全部找出來。

回到上述的例子中,我們認真的閱讀/documention/cpufreq下的文檔說明。目前的Linux源碼會把模組相關的文檔說明儲存在源碼目錄的documention的檔案夾下,如果待分析的模組沒有文檔說明,這多少會增加定位關鍵源碼檔案的難度,但是不會導致我們找不到我們要分析的源碼。通過閱讀文檔說明,我們至少能關注到/driver/cpufreq/cpufreq.c這個源檔案。通過這個對源檔案的文檔說明,結合之前搜羅到的調頻策略,我們很容易關注到cpufreq_performance.c、cpufreq_powersave.c、cpufreq_userspace.c、cpufreq_ondemand、cpufreq_conservative.c這五個源檔案。所有涉及的檔案都找完了嗎?不用擔心,從它們開始分析,遲早能找到其他的源檔案。如果在windows下使用sourceinsight閱讀核心源碼的話,我們通過函數的調用和尋找符號引用等功能,結合代碼的分析可以很方便的找到另外的檔案freq_table.c、cpufreq_stats.c和/include/linux/cpufreq.h。

 

 

按照搜尋出的資訊流動方向,我們完全可以定位到需要分析的源碼檔案。源碼定位這一步並非十分關鍵,因為我們不需要找出所有源碼檔案,我們可以把部分工作延遲到分析代碼的過程中。源碼定位也比較關鍵,找到一部分源碼檔案是分析源碼的基礎。

第三步:簡單注釋

在已定位好的源碼檔案中,分析每個變數、宏、函數、結構體等代碼元素的大致含義和功能。之所以稱此為簡單注釋,並非指這部分的注釋工作很簡單,而是指這部分的注釋可以不必過分細化,只要大致描述出相關代碼元素的含義即可。相反,這裡的工作其實是整個分析流程中最困難的一步。因為這是第一次深入到核心代碼的內部,尤其是對於首次分析核心源碼的人來說,大量的生疏GNU的C文法和鋪天蓋地的宏定義會令人很絕望。此時只要沉下心來,弄清每個關鍵的痛點,才能保證以後碰到類似的痛點不會再被困住。而且,我們對核心相關的其他知識會不斷的像樹一樣擴充開來。

比如在cpufreq.c檔案開始就會出現“DEFINE_PER_CPU”宏的使用,我們通過查閱資料可以基本弄清這個宏的含義和功能。這裡使用的手段和之前搜集資料使用的方法基本一致,另外我們也可以使用sourceinsight提供的轉到定義等功能查看它的定義,或者使用LKML(Linux Kernel Mail List)查閱,實在不行我們還可以到www.stackoverflow.com提問尋求解答(想瞭解什麼是LKML和stackoverflow?搜集資料吧!)。總之利用所有可能的手段,我們總能得到這個宏的含義——為每個CPU定義一個獨立使用的變數。

我們也不要強求一次就能把注釋描述的很準確(我們甚至都沒必要弄清每個函數的具體實現流程,只要弄清大致功能含義即可),我們結合搜集到的資料和後邊代碼的分析不斷的完善注釋的含義(源碼中原有的注釋和標識符命名在此很有利用價值)。通過不斷的注釋,不斷的查閱資料,不斷的修改注釋的含義。

 

 

當我們把所有涉及的源碼檔案簡單注釋完畢後我們可以達到如下效果:

1.基本弄清了源碼中代碼元素存在的含義。

2.找出了該模組所涉及的基本上全部的關鍵源碼檔案。

結合之前搜集到的資訊和資料對該待分析代碼的整體或者架構描述,我們可以將分析的結果和資料對比,以確定和修正我們對代碼的理解。這樣,通過一遍的簡單注釋,我們就可以從整體上把握了源碼模組的主要結構。這也達到了我們簡單注釋的基本目的。

第四步:詳細注釋

完成代碼的簡單注釋後,可以認為對模組的分析工作完成了一半了,剩下的內容就是對代碼的深入分析和徹底理解。簡單注釋總是不能將代碼元素的具體含義描述的十分精確,因此詳細注釋是十分有必要的。這一步中,我們需要弄清以下內容:

1.變數定義在何時被使用。

2.宏定義的代碼何時被使用。

3.函數的參數和傳回值的含義。

4.函數的執行流程和調用關係。

5.結構體欄位的具體含義和使用條件。

我們甚至可以把這一步稱為函數詳細注釋,因為函數之外的代碼元素的含義基本上在簡單注釋中已經比較明確了。而函數本身的執行流程、演算法等是這部分注釋和分析的主要任務。

比如cpufreq_ondemand策略的實現演算法(函數dbs_check_cpu中)是如何?的。我們需要逐步分析該函數使用的變數和調用的函數等資訊,弄清演算法的來龍去脈。最好的結果,我們需要這些複雜函數的執行流程圖和函數呼叫歷程圖,這是最直觀的表達方式。

 

 

通過這一步的注釋,我們基本上能完全把握待分析代碼整體的實現機制了。而所有的分析工作可以認為完成了80%。這一步工作尤其關鍵,我們必須盡量讓注釋的資訊足夠的準確,才能更好的理解待分析代碼的內部模組的劃分。雖然Linux核心中使用了宏文法“module_init”和“module_exit”聲明模組檔案,但是對模組內部子功能的劃分是建立在充分瞭解模組的功能基礎上的。只有正確劃分好模組,我們才能弄清模組提供了哪些外部函數和變數(使用EXPORT_SYMBOL_GPL或者EXPORT_SYMBOL匯出的符號)。才能繼續下一步的模組內標識符依賴關係分析。

第五步:模組內部標識符依賴關係

通過第四步對代碼模組的劃分,我們就可以很“輕鬆”地逐個對模組進行分析。一般的,我們可以從檔案底部的模組出入口函數開始(“module_init”和“module_exit”聲明的函數,一般都在檔案最後),根據它們調用的函數(自己定義的或者其他模組的函數)和使用的關鍵變數(本檔案內的全域變數或者其他模組的外部變數)畫出“函數-變數-函數”依賴關係圖——我們稱為標識符依賴關係圖。

當然,模組內標識符依賴關係並非是單純的樹形結構,很多情況是錯綜複雜的網路關係。這時候,我們對代碼的詳細注釋的作用就體現出來了。我們根據函數本身的含義,將模組進行子功能劃分,抽取出每個子功能的標識符依賴樹。

 

 

通過標識符依賴關係分析,可以很清晰的展示模組定義的函數調用了那些函數,使用了哪些變數,以及模組子功能之間的依賴關係——公用了哪些函數和變數等。

第六步:模組間相互依賴關係

一旦將所有的模組內部標識符依賴關係圖整理完畢,根據模組使用的其他模組的變數或函數,可以很容易得到模組之間的依賴關係。

 

 

cpufreq代碼的模組依賴關係可以表示為如下關係。

 

 

第七步:模組架構圖

透過模組間的依賴關係圖,可以很清楚的表達模組在整個待分析代碼中的地位和功能。基於此,我們可以將模組分類,整理出代碼的架構關係。

 

 

如cpufreq的模組依賴關係圖所示,我們可以很清楚的看到所有的調頻原則模組都是依賴於核心模組cpufreq、cpufreq_stats和freq_table的。如果我們把被依賴的三個模組抽象為代碼的核心架構的話,這些調頻原則模組都是建立在這個架構之上的,它們負責和使用者層互動。而核心模組cpufreq提供了驅動等相關的介面負責與系統底層互動。因此,我們可以得到如下的模組架構圖。

 

 

當然,架構圖並非模組的無機拼接,我們還需要結合查閱的資料去豐富架構圖的含義。因此,這裡的架構圖的細節會隨著不同的人的理解有所偏差。但是架構圖主體的含義很基本一致的。至此,我們完成了待分析的核心代碼的所有分析工作。

四、總結

正如文章開始所說,我們不可能對全部的核心代碼進行分析。因此,通過對待分析的代碼進行資訊搜集,然後按照上述的流程分析出代碼的原本始末是瞭解核心本質的有效手段。這種按照具體需要分析核心代碼的方式,為快速進入Linux核心的世界提供了可能。通過這種方式,不斷的對核心的其他模組分析,最後綜合得到自己對Linux核心的理解,也就達到了我們學習Linux核心的目的。

最後向大家推薦兩本學習核心的參考書。一本是《Linux核心的設計與實現》,該書為讀者快速精簡的介紹了Linux核心的主要功能和實現。但不會把讀者帶入Linux核心代碼的深淵中,是瞭解核心架構和入門Linux核心代碼的非常好的參考書,同時該書會提高讀者對核心代碼的興趣。另一本是《深入理解Linux核心》,該書的經典我不必多說。我只是建議,如果想更好的學習本書,最好是結合著核心代碼一起閱讀。由於這本書對核心代碼描述的十分詳細,所以結合代碼進行閱讀可以協助我們更好的理解核心代碼。同時,在分析核心代碼的過程中,也可以在本書中找到具有參考價值的資料。最後,願大家早日進入核心的世界,體驗Linux帶給我們的驚喜!

 

 

來源:http://www.cnblogs.com/fanzhidongyzby/archive/2013/03/20/2970624.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.