當前位置: 華文問答 > 數碼

未來想從事Linux 後台開發,需要學習linux內核嗎?像讀內核源碼。還是學好linux網絡編程,C,演算法。學習內核的意義有哪些呢?

2012-10-19數碼

先列出主要觀點,有時間再補充細節:

「學習Linux內核」對不同的人有不同的含義,學習方法、側重點、投入的精力也大不相同。我大致分三類:reader、writer、hacker。reader 就是了解某個功能在內核的大致實作 how does it work,一般不關心某些極端情況下(記憶體不足、受到攻擊)的處理方法,對於看不懂的地方也可以跳過。我自己最多算半個 reader,只看我感興趣的一小部份程式碼(我只關心 TCP 收發數據,不管 IP routing/forwarding/fragment 等,更不會去關心 ethernet 層),而且讀一個函數一般只看主幹(happy path),不管 security/debugging/tracing,經常忽略錯誤處理分支。writer 是給內核加feature和改bug的人,需要更進一步的知識,寫程式碼要考慮 how not to break it(哪些地方需要加鎖,按照什麽順序加鎖以避免死結,如何正確釋放分配的資源等等)。hacker 是透過分析程式碼找出安全漏洞並加以利用的人,研究 how to break it,讀程式碼恐怕更註意找出error handling分支沒有覆蓋的case。

內核向使用者態提供的介面很穩定,但是內核的具體實作變化很快,你深入鉆研獲得的知識很容易就過時。比方說 Linux 的 TCP 實作在2015年3月新加了 TCP_NEW_SYN_RECV 這個非標準的 TCP state,同年6月釋出的 kernel 4.1 才開始用它。如果你學的是一年前的 kernel,那麽建立TCP連線這方面的細節知識對於今年釋出的 Ubuntu 16.04 可能已經過時了。

Linux內核的編碼風格不值得效仿。比如 tcp_v4_rcv 這個函數,有很多 goto:
tcp_ipv4.c [linux/net/ipv4/tcp_ipv4.c]

Linux內核的一些做法在 C 語言中是合理的(比方說用包含函數指標的各種 xxx_ops struct 來手工實作虛擬函式表,透過控制 struct 記憶體布局來模擬繼承:tcp_sock 繼承 inet_connection_sock 繼承 inet_sock 繼承 sock 繼承 sock_common),在其他高級語言中往往有更簡便的實作方式,不必生搬硬套。遇到 xxx_ops->some_func(arg) 這種程式碼,思路容易斷線,這個 xxx_ops 到底指向哪個具體實作?最好能把程式碼跑起來,用偵錯程式單步跟蹤,一下子就定位到了 callee。當然,讀 OO 程式碼也會遇到這個困難,傳進來的這個 interface 在執行時到底是哪份實作?讀 Python 程式碼就更難了,函數參數光有個名字,連 type 都沒有。

Linux內核比較註重程式碼的通用性和復用性,要照顧那些雖然你用不到但少數人會用的需求。通用性方面,雖然現在大家都用乙太網路,但是網絡協定棧的程式碼還在支持 FDDI、Token Ring(從 3.5 版起已刪除)、ATM 等,偶爾會擾亂視線,更在原本簡單直接的做法上增加間接層,加大了理解程式碼的負擔。復用性方面,從 3.17 版開始,IPv4 和 IPv6 共享同一個 tcp_conn_request() 函數[tcp: add tcp_conn_request · torvalds/linux@1fb6f15 · GitHub]。原來的做法是各自有 tcp_v4_conn_request() 和 tcp_v6_conn_request(),新合並的 tcp_conn_request() 為了處理 v4/v6 的不同情況,用了一個 struct tcp_request_sock_ops* 參數,這程式碼讀起來就比原來繞了。雖然我不關心 IPv6 的實作,但讀程式碼的時候卻不能排除其幹擾。

Linux內核廣泛采用的侵入式數據結構設計恐怕很難套用到一般程式開發中。基本上是個高維十字連結串列,一個節點(struct)可以同時位於多個hash/list/tree中。

如果你本身就要從事內核開發,那麽以上這些都不是問題。對於這使用者態寫server的人,學內核的目的是什麽,學到的知識能不能/要不要/如何用到日常開發中,這是值得思考的。

同樣的情況也出現在 Java 開發者身上,一般的 Java 程式設計師要不要讀 JVM 的實作?我記得 @RednaxelaFX 對此有過精彩的論述,我完全同意他的看法。特別是當你功力不夠時,強行去讀源碼,結果讀錯了地方(一個函數有多個實作,你讀的那個其實沒有被呼叫,甚至沒有被編譯),造成了錯誤的認識,回頭來還跟人爭論說JVM/kernel裏是如何如何實作的,那還不如不讀。

逢人就推薦閱讀 Linux 內核源碼,就像向每個學數據結構的人推薦 TAOCP 一樣,是中文網絡上特有的現象,我對此是不贊成的。(前幾年還有一問C++網絡編程就推薦ACE庫的現象,現在少多了。)