首頁  >  文章  >  系統教程  >  深入解析 Linux 核心源碼,探索作業系統本質

深入解析 Linux 核心源碼,探索作業系統本質

WBOY
WBOY原創
2024-08-08 09:33:081040瀏覽

深入解析 Linux 内核源码,探索操作系统本质

一、內核源碼之我見

Linux核心程式碼的龐大令不少人“望而生畏”,也正由於這般,促使人們對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 mint,很容易深陷程式碼細節的泥沼中。核心程式碼畢竟龐大,而且它也有它的設計原則和架構,否則維護它對任何人來說都是一個惡夢!假如我們理清程式碼模組的整體設計思路,​​再去剖析程式碼的實現,可能剖析原始碼就是一件輕鬆快樂的事情了。

linux内核源码剖析_linux内核源码是什么语言_linux内核源码分析

針對那些問題,我個人是這樣理解的。如果沒有接觸過小型軟體項目,可能剖析Linux核心程式碼是個挺好的累積小型專案經驗的機會(確實,Linux程式碼是我目前接觸到的最大的專案了!)。假如你對電腦底層了解的不夠透徹,這麼我們可以選擇邊剖析邊學習的方法去累積底層的知識。可能剛開始剖析程式碼的進度會稍顯遲鈍,然而隨著知識的不斷積累,我們對Linux核心的「業務邏輯」會漸漸明朗上去。最後一點,怎麼從全局的角度掌握剖析的源碼,這也是我想與你們分享的經驗。

三、內核原始碼剖析方式

第一步:資料收集

從人認識新事物的角度來講,在探求事物本質之前,必須有一個了解新鮮事物的過程,這個過程是的我們對新鮮事物形成一個初步的概念。例如我們想學習吉他,這麼我們必須先了解演奏吉他鬚要我們學習基本的視唱、簡譜、五線譜等基礎知識,然後學習二胡演奏的方法和指法,最後才會真正的開始練習吉他。

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

定位好要剖析的程式碼範圍linux記憶體管理,我們就可以動手用手頭的一切資源,盡可能的全面了解該部分程式碼的整體結構和大致功能。

源码

這兒所說的一切資源是指無論是Baidu、Google小型網路搜尋引擎,還是作業系統原理教材和專業書籍,亦或是別人提供的經驗和資料,甚至是Linux源碼提供的文檔、註釋和原始碼標示符的名稱(不要小看程式碼中的標示符的命名,有時它們能提供關鍵的資訊)。其實這兒的一切資源指的就是你能想到的一切可用資源。其實,我們不太可能透過這些方式的資訊收集來獲得所有的我們想要的信息,我們但求盡可能全面即可。由於資訊收集的越全面,然後剖析程式碼的過程能使用的資訊就更多,剖析過程的困難就會越小。

這兒舉一個簡單的反例,假設我們要剖析Linux的變頻機制實現的程式碼。目前為止我們僅僅是曉得這個名詞而已,透過字面含意我們可以大致推測它應當和CPU的頻度調節相關。透過資訊收集,我們應當能得到如下的相關的資訊:

1. CPUFreq機制。

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

3. /driver/cpufreq/。

linux内核源码是什么语言_linux内核源码剖析_linux内核源码分析

4. /documention/cpufreq。

5. Pstate和Cstate。

……

剖析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(LinuxKernelMailList)查閱,實在不行我們還可以到提問尋求解答(想了解哪些是LKML和stackoverflow?收集資料吧!)。其實借助所有可能的手段,我們總是可以得到這個宏的涵義──為每位CPU定義一個獨立使用的變數。

linux内核源码剖析_linux内核源码是什么语言_linux内核源码分析

我們也不要強求一次能夠把註釋描述的很確切(我們甚至都沒必要弄清每位函數的具體實現流程,只要弄清大致功能涵義即可),我們結合收集到的資料和上面程式碼的剖析不斷的建立註釋的涵義(源碼中原有的註釋和標示符命名在此很有借助價值)。透過不斷的註釋,不斷的查閱資料,不斷的更改註釋的含義。

源码

當我們把所有涉及的源碼文件簡單註解完畢後我們可以達到如下療效:

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”聲明的函數,通常都在文件最後)linux內核源碼剖析,按照它們調用的函數(自己定義的或則其他模組的函數)和使用的關鍵變數(本文件內的全域變數或則其他模組的外部變數)畫出「函數-變數-函數」依賴關係圖-我們稱為標示符依賴關係圖。

其實,模組內標示符依賴關係並非是單純的樹狀結構,好多情況是紛繁複雜的網路關係。這時侯,我們對程式碼的詳盡註解的作用就彰顯下來了。我們依照函數本身的含意,將模組進行子功能界定,抽取出每位子功能的標示符依賴樹。

源码

透過標示符依賴關係剖析,可以很清晰的展示模組定義的函數呼叫了這些函數,使用了什麼變量,以及模組子功能之間的依賴關係-公用了什麼函數和變數等。

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

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

linux内核源码是什么语言_linux内核源码剖析_linux内核源码分析

源码

cpufreq程式碼的模組相依性可以表示為如下關係。

源码

第七步:模組架構圖

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

源码

如cpufreq的模組依賴關係圖所示,我們可以很清楚的看見所有的調頻策略模組都是依賴核心模組cpufreq、cpufreq_stats和freq_table的。假如我們把被依賴的三個模組具象為程式碼的核心框架的話,那些調頻策略模組都是建構在這個框架之上的,它們負責和使用者層互動。而核心模組cpufreq提供了驅動等相關的插口負責與系統底層互動。因而,我們可以得到如下的模組架構圖。

源码

其實,架構圖並非模組的無機拼接,我們還要結合查閱的資料去豐富架構圖的含意。因而,這兒的架構圖的細節會隨著不同的人的理解而有所誤差。且架構圖主體的含意很基本一致的。至此,我們完成了待剖析的核心程式碼的所有剖析工作。

四、總結

正如文章開始所說,我們不可能對全部的核心程式碼進行剖析。因而,透過對待剖析的程式碼進行資訊收集,之後根據上述的流程剖析出程式碼的原先始末是了解內核本質的有效手段。這些根據具體須要剖析核心程式碼的形式,為快速步入Linux核心的世界提供了可能。透過這些方法,不斷的對內核的其他模組剖析,最後綜合得到自己對Linux內核的理解,也就達到了我們學習Linux內核的目的。

最後推薦給你們兩本學習核心的參考書。一本是《Linux核心的設計與實作》,為讀者快速精簡的介紹了Linux核心的主要功能與實作。但不會把讀者帶入Linux核心程式碼的深淵中,是了解核心架構和入門Linux核心程式碼的特別好的參考書,同時該書會增強讀者對核心程式碼的興趣。另一本是《深入理解Linux核心》,該書的精典我何必多說。我只是建議,若果想更好的學習本書,最好是結合核心程式碼一起閱讀。因為這本書對內核程式碼描述的非常詳盡,所以結合程式碼進行閱讀可以幫助我們更好的理解內核程式碼。同時,在剖析核心程式碼的過程中,也可以在本書中找到具有參考價值的資料。最後,願你們盡快步入內核的世界,體驗Linux帶給我們的驚喜!

以上是深入解析 Linux 核心源碼,探索作業系統本質的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn