linux核心的原始碼放在/usr/src/linux目錄下。核心原始碼的組成:1、arch目錄,包含了此核心原始碼所支援的硬體體系結構相關的核心程式碼;2、include目錄,包括了核心的大多數include檔案;3、init目錄,包含核心啟動程式碼;4、mm目錄,包含所有的記憶體管理程式碼;5、drivers目錄,包含系統中所有的裝置驅動程式;6、Ipc目錄,包含核心的進程間通訊程式碼。
本教學操作環境:linux7.3系統、Dell G3電腦。
linux核心的原始碼放在哪裡
Linux的核心原始碼可以從很多途徑得到。一般來講,在安裝的linux系統下,/usr/src/linux目錄下的東西就是核心原始碼。
對於原始程式碼的閱讀,要想比較順利,事先最好對原始程式碼的知識背景有一定的了解。
Linux核心原始碼的組成如下(假設相對於linux目錄):
##arch
這個子目錄包含了此核心原始碼所支援的硬體體系結構相關的核心程式碼。如對於X86平台就是i386。include
這個目錄包含了核心的大多數include檔案。另外對於每種支援的體系結構分別有一個子目錄。init
此目錄包含核心啟動程式碼。mm
此目錄包含了所有的記憶體管理程式碼。與具體硬體體系結構相關的記憶體管理程式碼位於arch/*/mm目錄下,如對應於X86的就是arch/i386/mm/fault.c 。drivers
系統中所有的裝置驅動程式都位於此目錄中。它又進一步劃分成幾類裝置驅動,每一種也有對應的子目錄,如音效卡的驅動對應於drivers/sound。Ipc
此目錄包含了核心的進程間通訊程式碼。modules
此目錄包含已建置可動態載入的模組。fs Linux
支援的檔案系統程式碼。不同的檔案系統有不同的子目錄對應,如ext2檔案系統對應的就是ext2子目錄。Kernel
主要核心程式碼。同時與處理器結構相關程式碼都放在arch/*/kernel目錄下。Net
核心的網路部分程式碼。裡面的每個子目錄對應於網路的一個面向。Lib
此目錄包含了核心的函式庫程式碼。與處理器結構相關函式庫程式碼被放在arch/*/lib/目錄下。Scripts
此目錄包含用於設定核心的腳本檔案。Documentation
此目錄是一些文檔,起參考作用。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)查閱。總之利用所有可能的手段,我們總是能得到這個宏的意義──為每個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核心的源碼放在哪個文件的詳細內容。更多資訊請關注PHP中文網其他相關文章!