談談如何學習Linux內核

學習內核的好處很多,在學習過程中不僅可以夯實大量理論基礎,還可以學習到很多編碼慣用法,提升學習能力和分析能力。


一、確立高度,明確目標

高度決定視角,視角決定行動

在學習內核過程中最容易犯的錯誤,也是非常難掌握的其實是你站在一個什麼樣的高度上去學習。站什麼樣的高度去學習也與自身的能力相關,所以這個問題其實更多是在新開始學習的學習者感到非常痛苦的一件事。一方面又希望自己能學懂,但是又不知道如何開始入手。

我列舉幾個常見的例子:

(1) 一開始就看源碼,最開始我也做過這種事,內核有什麼都不知道,結果就想著啃0.11的內核,結果很顯然,2天立馬放棄,完全看不懂。

(2) 翻開書從第一頁開始往下啃,如果這本書比較薄還好,如果比較厚,比如《深入Linux內核架構》,那看2天也得放棄。

(3) 不喜歡看目錄,不喜歡快速瀏覽,就想著一個一個字眼的往下摳。如果本身有一定基礎,看的時候還不會覺得膩,但是很快就發現,看了半天,什麼都沒有記住。

還有很多類似的問題,這些都是我們平時學習的時候特別容易出現的一些誤區。這些其實都是沒有正確審視自己的能力,胡亂挑選高度導致。

高度是什麼?

高度越高,也就是越偏向於理解各種抽象概念,傾向於構建對整體結構的一個認知,忽略一些不必要細節,不關心技術實現手段。高度越低,也就是越偏向於對使用技術的選擇,傾向於代碼實現的各個細節,但是前提一般會在某個抽象的概念領域內進行各種細節性的討論。

我們的大腦更傾向於理解抽象的內容,但是在行動時,我們卻更傾向於去把握細節性的內容。結果導致的內容就是,我們總是希望通過學習細節來構造對抽象概念的理解,最後被細節性內容中各種噪聲干擾思緒,產生一種“這東西好難”的錯覺。在理解了這點,那高度對我們的行為有和指導意義也就呼之欲出了?
以讀書為例。站的越高,意味著自己看的內容越粗糙,也就是看書的時候不會去逐字逐句的看,而是一個章節一個章節的看,極端的情況就是隻看目錄,在這個過程中主要集中精力構建整體結構,對核心的概念進行抽象。這時候學的內容都相對錶面,但是好處就是對以後的學習有很強的指導意義,缺點很明顯,會讓人底氣不足,而且在達到一定程度後,很容易到達瓶頸,發覺怎麼看都看不懂了。

站的越低,意味著自己看的內容很細緻,看書的時候就是一個個字眼的扣,極端情況就是開始閱讀源碼,去看開源社區的各種問題。但是就像詩句說的,站的越低,也就常有一種“不識廬山真面目,只緣身在此山中”的感覺。這種情況下特別容易被各種細節干擾,例如為什麼要有這些參數,為什麼這裡需要判斷這個條件等等這些細枝末節的問題。

如何運用高度

以前對一篇博文印象很深刻,作者理解的學習曲線劃分成了兩個比較大的過程,上升的過程就是一個不斷學習積累的過程,而平緩沒有增長的過程則是對之前積累到飽和的知識進行消化的過程。我將這個學習過程進行進一步的劃分,我覺得在學習積累的前半部分應該以偏向學習抽象概念為主,而後半部分應該偏向學習實現細節。


談談如何學習Linux內核


所以個人的心得是從高到低的學習,在一個新的學習階段,應該先多花點時間學習一些概念化的內容,這時候切忌去看具體的實現,而是多考慮如何在大腦中構建各種抽象模型

對整體的架構有所概念了,然後開始學習一些細節性的內容,比如開始看些源碼,摳寫書上的字眼,讀讀一些具體的博客什麼的。


二、學習小Tips


1.如何看書


不要從第一頁開始翻 不要一頁一頁的翻

  • 花些時間看看前言,在很多書的前言部分,作者會告訴你,整本書的結構應該是什麼樣,應該要以什麼樣的順序去閱讀,在閱讀的時候應該站在什麼樣的角度去閱讀,這是作者的建議,有什麼比作者的建議更值得我們聽取呢!?
  • 不要寄希望與一次看懂一本書,越是好的書越是要反覆的看,但是很多人對這個反覆理解有問題,認為反覆的看就是一頁頁翻,重複看幾遍。其實不是這樣,每次反覆應該讓自己換一個高度,第一次翻的時候可以站在很高的高度,看一本書甚至只需要1天的時間,重複幾次後,站的高度應該越低,很可能看一個章節需要1天時間,甚至有時候看一頁就需要1天的時間。
  • 一本書的目錄就像你在沙漠中的指南針,不要忽略目錄的作用。每次翻開書,在決定自己看什麼之前,花點時間瀏覽下目錄,讓自己回憶(瞭解)要看的章節的架構,帶著這個結構去學習事半功倍。
  • 帶著問題去看書,這點很難,因為提什麼樣的問題和你選擇的高度密切相關,站的高度越高,那就越不要給自己提一些細節性的問題,反之則反之。

2.如何看代碼

如果開始看代碼,一定要記住,自己已經站在一個非常底層的高度度了,能夠有能力閱讀代碼,就意味著你必須對整體的結構有比較清晰的認識,如果你都不知道這個結構,那看代碼為時太早。

無論是什麼樣的代碼,其實思路都很類似,即使Linux內核是用C這種面相過程的語言編寫,但是這麼多年發展下來,Linux內核已經帶有了大量面對對象編程的特點。

在看代碼的時候也是有兩種不同的高度可以選擇,我先解釋其中最細緻的一種:

(1)如何閱讀函數

一個函數寫下來經常上百行,但是你需要一行一行的看麼?肯定不能,那清晰認識一個函數的結構就很重要。

一個函數就是為了解決一個問題,函數名基本都能說明其功能,函數參數是輸入,返回值就是輸出,函數體就是整體的執行邏輯。在函數體內部,也基本都是類似的邏輯,先是對各種輸入參數進行檢查,然後書寫功能邏輯,然後構造輸出的結果。所以一個函數寫下來總是這樣的一種結構。


輸出結果 函數名(輸入)
{
if (輸入的參數有問題)
{
異常處理,跳出
}


準備參數

功能邏輯


構造輸出結果


返回輸出結果
}


一個函數其實就是一個方法,閱讀的難度比書寫的難度要低,書寫代碼需要考慮的問題非常多,但是在閱讀代碼的時候問題就簡單很多,很多書寫代碼過程中需要考慮的問題在閱讀代碼的過程中就不需要考慮 。

  • 函數名:在書寫代碼過程中需要考慮一個函數的函數名需要能夠精確表達出這個函數所具備的功能,所以經常存在各種名目規範。而閱讀代碼過程則可以通過閱讀函數名大致瞭解這個函數的功能。
  • 註釋:在編寫代碼的時候,都會建議添加對應的函數註釋,解釋函數體的功能和一些注意事項;在閱讀過程中可以選擇性的閱讀這些註釋(注意:是選擇性閱讀,千萬不要每個註釋都讀)
  • 輸入參數:在書寫代碼的時候,這部分的內容也是很頭疼的內容,不僅需要確定需要哪些輸入,還需要輸入的形式,而且還需要精確定義每個輸入參數的語義;但是在閱讀代碼的過程中,這部分內容基本可以忽略,我們很少會關係所看到的函數需要哪些參數輸入。
  • 輸出結果:在書寫代碼的時候,這部分也是很頭疼的一件事,因為精確定義輸出結果也是非常困難和麻煩的一件事;在閱讀代碼過程中,也需要注意輸出結果,不然一個函數執行了老半天,結果連輸出結果是什麼都沒概念,也太失敗了點。
  • 參數檢測:在編寫代碼過著中非常煩的一件事,每個人都希望調用函數的人會傳入正確的參數,但是根本做不到,結果每次都要花費一定精力對輸入參數的邊界、非空等進行檢查;在閱讀代碼過程中,根本不需要閱讀這部分的代碼,恰恰這部分內容在每個函數體中佔據了相當一部分的位置;
  • 參數準備:編寫代碼的過程中,因為函數體內部的邏輯需要進行很多準備,所以常常需要有一個參數準備的過程;而閱讀代碼的過程基本可以忽略這部分的邏輯,或者快速瀏覽這部分邏輯,這裡恰恰是很多新手花費大量精力糾纏的內容,其實沒必要在這裡糾結,跳過就好。
  • 功能邏輯:這部分是函數體中最為精華的部分,而且代碼編寫起來也是相當的麻煩,被各種邏輯弄的死去火來,最後還需要重構等等手段;在閱讀代碼過程中,這部分其實很難把握,因為功能邏輯可能被封裝在另外一個函數內部,這時候大家會習慣性的繼續深入看,結果弄的自己更加混亂,又比如有的時候幾個功能邏輯點組成了一套邏輯,但是大家卻將這部分邏輯割裂來看,結果總感覺讀的很彆扭。這部分內容需要一些經驗,但是有一個指導,就是在看這部分代碼的時候要注意自己所站的高度,選擇採用何種策略。
  • 構造輸出結果:函數體內部還會花費大量的代碼進行對最後返回結果的構造工作,就像搭積木一樣;不過在閱讀代碼的時候,我們並不需要花費太多精力在這些邏輯上,多注意注意一些返回結果的語義。

閱讀代碼還有很多技巧,例如如何在帶有goto語句的代碼中快速理解邏輯,如何界定那些註釋是可以忽略的,如何將一些代碼邏輯看成一塊整體內容,何時應該跳到更深的一層函數閱讀等等。這些都需要平時的經驗積累。

(2)如何在大量的代碼中游刃有餘

看代碼有一個粒度問題,我們不能一行一行的看,也不能一個一個函數的看,我之前提到了,Linux內核有大量面向對象編程的影子,所以在看大量代碼的時候,必須學會面向對象編程的思維模式。這樣對自己在大量代碼閱讀中提供大量參考意見。

或許有人會告訴你,面向對象編程就是弄明白什麼是對象、如何寫一個class就可以了。確實,學習面向對象編程,弄明白對象是基礎,不過我覺得可以再拔高一點,理解一些更抽象的概念,在這些抽象概念的指導下去學習,可以有更多的指導意義。

  • 層:層並不是面向對象編程特有,但是理解層是很重要的,我們遇到的典型的層就是網絡協議棧,為什麼我們網絡協議會有那麼多層,就因為需要處理的事情太多,我們不得不將內容一塊塊的分割,分割的時候,發現用層進行組織,可以讓結構更加清晰,所以你以後會發現,大量的系統都會帶有層的味道。linux內核中帶有大量的層設計,如網絡協議棧有層,內存管理與尋址有層,文件I/O也有層。
  • 領域模型:領域模型就是一個系統中最為核心的幾個抽象實體,一個系統,基本就是圍繞著領域模型展開,在學習內核不同的子系統的時候,一定要花大量的精力在領域模型上,切記!!!在Linux內核上也有大量的領域模型,例如在虛擬文件系統部分存在4大抽象inode,dentry,file等。在進程調度系統的最核心抽象是task_struct。在進程地址空間則有mm_struct,address_space等這些核心的領域模型。我感覺可以花費80%的時間在理解這些領域模型上。
  • 領域驅動類:領域模型內部其實是大量的屬性組成,但是如果只有屬性,沒有一個執行的方法,那這個領域模型也不能發揮作用,面向對象編程的做法就是將這些方法編程領域驅動類,說的直白一些就是接口。在Linux中就是那些函數指針和對應的回調函數。平時看代碼,大家會花費大量的時間去看各個回調函數,這個其實是吃力不討好的辦法,與其花大量的心思去看各個回調函數的實現,不如多思考下,為什麼會有這些操作方法,它們是如何抽象出來的。


如果能夠理解上述的這幾個抽象,那在大量代碼中如何遊刃有餘就相對容易了,有一個簡單的套路:
(1) 在較高的角度,弄明白一個系統為了解決什麼問題,應該有哪些抽象

(2) 在對整體結構有所瞭解以後,花心思看看這些抽象對應的領域模型,因為一般情況領域模型很龐大,所以看的時候也需要有步驟的進行拆解學習。

(3) 在對領域模型有所瞭解後,開始看領域驅動類,想明白為什麼會有這些操作。

(4) 在上述準備好後,就可以花費一些時間去看各個函數的具體實現,並且在看的過程中多思考領域模型為什麼這麼設計。


三、學習資料

學習的資料參考:

需要C/C++ Linux服務器架構師學習資料後臺私信“1”獲取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

談談如何學習Linux內核


分享到:


相關文章: