楠木軒

兩個月新增 80萬行代碼,Linux 內核維護為什麼不會崩?

由 童豔紅 發佈於 科技

8 月初,當 Linux 5.8 RC 版本開放測試時,大多數的新聞都聚焦於它的大小,稱其為“史上最大的內核版本”。正如 Linus Torvalds 本人指出的那樣,“儘管沒有任何一件事情能脱穎而出……但 5.8 似乎是我們有史以來最大的發行版之一。”

確實,剛剛發佈的 Linux 內核 5.8 RC 具有超過 14,000 個 commit,約 80 萬行新代碼以及大約 100 名新貢獻者。要知道,距離 5.7 正式版發佈才僅僅過去了約 2 個月的時間。

Linux 內核維護者 Steven Rostedt 認為,5.8 之所以變得如此之大,很有可能是因為 COVID-19 疫情讓很多人難以出門旅行,所有人都因此能夠在這期間完成比平時更多的工作。

Rostedt 表示,從一個經驗豐富的 Linux 內核貢獻者和維護者的角度來看,5.8 RC 發行版特別令人震驚的並不是它的大小,而是它的空前規模對於那些正在維護它的人來説卻沒有造成困擾,“我認為這是因為 Linux 具有比世界上任何軟件項目都好的工作流程。”

擁有最佳的工作流程意味着什麼?對 Rostedt 而言,這歸結為 Linux 內核開發人員隨着時間的推移建立的一系列基本規則,以使他們能夠持續不斷地大規模、可靠地發展 Linux 內核項目。

Rostedt 站在一個 Linux 內核資深維護者的角度,為我們分享了龐大的 Linux 內核項目 30 年來是如何有條不紊地運轉的。

第一個關鍵因素是 Git

首先讓我們從 Linux 項目的歷史來看。在該項目的早期(1991-2002年),人們只能直接將補丁發送給 Linus Torvalds。準確地説,Linus 從項目的子維護者那裏獲取補丁,而這些子維護者從其他代碼貢獻者那裏獲取補丁。

隨着 Linux 內核變得越來越大,代碼越來越複雜,很快他們就發現,一切都變得很難擴展和跟蹤,並且項目將始終面臨合併不兼容代碼的風險。

這導致 Linus 開始探索包括 BitKeeper 在內的各種版本管理工具。BitKeeper 是一種最早的分佈式版本管理的方法,其他的版本管理系統通常使用簽出/修改/簽入協議,而 BitKeeper 則向所有人提供整個倉庫的副本,並允許開發人員將其變更發送出去以進行合併。

Linux 在 2002 年開始短暫地採用了 BitKeeper,但是由於其本身是一個專有軟件,被認為不符合社區對開源工作的信念,於是該工具在 2005 年停止使用。

為了尋找替代品,Linus 消失了一段時間,並帶着 git 回來了,後者成為了更強大的分佈式版本管理系統,並且是管理流程的第一個重要實例化。Git 的出現使 Linux 開發在今天依然運轉良好。

Rostedt 為我們列出了 Linux 內核工作流程中,圍繞 Git 展開的七個重要基本原則。

七大基本原則
  • 每次commit只能做一件事

Linux 的中心原則是,所有更改都必須分解為小步驟進行 —— 您的每個commit都只能做一件事。這並不意味着每個 commit 都必須很小,比如對在數千個文件中使用的函數的API進行簡單更改,可以使更改量很大,但仍然可以接受,因為它是針對某一項單一任務的更改。

通過始終遵循此原則,項目維護者可以更輕鬆地識別和隔離任何有問題的更改,而不影響其他的功能。

  • commit 不能破壞構建

不僅應該將所有更改分解為儘可能小的變量,而且還不能破壞內核。即每個步驟都必須完全起作用,並且不引起退化。這就是為什麼對函數原型的更改還必須更新調用它的每個文件,以防止構建中斷的原因。因此,每個步驟都必須作為一個獨立的更改來工作,這將我們帶到了下一點:

  • 所有代碼都是二等分的

如果在某個時候發現了錯誤,則需要知道是哪個更改導致了問題。從本質上講,二等分是一種操作,它使開發者可以找到所有發生錯誤的確切時間點。

為此,請轉到最後一個已知的工作 commit 所在的節點,並且已知第一個 commit 已損壞,然後在該點測試代碼。如果可行,則前進到下一個節點;如果不是,則返回更上層的節點。

這樣一來,開發者就可以在十幾次編譯/測試中,從成千上萬的可能 commit 中分離出導致問題出現的 commit 。Git 甚至可以通過 git bisect 功能幫助自動化該過程。

重要的是,這隻有在開發者遵守以前的規則的情況下才能很好地起作用:每個 commit 僅做一件事。否則,您將不知道是 commit 的許多更改中的哪一個導致了問題

如果 commit 破壞了構建讓整個項目無法正常啓動,同時等分線又恰好落在了該 commit 上,則您將不知道接下來是該往上一個節點測試還是往下一個節點測試,因為它們都有問題。

這意味着您永遠都不應編寫依賴於將來 commit 的 commit ,例如:調用尚不存在的函數,或更改全局函數的參數而不更改同一 commit 中的所有調用者。

  • 永遠不要 rebase 公共分支

Linux 項目工作流程不允許 rebase 他人使用的任何公共分支。因為 rebase 這些公共分支後,已重新基準化的 commit 將不再與基於原存儲庫中的相同 commit 匹配。在樹的層次結構中,不是葉子的公共主幹部分不能重新設置基準,否則將會破壞層次結構中的下游分支。

  • Git 正確合併

其他的版本管理系統是合併來自不同分支代碼的噩夢,它們通常難以弄清代碼衝突,並且需要大量的手動工作來解決。而 Git 的結構可以輕鬆完成這項工作,因此 Linux 項目也從中直接受益。這就是為什麼 5.8 版本的大小並不重要的重要原因。

在 5.8-RC1 發佈週期中,平均每天有 200個 commit ,並從 5.7 版本中繼承了 880 個合併。一些維護者注意到了其中增加的工作量,但是對此仍然沒有感到什麼太大的壓力或者導致倦怠。

  • 保留定義明確的 commit 日誌

不幸的是,這可能是許多其他項目忽略的最重要的原則之一。每個 commit 都必須是獨立的,這也應該包括與該 commit 相應的日誌。內核貢獻者必須在更改的 commit 日誌中做出説明,讓所有人瞭解與正在進行的更改相關的所有內容。

Rostedt 提到,他自己的一些最冗長和最具描述性的變更日誌,往往是針對一些單行代碼提交的,因為這些單行代碼更改是非常細微的錯誤修復,且代碼本身包含的信息極少。因此更改的代碼越少,日誌反而應該説明得更詳細。

在一個 commit 過了幾年之後,幾乎沒有人會記得當初為什麼進行更改。Git 的 blame 功能就可以顯示這些代碼的修改記錄。

比如一些 commit 可能非常古老,也許您需要去除一個鎖定,或者對某些代碼進行更改,而又不確切知道它為什麼存在,就可以使用 git blame 來查看。編寫良好的代碼更改日誌可以幫助確定是否可以刪除該代碼或如何對其進行修改。

Rostedt 説:“有好幾次我很高興能在代碼上看到詳細的變更日誌,因為我不得不刪除這些代碼,而變更日誌的描述讓我知道我這麼做是可以的。”

  • 持續測試和集成

最後一項基本原則是開發過程中進行持續測試和持續集成。在向上遊發送 commit 請求之前,開發者會測試每個 commit 。Linux 社區還有一個名為Linux-next 的鏡像 ,它提取維護人員在其存儲庫的特定分支上進行的所有更改,並對其進行測試以確保它們能正確集成。

Linux-next 非常有效地運行着整個內核的可測試分支,該分支將用於下一個發行版。Linux-next 是一個公共倉庫,任何人都可以測試它,這種情況經常發生 —— 人們現在甚至發佈有關 Linux-next 中代碼的錯誤報告。事實上,已經進入 Linux-next 幾周的代碼基本上可以確定會最終進入主線發行版中。

軟件開發行業的黃金標準

所有的這些原則制度使 Linux 社區能夠以如此龐大的規模(常規 9 周為一個版本迭代週期)發佈令人難以置信的可靠代碼(每個版本平均 10,000 次 commit ,最後一個版本超過 14,000 次 commit )。

Rostedt 指出,Linux 項目取得空前成功的另一個因素是他們社區的文化。Linux 內核社區內部存在一種持續改進的文化,這使他們能夠首先採用這些實踐。

同時他們還有一種信任的文化,“我們有一條清晰的途徑,人們可以通過該途徑做出貢獻,並隨着時間的推移證明他們願意且有能力推進該項目的發展。這將建立一個相互信任的關係網,這些關係對於項目的長期成功至關重要。”

Rostedt 認為,內核開發者的肩上承擔着比其他任何項目都要重的責任。“在內核層,我們別無選擇,只能遵循這些做法。因為所有其他應用程序都在內核之上運行,內核中的任何性能問題或錯誤都將導致上層的應用程序出現性能問題或錯誤。

我們必須完美處理內核中的錯誤,否則,整個計算機系統都將受到損害。我們非常關心每個錯誤,因為內核中的錯誤帶來的風險很高,這種思維方式也能讓我們很好地服務於任何軟件項目。”

上層的應用程序會因為錯誤而崩潰,造成的後果可能是惹惱用户,但風險不高。而內核的錯誤可能導致的後果是讓計算機上的一切都出現問題,承擔着巨大的風險。

這就是 Linux 內核開發工作流程被視為軟件開發行業黃金標準的原因。