英語

將補丁應用於 Linux 核心

原文作者

Jesper Juhl,2005 年 8 月

注意

本文件已過時。在大多數情況下,您幾乎肯定會希望使用 Git,而不是手動使用 patch

Linux 核心郵件列表上一個常見問題是如何將補丁應用於核心,或者更具體地說,補丁應該應用於眾多樹/分支中的哪個基礎核心。希望本文件能為您解答此問題。

除了解釋如何應用和還原補丁之外,本文還簡要介紹了不同的核心樹(以及如何應用其特定補丁的示例)。

什麼是補丁?

補丁是一個小型文字文件,包含原始碼樹兩個不同版本之間的更改增量。補丁是使用 diff 程式建立的。

要正確應用補丁,您需要知道它是從哪個基礎版本生成的,以及補丁會將原始碼樹更改為哪個新版本。這些資訊應存在於補丁檔案元資料中,或者可以從檔名中推斷出來。

如何應用或還原補丁?

您可以使用 patch 程式應用補丁。patch 程式讀取一個 diff(或補丁)檔案,並根據其中描述的內容對原始碼樹進行更改。

Linux 核心的補丁是相對於包含核心原始碼目錄的父目錄生成的。

這意味著補丁檔案內的檔案路徑包含生成時所針對的核心原始碼目錄名稱(或“a/”和“b/”等其他目錄名稱)。

由於這不太可能與您本地機器上的核心原始碼目錄名稱匹配(但通常有助於檢視未標記的補丁是針對哪個版本生成的),因此您應該進入核心原始碼目錄,然後在應用補丁時從補丁檔案中的檔名中剝離路徑的第一個元素(patch 命令的 -p1 引數可以做到這一點)。

要還原以前應用的補丁,請使用 patch 命令的 -R 引數。因此,如果您像這樣應用了一個補丁:

patch -p1 < ../patch-x.y.z

您可以像這樣還原(撤銷)它:

patch -R -p1 < ../patch-x.y.z

如何將補丁/diff 檔案提供給 patch

這(與 Linux 和其他類 UNIX 作業系統一樣)可以透過多種不同方式完成。

在下面的所有示例中,我使用以下語法透過 stdin 將檔案(未壓縮形式)提供給 patch

patch -p1 < path/to/patch-x.y.z

如果您只想能夠跟隨下面的示例,而不想了解 patch 的多種使用方式,那麼您可以在此處停止閱讀本節。

patch 還可以透過 -i 引數獲取要使用的檔名,如下所示:

patch -p1 -i path/to/patch-x.y.z

如果您的補丁檔案已使用 gzip 或 xz 壓縮,並且您不想在應用之前解壓縮它,那麼您可以像這樣將其提供給 patch

xzcat path/to/patch-x.y.z.xz | patch -p1
bzcat path/to/patch-x.y.z.gz | patch -p1

如果您希望在應用補丁檔案之前手動解壓縮它(我假設您在下面的示例中已經這樣做了),那麼您只需對檔案執行 gunzip 或 xz -- 如下所示:

gunzip patch-x.y.z.gz
xz -d patch-x.y.z.xz

這將留下一個純文字的 patch-x.y.z 檔案,您可以根據自己的喜好透過 stdin 或 -i 引數將其提供給 patch

patch 的其他幾個有用引數是 -s,它使 patch 除了錯誤之外保持靜默,這有助於防止錯誤過快地滾動出螢幕;以及 --dry-run,它使 patch 只打印將發生的操作列表,但實際上不進行任何更改。最後,--verbose 告訴 patch 列印有關正在進行的工作的更多資訊。

打補丁時的常見錯誤

patch 應用補丁檔案時,它會嘗試以不同方式驗證檔案的健全性。

檢查檔案是否看起來像一個有效的補丁檔案,以及檢查被修改部分周圍的程式碼是否與補丁中提供的上下文匹配,只是 patch 進行的兩項基本健全性檢查。

如果 patch 遇到看起來不太對勁的情況,它有兩個選擇。它可以拒絕應用更改並中止,或者它可以嘗試找到一種方法,透過一些微小的更改來應用補丁。

一個 patch 會嘗試修復的‘不太對勁’的例子是,所有上下文都匹配,被更改的行也匹配,但是行號不同。這可能會發生,例如,如果補丁在檔案中間進行了更改,但由於某些原因,檔案開頭附近新增或刪除了幾行。在這種情況下,一切看起來都很好,只是向上或向下移動了一點,patch 通常會調整行號並應用補丁。

每當 patch 應用一個它需要稍作修改才能適應的補丁時,它會透過提示“補丁應用時帶有 fuzz”來告知您。您應該警惕此類更改,因為即使 patch 很可能做對了,它也並非/總是/做對,結果有時會是錯誤的。

patch 遇到無法透過 fuzz 修復的更改時,它會直接拒絕,並留下一個帶有 .rej 副檔名(一個拒絕檔案)的檔案。您可以閱讀此檔案以確切瞭解無法應用的更改是什麼,這樣如果您願意,可以手動修復它。

如果您的核心原始碼沒有應用任何第三方補丁,而只有來自 kernel.org 的補丁,並且您按正確順序應用補丁,並且您自己沒有修改原始檔,那麼您應該永遠不會看到 patch 發出的 fuzz 或 reject 訊息。如果您仍然看到此類訊息,那麼您的本地原始碼樹或補丁檔案很可能已以某種方式損壞。在這種情況下,您應該嘗試重新下載補丁,如果問題仍然存在,建議您從 kernel.org 完整下載一個全新的原始碼樹。

讓我們再看看 patch 可能產生的一些訊息。

如果 patch 停止並顯示 File to patch: 提示,則說明 patch 找不到要打補丁的檔案。最可能的原因是您忘記指定 -p1 或您在錯誤的目錄中。較少見的情況是,您會發現需要使用 -p0 而不是 -p1 應用的補丁(閱讀補丁檔案應該會揭示是否是這種情況 -- 如果是,那麼這是建立補丁的人的錯誤,但並非致命)。

如果您收到類似 Hunk #2 succeeded at 1887 with fuzz 2 (offset 7 lines). 的訊息,則表示 patch 必須調整更改的位置(在此示例中,它需要將更改從預期位置移動 7 行以使其適應)。

結果檔案可能正常也可能不正常,這取決於檔案與預期不同的原因。

如果您嘗試應用一個針對不同核心版本(而非您嘗試打補丁的版本)生成的補丁,這種情況經常發生。

如果您收到類似 Hunk #3 FAILED at 2387. 的訊息,則表示補丁無法正確應用,並且 patch 程式無法透過模糊匹配完成。這將生成一個 .rej 檔案,其中包含導致補丁失敗的更改,以及一個 .orig 檔案,顯示無法更改的原始內容。

如果您收到 Reversed (or previously applied) patch detected!  Assume -R? [n],則表示 patch 檢測到補丁中包含的更改似乎已經完成。

如果您確實之前應用過此補丁,並且只是錯誤地重新應用了它,那麼只需回答 [n]o 並中止此補丁。如果您之前應用過此補丁,並且實際上打算還原它,但忘記指定 -R,那麼您可以在此處回答 [y]es,讓 patch 為您還原它。

如果補丁建立者在建立補丁時顛倒了源目錄和目標目錄,也會發生這種情況,在這種情況下,還原補丁實際上會應用它。

類似 patch: **** unexpected end of file in patchpatch unexpectedly ends in middle of line 的訊息意味著 patch 無法理解您提供給它的檔案。這可能是您的下載損壞了,您嘗試在未解壓縮的情況下將壓縮的補丁檔案提供給 patch,或者您正在使用的補丁檔案在傳輸過程中被郵件客戶端或郵件傳輸代理破壞了,例如,將一行長文字拆分成兩行。通常,這些警告可以透過合併(連線)被拆分的這兩行來輕鬆修復。

正如我上面已經提到的,如果您將 kernel.org 的補丁應用到未修改的正確版本的原始碼樹,這些錯誤永遠不應該發生。因此,如果您在使用 kernel.org 補丁時遇到這些錯誤,那麼您應該假定您的補丁檔案或您的樹已損壞,我建議您重新開始,完整下載一個新的核心樹和您希望應用的補丁。

是否有 patch 的替代方案?

是的,有替代方案。

您可以使用 interdiff 程式 (http://cyberelk.net/tim/patchutils/) 生成一個表示兩個補丁之間差異的補丁,然後應用結果。

這將使您能夠一步從 5.7.2 遷移到 5.7.3。interdiff 的 -z 標誌甚至允許您直接以 gzip 或 bzip2 壓縮形式向其提供補丁,無需使用 zcat、bzcat 或手動解壓縮。

下面是如何一步從 5.7.2 遷移到 5.7.3 的方法:

interdiff -z ../patch-5.7.2.gz ../patch-5.7.3.gz | patch -p1

儘管 interdiff 可能會為您節省一兩個步驟,但通常建議您執行額外的步驟,因為 interdiff 在某些情況下可能會出錯。

另一個替代方案是 ketchup,這是一個用於自動下載和應用補丁的 Python 指令碼 (https://www.selenic.com/ketchup/)。

其他有用的工具包括 diffstat,它顯示補丁所做更改的摘要;lsdiff,它顯示補丁檔案中受影響檔案的簡短列表,以及(可選地)每個補丁開始的行號;以及 grepdiff,它顯示補丁修改的檔案列表,其中補丁包含給定的正則表示式。

在哪裡可以下載補丁?

補丁可在 https://kernel.linux.club.tw/ 找到。大多數最新補丁都從首頁連結,但它們也有特定的存放位置。

5.x.y (-stable) 和 5.x 補丁位於:

5.x.y 增量補丁位於:

-rc 補丁不儲存在網路伺服器上,而是根據 git 標籤按需生成,例如:

穩定的 -rc 補丁位於:

5.x 核心

這些是 Linus 釋出的基礎穩定版本。編號最高的版本是最新的。

如果發現迴歸或其他嚴重缺陷,則將在本基礎之上釋出一個 -stable 修復補丁(見下文)。一旦釋出新的 5.x 基礎核心,就會提供一個補丁,它是前一個 5.x 核心與新核心之間的增量。

要應用一個從 5.6 遷移到 5.7 的補丁,您需要執行以下操作(請注意,此類補丁應用於 5.x.y 核心之上,而是應用於基礎 5.x 核心之上 -- 如果您需要從 5.x.y 遷移到 5.x+1,您需要首先還原 5.x.y 補丁)。

這裡有一些例子:

# moving from 5.6 to 5.7

$ cd ~/linux-5.6                # change to kernel source dir
$ patch -p1 < ../patch-5.7      # apply the 5.7 patch
$ cd ..
$ mv linux-5.6 linux-5.7        # rename source dir

# moving from 5.6.1 to 5.7

$ cd ~/linux-5.6.1              # change to kernel source dir
$ patch -p1 -R < ../patch-5.6.1 # revert the 5.6.1 patch
                                # source dir is now 5.6
$ patch -p1 < ../patch-5.7      # apply new 5.7 patch
$ cd ..
$ mv linux-5.6.1 linux-5.7      # rename source dir

5.x.y 核心

帶有三位版本號的核心是 -stable 核心。它們包含針對特定 5.x 核心中發現的安全問題或重大回歸的少量關鍵修復。

這是推薦給那些想要最新穩定核心,且對測試開發/實驗版本不感興趣的使用者的分支。

如果沒有 5.x.y 核心可用,則編號最高的 5.x 核心是當前的穩定核心。

-stable 團隊提供普通補丁和增量補丁。下面是應用這些補丁的方法。

普通補丁

這些補丁不是增量的,這意味著例如 5.7.3 補丁不能應用於 5.7.2 核心原始碼之上,而應應用於基礎 5.7 核心原始碼之上。

因此,為了將 5.7.3 補丁應用於您現有的 5.7.2 核心原始碼,您必須首先撤銷 5.7.2 補丁(這樣您就只剩下基礎的 5.7 核心原始碼),然後應用新的 5.7.3 補丁。

這裡有一個小例子:

$ cd ~/linux-5.7.2              # change to the kernel source dir
$ patch -p1 -R < ../patch-5.7.2 # revert the 5.7.2 patch
$ patch -p1 < ../patch-5.7.3    # apply the new 5.7.3 patch
$ cd ..
$ mv linux-5.7.2 linux-5.7.3    # rename the kernel source dir

增量補丁

增量補丁有所不同:它們不是應用於基礎 5.x 核心之上,而是應用於前一個穩定核心(5.x.y-1)之上。

以下是應用這些補丁的示例:

$ cd ~/linux-5.7.2              # change to the kernel source dir
$ patch -p1 < ../patch-5.7.2-3  # apply the new 5.7.3 patch
$ cd ..
$ mv linux-5.7.2 linux-5.7.3    # rename the kernel source dir

-rc 核心

這些是釋出候選核心。這些是 Linus 在他認為當前的 git(核心的原始碼管理工具)樹處於一個合理健全且適合測試的狀態時釋出的開發核心。

這些核心不穩定,如果您打算執行它們,應該預料到偶爾會出現故障。然而,這是主要開發分支中最穩定的一個,並且最終也將成為下一個穩定核心,因此由儘可能多的人進行測試非常重要。

對於那些希望幫助測試開發核心但不想執行一些真正實驗性內容的人來說,這是一個很好的分支(此類人員應參閱下面關於 -next 和 -mm 核心的部分)。

-rc 補丁不是增量的,它們適用於基礎 5.x 核心,就像上面描述的 5.x.y 補丁一樣。-rcN 字尾之前的核心版本表示此 -rc 核心最終將成為的核心版本。

因此,5.8-rc5 意味著這是 5.8 核心的第五個釋出候選版本,該補丁應應用於 5.7 核心原始碼之上。

以下是應用這些補丁的 3 個示例:

# first an example of moving from 5.7 to 5.8-rc3

$ cd ~/linux-5.7                        # change to the 5.7 source dir
$ patch -p1 < ../patch-5.8-rc3          # apply the 5.8-rc3 patch
$ cd ..
$ mv linux-5.7 linux-5.8-rc3            # rename the source dir

# now let's move from 5.8-rc3 to 5.8-rc5

$ cd ~/linux-5.8-rc3                    # change to the 5.8-rc3 dir
$ patch -p1 -R < ../patch-5.8-rc3       # revert the 5.8-rc3 patch
$ patch -p1 < ../patch-5.8-rc5          # apply the new 5.8-rc5 patch
$ cd ..
$ mv linux-5.8-rc3 linux-5.8-rc5        # rename the source dir

# finally let's try and move from 5.7.3 to 5.8-rc5

$ cd ~/linux-5.7.3                      # change to the kernel source dir
$ patch -p1 -R < ../patch-5.7.3         # revert the 5.7.3 patch
$ patch -p1 < ../patch-5.8-rc5          # apply new 5.8-rc5 patch
$ cd ..
$ mv linux-5.7.3 linux-5.8-rc5          # rename the kernel source dir

-mm 補丁和 linux-next 樹

-mm 補丁是 Andrew Morton 釋出的實驗性補丁。

過去,-mm 樹也用於測試子系統補丁,但此功能現在透過 linux-next (https://kernel.linux.club.tw/doc/man-pages/linux-next.html) 樹完成。子系統維護者首先將他們的補丁推送到 linux-next,然後在合併視窗期間直接將其傳送給 Linus。

-mm 補丁充當新功能和其他未透過子系統樹合併的實驗性補丁的試驗場。一旦此類補丁在 -mm 中證明了其價值一段時間,Andrew 就會將其推送到 Linus 以便包含在主線中。

linux-next 樹每天更新,幷包含 -mm 補丁。兩者都在不斷變化中,包含許多實驗性功能、大量不適合主線的除錯補丁等,是本文件中描述的分支中最具實驗性的一個。

這些補丁不適用於預期穩定的系統,並且它們比任何其他分支的執行風險更高(請確保您有最新的備份 -- 這適用於任何實驗性核心,但對於 -mm 補丁或使用來自 linux-next 樹的核心而言更是如此)。

非常感謝對 -mm 補丁和 linux-next 的測試,因為這些補丁的全部目的是在更改合併到更穩定的主線 Linus 樹之前,剔除迴歸、崩潰、資料損壞錯誤、構建破壞(以及一般任何其他錯誤)。

但是 -mm 和 linux-next 的測試人員應該意識到,故障比任何其他樹都更常見。

這總結了各種核心樹的解釋列表。我希望您現在清楚如何應用各種補丁並幫助測試核心。

感謝 Randy Dunlap、Rolf Eike Beer、Linus Torvalds、Bodo Eggert、Johannes Stezenbach、Grant Coady、Pavel Machek 以及我可能遺漏的其他人員對本文件的審閱和貢獻。