Linux 內(nèi)核動(dòng)手編譯實(shí)用指南
出于各種原因,自行編譯 Linux 內(nèi)核可能引起你的興趣。這些原因可能包括但不限于:
- 測(cè)試一個(gè)比你目前的 Linux 發(fā)行版更新的內(nèi)核版本
- 采用一組不同的配置選項(xiàng)、驅(qū)動(dòng)來(lái)構(gòu)建內(nèi)核
- 學(xué)習(xí)者的好奇心 ??
此指南將一步步指導(dǎo)你如何親自編譯 Linux 內(nèi)核,包括你該運(yùn)行哪些命令,為什么運(yùn)行這些命令以及這些命令的執(zhí)行效果。本文篇幅較長(zhǎng),所以請(qǐng)做好準(zhǔn)備!
?? 諸如 Ubuntu 這樣的發(fā)行版提供了更簡(jiǎn)單地安裝主線 Linux 內(nèi)核的方式。但本教程目標(biāo)是從源碼手動(dòng)完成所有工作。此教程需要你付出時(shí)間、耐心以及豐富的 Linux 命令行使用經(jīng)驗(yàn)。本文更注重親身實(shí)踐的體驗(yàn)。不管怎么說(shuō),我仍建議你在虛擬機(jī)或備用系統(tǒng)中嘗試此冒險(xiǎn),而非在你的主系統(tǒng)上進(jìn)行。
前置準(zhǔn)備
在軟件領(lǐng)域,構(gòu)建任何事物都有兩個(gè)基本要求:
- 源代碼
- 構(gòu)建依賴
因此,作為預(yù)備環(huán)節(jié),我們需要下載 Linux 內(nèi)核的源碼壓縮包,并安裝一些能讓我們成功構(gòu)建 Linux 內(nèi)核的依賴項(xiàng)。
Linux 版本導(dǎo)覽
在任何時(shí)刻,Freax Linux 內(nèi)核都有四種“版本”。
Linux 的這些 “版本”,按照開(kāi)發(fā)流程的順序是:
- linux-next 樹(shù): 所有準(zhǔn)備合并到 Linux 代碼庫(kù)的代碼首先被合并到 linux-next 樹(shù)。它代表的是 Linux 內(nèi)核最新也是“最不穩(wěn)定”的狀態(tài)。大多數(shù) Linux 內(nèi)核開(kāi)發(fā)者和測(cè)試人員使用這個(gè)來(lái)提高代碼質(zhì)量,為 Linus Torvalds 的后續(xù)提取做準(zhǔn)備。請(qǐng)謹(jǐn)慎使用!
- 發(fā)布候選版(RC) / 主線版: Linus 從 linux-next 樹(shù)抽取代碼并創(chuàng)建一個(gè)初始發(fā)布版本。這個(gè)初始發(fā)布版本的測(cè)試版稱為 RC(發(fā)布候選Release Candidate)版本。一旦 RC 版本發(fā)布,Linus 只會(huì)接受對(duì)它的錯(cuò)誤修復(fù)和性能退化相關(guān)的補(bǔ)丁?;A(chǔ)這些反饋,Linus 會(huì)每周發(fā)布一個(gè) RC 內(nèi)核,直到他對(duì)代碼感到滿意。RC 發(fā)行版本的標(biāo)識(shí)是
-rc
后綴,后面跟一個(gè)數(shù)字。 - 穩(wěn)定版: 當(dāng) Linus 覺(jué)得最新的 RC 版本已穩(wěn)定時(shí),他會(huì)發(fā)布最終的“公開(kāi)”版本。穩(wěn)定發(fā)布版將會(huì)維護(hù)幾周時(shí)間。像 Arch Linux 和 Fedora Linux 這樣的前沿 Linux 發(fā)行版會(huì)使用此類版本。我建議你在試用 linux-next 或任何 RC 版本之前,先試一試此版本。
- LTS 版本: 每年最后一個(gè)穩(wěn)定版將會(huì)再維護(hù) 幾年。這通常是一個(gè)較舊的版本,但它會(huì) 會(huì)積極地維護(hù)并提供安全修復(fù)。Debian 的穩(wěn)定版本會(huì)使用 Linux 內(nèi)核的 LTS 版版本。
若想了解更多此方面的知識(shí),可參閱 官方文檔。
本文將以當(dāng)前可用的最新穩(wěn)定版為例,編寫此文時(shí)的 Linux 內(nèi)核版本是 6.5.5。
系統(tǒng)準(zhǔn)備
由于 Linux 內(nèi)核使用 C 語(yǔ)言編寫,編譯 Linux 內(nèi)核至少需要一個(gè) C 編譯器。你的計(jì)算機(jī)上可能還需要其他一些依賴項(xiàng),現(xiàn)在是安裝它們的時(shí)候了。
?? 這個(gè)指南主要聚焦于使用 GNU C 編譯器(GCC)來(lái)編譯 Linux 內(nèi)核。但在未來(lái)的文章中(可能會(huì)深入介紹 Rust 的支持),我可能會(huì)介紹使用 LLVM 的 Clang 編譯器作為 GCC 的替代品。
不過(guò),請(qǐng)注意,MSVC 并不適用。盡管如此,我仍期待有微軟的員工為此發(fā)送修補(bǔ)程序集。我在瞎想啥?
對(duì)于 Arch Linux 以及其衍生版本的用戶,安裝命令如下:
sudo pacman -S base-devel bc coreutils cpio gettext initramfs kmod libelf ncurses pahole perl python rsync tar xz
對(duì)于 Debian 以及其衍生版本的用戶,安裝命令如下:
sudo apt install bc binutils bison dwarves flex gcc git gnupg2 gzip libelf-dev libncurses5-dev libssl-dev make openssl pahole perl-base rsync tar xz-utils
對(duì)于 Fedora 以及其衍生版本的用戶,安裝命令如下:
sudo dnf install binutils ncurses-devel \
/usr/include/{libelf.h,openssl/pkcs7.h} \
/usr/bin/{bc,bison,flex,gcc,git,gpg2,gzip,make,openssl,pahole,perl,rsync,tar,xz,zstd}
下載 Linux 內(nèi)核源碼
請(qǐng)?jiān)L問(wèn) kernel.org,在頁(yè)面中尋找第一個(gè) 穩(wěn)定Stable
通過(guò)點(diǎn)擊黃色的方框,你就可以下載 Tar 文件。同時(shí),也別忘了下載相匹配的 PGP 簽名文件,稍后我們需要用到它來(lái)驗(yàn)證 Tar 文件。它的擴(kuò)展名為 .tar.sign
。
校驗(yàn) Tar 文件的完整性
你如何知道剛下載的 Tar 文件是否被損壞?對(duì)于個(gè)人來(lái)說(shuō),一個(gè)損壞的 Tar 文件只會(huì)浪費(fèi)你的寶貴時(shí)間,如果你是在為一個(gè)組織工作,那么可能會(huì)危及到組織的安全(這時(shí)你可能還有更大的問(wèn)題需要擔(dān)憂,但我們并不想讓所有人都產(chǎn)生創(chuàng)傷后應(yīng)激障礙?。?/p>
為了驗(yàn)證我們的 Tar 文件的完整性,我們需要先解壓它。目前,它是使用 XZ 壓縮算法壓縮的。因此,我將使用 unxz
工具(其實(shí)就是 xz --decompress
的別名)來(lái)解壓 .tar.xz
格式的壓縮文件。
unxz --keep linux-*.tar.xz
解壓完成后,我們需要獲取 Linus Torvalds 和 Greg KH 使用的 GPG 公開(kāi)密鑰。這些密鑰用于對(duì) Tar 文件進(jìn)行簽名。
gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org
你應(yīng)該可以得到一個(gè)與我在我的電腦上看到的類似的結(jié)果:
$ gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org
gpg: /home/pratham/.gnupg/trustdb.gpg: trustdb created
gpg: key 38DBBDC86092693E: public key "Greg Kroah-Hartman <gregkh@kernel.org>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: key 79BE3E4300411886: public key "Linus Torvalds <torvalds@kernel.org>" imported
gpg: Total number processed: 1
gpg: imported: 1
pub rsa4096 2011-09-23 [SC]
647F28654894E3BD457199BE38DBBDC86092693E
uid [ unknown] Greg Kroah-Hartman <gregkh@kernel.org>
sub rsa4096 2011-09-23 [E]
pub rsa2048 2011-09-20 [SC]
ABAF11C65A2970B130ABE3C479BE3E4300411886
uid [ unknown] Linus Torvalds <torvalds@kernel.org>
sub rsa2048 2011-09-20 [E]
在導(dǎo)入 Greg 和 Linus 的密鑰后,我們可以使用 --verify
標(biāo)志來(lái)驗(yàn)證 Tar 的完整性,操作如下:
gpg2 --verify linux-*.tar.sign
如果驗(yàn)證成功,你應(yīng)該會(huì)看到如下的輸出信息:
$ gpg2 --verify linux-*.tar.sign
gpg: assuming signed data in 'linux-6.5.5.tar'
gpg: Signature made Saturday 23 September 2023 02:46:13 PM IST
gpg: using RSA key 647F28654894E3BD457199BE38DBBDC86092693E
gpg: Good signature from "Greg Kroah-Hartman <gregkh@kernel.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E
務(wù)必查看是否存在 gpg: Good signature
的提示,然后再繼續(xù)!
?? 你可以忽略以下警告:
WARNING: This key is not certified with a trusted signature! There is no indication that the signature belongs to the owner.
。我們已根據(jù) Linus 和 Greg 的郵件地址獲取了公開(kāi)密鑰,并無(wú)需對(duì)此警告感到擔(dān)憂。
解壓 Tar 文件
如果你順利的進(jìn)行到這里,意味著你的 Tar 文件完整性檢查已經(jīng)成功完成。接下來(lái),我們將從 Tar 文件中解壓出 Linux 內(nèi)核的源碼。
The "TAR" xkcd comic: https://xkcd.com/1168/
The "TAR" xkcd comic: https://xkcd.com/1168/
這個(gè)步驟十分簡(jiǎn)單,只需對(duì) Tar 文件執(zhí)行 tar -xf
命令,如下:
tar -xf linux-*.tar
在這里,-x
選項(xiàng)表示解壓,-f
選項(xiàng)則用來(lái)告訴 Tar 文件的文件名。
這個(gè)解壓過(guò)程可能需要幾分鐘時(shí)間,你可以先放松,耐心等待一下。
配置 Linux 內(nèi)核
Linux 內(nèi)核的構(gòu)建過(guò)程會(huì)查找 .config
文件。顧名思義,這是一個(gè)配置文件,用于指定 Linux 內(nèi)核的所有可能的配置選項(xiàng)。這是必需的文件。
獲取 Linux 內(nèi)核的 .config
文件有兩種方式:
- 使用你的 Linux 發(fā)行版的配置作為基礎(chǔ)(推薦做法)
- 使用默認(rèn)的,通用的配置
?? 也有第三種方法,也就是從零開(kāi)始,手動(dòng)配置每一個(gè)選項(xiàng),但注意,這需要配置超過(guò) 12,000 個(gè)選項(xiàng)。并不推薦這種方式,因?yàn)槭謩?dòng)配置所有選項(xiàng)將花費(fèi)大量的時(shí)間,并且你還需要理解每個(gè)啟用和禁用選項(xiàng)的含義。
使用發(fā)行版提供的配置
使用你的 Linux 發(fā)行版提供的配置是一個(gè)安全的選擇。 如果你只是跟隨這個(gè)指南測(cè)試一個(gè)不是你的發(fā)行版提供的新內(nèi)核,那么這就是推薦的方式。
你的 Linux 發(fā)行版的 Linux 內(nèi)核配置文件會(huì)在以下兩個(gè)位置之一:
- 大多數(shù) Linux 發(fā)行版,如 Debian 和 Fedora 及其衍生版,將會(huì)把它存在
/boot/config-$(uname -r)
。 - 一些 Linux 發(fā)行版,比如 Arch Linux 將它整合在了 Linux 內(nèi)核中。所以,可以在
/proc/config.gz
找到。
?? 如果兩者都有,建議使用
/proc/config.gz
。這是因?yàn)樗谥蛔x文件系統(tǒng)中,所以是未被篡改的。
進(jìn)入含有已經(jīng)解壓出的 Tar 文件的目錄。
cd linux-*/
接著,復(fù)制你的 Linux 發(fā)行版的配置文件:
### Debian 和 Fedora 及其衍生版:
$ cp /boot/config-"$(uname -r)" .config
### Arch Linux 及其衍生版:
$ zcat /proc/config.gz > .config
更新配置文件
一旦完成這些步驟,接下來(lái)就需要“更新”配置文件了。因?yàn)槟愕陌l(fā)行版提供的配置很可能比你正在構(gòu)建的 Linux 內(nèi)核版本要舊。
?? 這同樣適用于像 Arch Linux 和 Fedora 這樣前沿的 Linux 發(fā)行版。 它們并不會(huì)因?yàn)橛行掳姹究捎镁土⒖贪l(fā)布更新。他們會(huì)進(jìn)行一些質(zhì)量控制工作,這必然會(huì)花費(fèi)些時(shí)間。因此,即便是你的發(fā)行版提供的最新內(nèi)核,相較于你在 kernel.org 上獲取的版本也會(huì)滯后幾個(gè)小版本。
要更新一個(gè)已有的 .config
文件,我們使用 make
命令搭配 olddefconfig
參數(shù)。簡(jiǎn)單解釋一下,這個(gè)命令的意思是使用 舊的、默認(rèn)的、配置。
這將使用“舊的配置文件”(當(dāng)前保存為 .config
,這是你發(fā)行版配置的一份直接副本),并檢查從上一版本以來(lái) Linux 代碼庫(kù)中新加的任何配置選項(xiàng)。如果找到任何新的、未配置 的選項(xiàng),該選項(xiàng)的默認(rèn)配置值會(huì)被使用,并會(huì)對(duì) .config
文件進(jìn)行更新。
原來(lái)的 .config
文件將被重命名為 .config.old
進(jìn)行備份,并將新的更改寫入至 .config
文件。
make olddefconfig
以下是我機(jī)器上的輸出:
$ file .config
.config: Linux make config build file, ASCII text
$ make olddefconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/confdata.o
HOSTCC scripts/kconfig/expr.o
LEX scripts/kconfig/lexer.lex.c
YACC scripts/kconfig/parser.tab.[ch]
HOSTCC scripts/kconfig/lexer.lex.o
HOSTCC scripts/kconfig/menu.o
HOSTCC scripts/kconfig/parser.tab.o
HOSTCC scripts/kconfig/preprocess.o
HOSTCC scripts/kconfig/symbol.o
HOSTCC scripts/kconfig/util.o
HOSTLD scripts/kconfig/conf
.config:8593:warning: symbol value 'm' invalid for USB_FOTG210_HCD
.config:8859:warning: symbol value 'm' invalid for USB_FOTG210_UDC
#
# configuration written to .config
#
針對(duì) Debian 及其衍生版用戶
Debian 及其衍生版為內(nèi)核模塊使用一個(gè)簽名證書。默認(rèn)情況下,你的計(jì)算機(jī)并不包含這個(gè)證書。
我推薦關(guān)閉啟用模塊簽名的選項(xiàng)。具體如下所示:
./scripts/config --file .config --set-str SYSTEM_TRUSTED_KEYS ''
./scripts/config --file .config --set-str SYSTEM_REVOCATION_KEYS ''
如果你不這么做,在后面你進(jìn)行 Linux 內(nèi)核構(gòu)建時(shí),可能會(huì)導(dǎo)致構(gòu)建失敗。要注意這點(diǎn)。
使用自定義配置
如果你出于學(xué)習(xí)內(nèi)核開(kāi)發(fā)的目的學(xué)習(xí)如何構(gòu)建 Linux 內(nèi)核,那你應(yīng)該這樣做。
?? 請(qǐng)注意,偏離你的 Linux 發(fā)行版的配置可能無(wú)法在實(shí)體硬件上“正?!惫ぷ?。問(wèn)題可能是特定硬件無(wú)法工作、Linux 內(nèi)核無(wú)法啟動(dòng)等。
因此,我們只建議在虛擬機(jī)中使用。
你可以通過(guò)查看 make help 的輸出 來(lái)查看 所有 可用的選項(xiàng),但我們主要關(guān)注三個(gè) make
目標(biāo):
defconfig
: 默認(rèn)配置。allmodconfig
: 根據(jù)當(dāng)前系統(tǒng)狀態(tài),盡可能地把項(xiàng)目構(gòu)建為可加載模塊(而非內(nèi)建)。tinyconfig
: 極簡(jiǎn)的 Linux 內(nèi)核。
由于 tinyconfig
目標(biāo)只會(huì)構(gòu)建少數(shù)項(xiàng)目,構(gòu)建時(shí)間將會(huì)縮短。我個(gè)人選擇它的原因主要有:
- 檢查我在代碼/工具鏈中做的修改是否正確,以及代碼是否可以編譯。
- 在虛擬機(jī)中只進(jìn)行少數(shù)選項(xiàng)的測(cè)試。
?? 在為 ARM 或 RISC-V 機(jī)器構(gòu)建 Linux 內(nèi)核時(shí),你可能需要 DTB(設(shè)備樹(shù)的二進(jìn)制文件)。使用
tinyconfig
目標(biāo)將不會(huì)啟用構(gòu)建 DTB 的選項(xiàng),你的內(nèi)核很可能無(wú)法啟動(dòng)。當(dāng)然,你可以用 QEMU 在沒(méi)有任何 DTB 的情況下啟動(dòng) Linux 內(nèi)核。但這篇文章并不會(huì)聚焦在此?;蛟S你可以通過(guò)評(píng)論,讓我在之后的時(shí)間里覆蓋這個(gè)話題 ??
除非你確切地知道自己在做什么,否則你應(yīng)當(dāng)使用 defconfig
目標(biāo)。 以下是我在我的電腦上運(yùn)行的效果:
$ make defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/confdata.o
HOSTCC scripts/kconfig/expr.o
LEX scripts/kconfig/lexer.lex.c
YACC scripts/kconfig/parser.tab.[ch]
HOSTCC scripts/kconfig/lexer.lex.o
HOSTCC scripts/kconfig/menu.o
HOSTCC scripts/kconfig/parser.tab.o
HOSTCC scripts/kconfig/preprocess.o
HOSTCC scripts/kconfig/symbol.o
HOSTCC scripts/kconfig/util.o
HOSTLD scripts/kconfig/conf
*** Default configuration is based on 'defconfig'
#
# configuration written to .config
#
修改配置
無(wú)論你是使用 Linux 發(fā)行版的配置并更新它,還是使用 defconfig
目標(biāo)創(chuàng)建新的 .config
文件,你都可能希望熟悉如何修改這個(gè)配置文件。最可靠的修改方式是使用 menuconfig
或 nconfig
目標(biāo)。
這兩個(gè)目標(biāo)的功能是相同的,只不過(guò)提供給你的界面有所不同。這是這兩者間唯一的區(qū)別。我個(gè)人更偏向于使用 menuconfig
目標(biāo),但近來(lái)我發(fā)現(xiàn) nconfig
在搜索選項(xiàng)時(shí)似乎更具直觀性,所以我逐漸轉(zhuǎn)向使用它。
首先,帶著 menuconfig
目標(biāo)運(yùn)行 make
命令:
$ make menuconfig
HOSTCC scripts/kconfig/mconf.o
HOSTCC scripts/kconfig/lxdialog/checklist.o
HOSTCC scripts/kconfig/lxdialog/inputbox.o
HOSTCC scripts/kconfig/lxdialog/menubox.o
HOSTCC scripts/kconfig/lxdialog/textbox.o
HOSTCC scripts/kconfig/lxdialog/util.o
HOSTCC scripts/kconfig/lxdialog/yesno.o
HOSTLD scripts/kconfig/mconf
在此界面,你可以根據(jù)各選項(xiàng)的類型來(lái)進(jìn)行切換操作。
有兩類可切換選項(xiàng):
- 布爾狀態(tài)選項(xiàng):這類選項(xiàng)只能關(guān)閉(
[ ]
)或作為內(nèi)建組件開(kāi)啟([*]
)。 - 三態(tài)選項(xiàng):這類選項(xiàng)可以關(guān)閉(
< >
)、內(nèi)建(<*>
),或作為可加載模塊(<M>
)進(jìn)行構(gòu)建。
想要了解更多關(guān)于某個(gè)選項(xiàng)的信息,使用上/下箭頭鍵導(dǎo)航至該選項(xiàng),然后按 <TAB>
鍵,直至底部的 < Help >
選項(xiàng)被選中,然后按回車鍵進(jìn)行選擇。此時(shí)就會(huì)顯示關(guān)于該配置選項(xiàng)的幫助信息。
在修改選項(xiàng)時(shí)請(qǐng)務(wù)必謹(jǐn)慎。
當(dāng)你滿意配置后,按 <TAB>
鍵直到底部的 < Save >
選項(xiàng)被選中。然后按回車鍵進(jìn)行選擇。然后再次按回車鍵(記住,此時(shí)不要更改文件名),就能將更新后的配置保存到 .config
文件中。
構(gòu)建 Linux 內(nèi)核
構(gòu)建 Linux 內(nèi)核實(shí)際上十分簡(jiǎn)單。然而,在開(kāi)始構(gòu)建之前,讓我們?yōu)樽远x內(nèi)核構(gòu)建添加一個(gè)標(biāo)簽。我將使用字符串 -pratham
作為標(biāo)簽,并利用 LOCALVERSION
變量來(lái)實(shí)施。你可以使用以下命令實(shí)現(xiàn)配置:
./scripts/config --file .config --set-str LOCALVERSION "-pratham"
這一命令將 .config
文件中的 CONFIG_LOCALVERSION
配置選項(xiàng)設(shè)為我在結(jié)尾指定的字符串,即 -pratham
。當(dāng)然,你也不必非得使用我所用的名字哦 ??
LOCALVERSION
選項(xiàng)可用于設(shè)置一個(gè)“本地”版本,它會(huì)被附加到通常的 x.y.z
版本方案之后,并在你運(yùn)行 uname -r
命令時(shí)一并顯示。
由于我正在構(gòu)建的是 6.5.5 版本內(nèi)核,而 LOCALVERSION
字符串被設(shè)為 -pratham
,因此,對(duì)我來(lái)說(shuō),最后的版本名將會(huì)是 6.5.5-pratham
。這么做的目的是確保我所構(gòu)建的自定義內(nèi)核不會(huì)與發(fā)行版所提供的內(nèi)核產(chǎn)生沖突。
接下來(lái),我們來(lái)真正地構(gòu)建內(nèi)核??梢杂靡韵碌拿钔瓿纱瞬襟E:
make -j$(nproc) 2>&1 | tee log
這對(duì)大部分(99%)用戶來(lái)說(shuō)已經(jīng)足夠了。
其中的 -j
選項(xiàng)用于指定并行編譯任務(wù)的數(shù)量。而 nproc
命令用于返回可用處理單位(包括線程)的數(shù)量。因此,-j$(nproc)
其實(shí)意味著“使用我擁有的 CPU 線程數(shù)相同數(shù)量的并行編譯任務(wù)”。
2>&1
會(huì)將 STDOUT 和 STDIN 重定向到相同的文件描述符,并通過(guò)管道傳輸給 tee
命令,這會(huì)將輸出存儲(chǔ)在一個(gè)名為 log
的文件,并且在控制臺(tái)打印出完全相同的文本。如果你在構(gòu)建時(shí)遇到錯(cuò)誤,并希望回顧日志來(lái)檢查出了什么問(wèn)題,這將會(huì)十分有用。遇到那種情況,你只需要簡(jiǎn)單執(zhí)行 grep Error log
命令就能找到線索。
自定義 make 目標(biāo)
在 Linux 內(nèi)核的源文件夾中,make
命令有一些自定義的目標(biāo)可供執(zhí)行各種操作。這些主要作為開(kāi)發(fā)者的參考。如果你的唯一目標(biāo)是安裝一個(gè)比你當(dāng)前發(fā)行版更新的 Linux 內(nèi)核,那么你完全可以跳過(guò)這部分內(nèi)容 ??
構(gòu)建目標(biāo)
作為一名開(kāi)發(fā)者,你可能只想構(gòu)建 Linux 內(nèi)核,或者只想構(gòu)建模塊,或者只想構(gòu)建設(shè)備樹(shù)二進(jìn)制(DTB)。在這種情況下,你可以指定一個(gè)構(gòu)建目標(biāo),然后 make
命令只會(huì)構(gòu)建指定的項(xiàng)目,而不會(huì)構(gòu)建其他的。
以下是一些構(gòu)建目標(biāo):
vmlinux
:純粹的 Linux 內(nèi)核。modules
:可加載模塊。dtbs
:設(shè)備樹(shù)二進(jìn)制文件(主要用于 ARM 和 RISC-V 架構(gòu))。all
:構(gòu)建所有被標(biāo)記了星號(hào)*
的項(xiàng)目(從make help
的輸出中可以查看)。
通常情況下,你并不需要指定構(gòu)建目標(biāo),因?yàn)樗鼈兌家呀?jīng)在構(gòu)建列表中。所列出的目標(biāo)是在你只想要測(cè)試某一個(gè)構(gòu)建目標(biāo),而不是其他目標(biāo)時(shí)的情況。
依據(jù)你的 計(jì)算機(jī)架構(gòu),構(gòu)建完成的 Linux 內(nèi)核鏡像(存放在 /boot
目錄)的名稱會(huì)有所不同。
對(duì)于 x86_64
,Linux 內(nèi)核的默認(rèn)鏡像名稱是 bzImage
。因此,如果你只需要構(gòu)建引導(dǎo)所需的 Linux 內(nèi)核,你可以像下面這樣設(shè)定 bzImage
為目標(biāo):
### 對(duì)于 x86_64
$ make bzImage
“那么如何在我的架構(gòu)上找到用來(lái)調(diào)用 make
的目標(biāo)名稱呢?”
有兩種方法。要么你可以執(zhí)行 make help
之后查找在 Architecture specific targets
下,第一個(gè)前面帶有星號(hào) *
的選項(xiàng)。
或者,如果你希望自動(dòng)完成,你可以利用 image_name
目標(biāo)得到鏡像的完全路徑(相對(duì)路徑),選擇性地添加 -s
標(biāo)志來(lái)獲得有用的輸出。
以下是我擁有的三臺(tái)電腦的輸出,一臺(tái)是 x86_64
,另一臺(tái)是 AArch64
,還有一臺(tái)是 riscv
:
### x86_64
$ make -s image_name
arch/x86/boot/bzImage
### AArch64
$ make -s image_name
arch/arm64/boot/Image.gz
### RISC-V
$ make -s image_name
arch/riscv/boot/Image.gz
現(xiàn)在,要只構(gòu)建 Linux 內(nèi)核鏡像,你可以這樣進(jìn)行:
make $(make -s image_name | awk -F '/' '{print $4}')
清理目標(biāo)
如果你需要清理構(gòu)建產(chǎn)生的文件,你可以用以下的目標(biāo)來(lái)實(shí)現(xiàn)你的需求:
clean
:除了.config
文件外,刪除幾乎所有其他內(nèi)容。mrproper
:執(zhí)行了make clean
的所有操作外,還會(huì)刪除.config
文件。distclean
:除了執(zhí)行make mrproper
的所有操作外,還會(huì)清理任何補(bǔ)丁文件。
安裝
一旦成功編譯了 Linux 內(nèi)核,接下來(lái)就是啟動(dòng)安裝一些東西的時(shí)候了。“一些
?? 雖然我將告訴你不同的安裝方式,尤其是關(guān)于如何改變默認(rèn)安裝路徑的方法,但如果你不確定自己在做什么,那么我不建議你這么做! 請(qǐng)慎重考慮,如果你決定走自定義的路線,那你需要自己負(fù)責(zé)后果。默認(rèn)設(shè)置之所以存在,是因?yàn)樗鼈冇衅涮厥獾脑???
安裝內(nèi)核模塊
Linux 內(nèi)核有部分在系統(tǒng)啟動(dòng)時(shí)并非必需的。這些部分被構(gòu)建為可加載模塊,即在需要時(shí)才進(jìn)行加載和卸載。
所以,首先需要安裝這些模塊。這可以通過(guò) modules_install
目標(biāo)完成。必須使用 sudo
,因?yàn)槟K會(huì)被安裝在 /lib/modules/<kernel_release>-<localversion>
這個(gè)需要 root
權(quán)限的路徑下。
這個(gè)過(guò)程不僅會(huì)安裝內(nèi)核模塊,還會(huì)對(duì)其進(jìn)行簽名,所以可能需要一些時(shí)間。好消息是你可以通過(guò)之前提到的 -j$(nproc)
選項(xiàng)來(lái)并行執(zhí)行安裝任務(wù),這樣會(huì)快一些。??
sudo make modules_install -j$(nproc)
給開(kāi)發(fā)者的提示: 你可以通過(guò)設(shè)定
INSTALL_MOD_PATH
變量來(lái)指定一個(gè)不同的路徑存放 Linux 模塊,而不用默認(rèn)的/lib/modules/<kernel_release>-<localversion>
,具體如下:sudo make modules_install INSTALL_MOD_PATH=<path>
另一個(gè)給開(kāi)發(fā)者的提示: 你可以使用
INSTALL_MOD_STRIP
變量來(lái)決定是否需要?jiǎng)冸x模塊的調(diào)試符號(hào)。如果未設(shè)定該變量,調(diào)試符號(hào)不會(huì)被剝離。當(dāng)設(shè)為1
時(shí),符號(hào)信息將會(huì)被使用--strip-debug
選項(xiàng)剝離,隨后該選項(xiàng)會(huì)傳遞給strip
(或者在使用 Clang 的時(shí)候傳遞給llvm-strip
)工具。
(可選)安裝 Linux 內(nèi)核頭文件
如果你打算使用這個(gè)內(nèi)核來(lái)支持樹(shù)外模塊,比如 ZFS 或英偉達(dá) DKMS,或者打算嘗試自行編寫模塊,你可能會(huì)需要 Linux 內(nèi)核提供的頭文件。
可以通過(guò)以下方式使用 headers_install
目標(biāo)來(lái)安裝 Linux 內(nèi)核頭文件:
sudo make headers_install
應(yīng)使用 sudo
命令,因?yàn)檫@些頭文件會(huì)被安裝到 /usr
目錄。同時(shí)還會(huì)在 /usr
目錄內(nèi)創(chuàng)建子目錄 include/linux
,然后將頭文件安裝到 /usr/include/linux
內(nèi)。
給開(kāi)發(fā)者的提示: 通過(guò)設(shè)定
INSTALL_HDR_PATH
變量,你可以修改 Linux 內(nèi)核頭文件的安裝路徑。
安裝 DTB(只針對(duì) ARM 和 RISC-V)
如果你使用的是 x86_64 架構(gòu),那么你可以跳過(guò)此步驟!
如果你針對(duì) ARM 或者 RISC-V 構(gòu)建了內(nèi)核,那么在運(yùn)行 make
的過(guò)程中,設(shè)備樹(shù)的二進(jìn)制文件可能已經(jīng)被編譯出來(lái)了。你可以通過(guò)在 arch/<machine_architecture>/boot/dts
目錄查找 .dtb
文件來(lái)確認(rèn)這一點(diǎn)。
這里提供了一個(gè)快速檢查的技巧:
### 對(duì)于 AArch32
$ find arch/arm/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for ARM32 were built"
### 對(duì)于 AArch64
$ find arch/arm64/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for ARM64 were built"
### 對(duì)于 RISC-V
$ find arch/riscv/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for RISC-V were built"
如果你看到出現(xiàn) DTBs for <arch> were built
的消息,那么你可以開(kāi)始安裝 DTB。這可以通過(guò) dtbs_install
目標(biāo)來(lái)實(shí)現(xiàn)。
需要使用 sudo
,因?yàn)樗鼈儠?huì)被安裝在 /boot/dtb-<kernel_release>-<localversion>
中,而這個(gè)目錄是由 root
所擁有的。
sudo make dtbs_install
給開(kāi)發(fā)者的提示: 就像安裝模塊一樣,你可以使用
INSTALL_DTBS_PATH
變量指定一個(gè)自定義的路徑來(lái)安裝設(shè)備樹(shù)二進(jìn)制文件。
安裝 Linux 內(nèi)核
最后,我們來(lái)安裝 Linux 內(nèi)核本身!這可以通過(guò) install
目標(biāo)來(lái)完成,就像這樣:
sudo make install
在這里必須使用 sudo
,因?yàn)?Linux 內(nèi)核將被安裝在 /boot
目錄,而這個(gè)目錄不允許普通用戶寫入。
?? 一般來(lái)講,
install
目標(biāo)也會(huì)更新引導(dǎo)加載程序,但是如果它沒(méi)有成功,那可能是不支持你使用的引導(dǎo)加載程序。如果你沒(méi)有使用 GRUB 作為你的引導(dǎo)加載程序,請(qǐng)一定要閱讀你引導(dǎo)加載程序的使用手冊(cè) ??給開(kāi)發(fā)者的提示: 并不奇怪,
INSTALL_PATH
變量被用來(lái)設(shè)定 Linux 內(nèi)核的安裝位置,而非默認(rèn)的/boot
目錄。
針對(duì) Arch Linux 用戶的說(shuō)明
如果你嘗試執(zhí)行了 make install
命令,可能已經(jīng)注意到產(chǎn)生了錯(cuò)誤。錯(cuò)誤如下:
$ sudo make install
INSTALL /boot
Cannot find LILO.
要在 Arch Linux 上實(shí)際完成 Linux 內(nèi)核的安裝,我們需要手動(dòng)復(fù)制 Linux 內(nèi)核鏡像文件。別擔(dān)心,如果你使用的是 Arch Linux,手動(dòng)操作應(yīng)該是家常便飯了。( ?° ?? ?°)
可以使用以下命令完成這個(gè)步驟:
sudo install -Dm644 "$(make -s image_name)" /boot/vmlinuz-<kernel_release>-<localversion>
因?yàn)槲揖幾g的是 6.5.5 版本的內(nèi)核,所以我將會(huì)執(zhí)行下面這條命令,你可以根據(jù)你的實(shí)際情況進(jìn)行適當(dāng)調(diào)整:
sudo install -Dm644 "$(make -s image_name)" /boot/vmlinuz-6.5.5-pratham
雖然不是必須的,但最好復(fù)制一份名為 System.map
的文件。既然你已經(jīng)在操作了,一并也復(fù)制了 .config
文件吧 ??
sudo cp -vf System.map /boot/System.map-<kernel_release>-<localversion>
sudo cp -vf .config /boot/config-<kernel_release>-<localversion>
生成初始 RAM 磁盤
當(dāng)你安裝 Arch Linux 時(shí),可能已經(jīng)了解過(guò) mkinitcpio
這個(gè)工具?,F(xiàn)在,我們將使用它來(lái)創(chuàng)建初始的 RAM 磁盤。
首先,我們需要?jiǎng)?chuàng)建一個(gè)預(yù)設(shè)文件。向 /etc/mkinitcpio.d/linux-<localversion>.preset
文件中添加以下內(nèi)容,根據(jù)實(shí)際需要來(lái)替換 <kernel_release>
和 <localversion>
。
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-<kernel_release>-<localversion>"
PRESETS=('default' 'fallback')
default_image="/boot/initramfs-<kernel_release>-<localversion>.img"
fallback_options="-S autodetect"
配置完成后,執(zhí)行下面的命令來(lái)生成初始 RAM 磁盤:
sudo mkinitcpio -p linux-<localversion>
我自己的電腦上得到的輸出如下,你的結(jié)果應(yīng)該會(huì)類似!
$ sudo mkinitcpio -p linux-pratham
==> Building image from preset: /etc/mkinitcpio.d/linux-pratham.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio.conf'
-> -k /boot/vmlinuz-6.5.5-pratham -c /etc/mkinitcpio.conf -g /boot/initramfs-6.5.5-pratham.img
==> Starting build: '6.5.5-pratham'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-6.5.5-pratham.img'
==> Image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux-pratham.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio.conf'
==> WARNING: No image or UKI specified. Skipping image 'fallback'
初始 RAM 磁盤已成功生成,現(xiàn)在我們可以進(jìn)入下一步,更新引導(dǎo)加載器!
更新 GRUB
一旦所有必要的文件已成功復(fù)制到其對(duì)應(yīng)的位置,接下來(lái),我們將進(jìn)行 GRUB 的更新。
使用以下命令對(duì) GRUB 引導(dǎo)加載器進(jìn)行更新:
sudo grub-mkconfig -o /boot/grub/grub.cfg
?? 如果你使用的引導(dǎo)加載器不是 GRUB,請(qǐng)參看 Arch Wiki 中相關(guān)的引導(dǎo)加載器文檔。
注意,更新 GRUB 并不會(huì)直接使新的內(nèi)核版本設(shè)為默認(rèn)啟動(dòng)選項(xiàng)。在引導(dǎo)時(shí),請(qǐng)?jiān)趩?dòng)菜單中手動(dòng)選擇新的內(nèi)核版本。
你可以通過(guò)選擇 Advanced options for Arch Linux
菜單,并在隨后的菜單中選擇 Arch Linux, with Linux <kernel_release>-<localversion>
來(lái)啟用新版的 Linux 內(nèi)核。
重啟電腦
恭喜你!你已經(jīng)完成了獲取 Linux 內(nèi)核源代碼、進(jìn)行配置、構(gòu)建以及安裝等所有步驟。現(xiàn)在只需要通過(guò)重啟電腦并進(jìn)入新構(gòu)建和安裝的 Linux 內(nèi)核,就可以開(kāi)始享受你的努力成果了。
啟動(dòng)時(shí),請(qǐng)確保從引導(dǎo)加載器中選擇正確的 Linux 內(nèi)核版本。系統(tǒng)啟動(dòng)后,運(yùn)行 uname -r
命令來(lái)確認(rèn)你正在使用預(yù)期的 Linux 內(nèi)核。
以下是我自己的電腦輸出的內(nèi)容:
$ uname -r
6.5.5-pratham
是時(shí)候開(kāi)始慶祝了! ??
卸載操作
?? 提示:在刪除當(dāng)前正在使用的內(nèi)核版本之前,你應(yīng)該首先切換至較舊的內(nèi)核版本。
可能你的 Linux 發(fā)行版所使用的 Linux 內(nèi)核版本就是你手動(dòng)編譯的版本,或者你自行編譯了新的內(nèi)核并注意到應(yīng)卸載舊的內(nèi)核以節(jié)省空間,于是你開(kāi)始想如何才能卸載。當(dāng)然,雖然我們無(wú)法簡(jiǎn)單地運(yùn)行 make uninstall
命令,但這并不代表沒(méi)有其他的方法!
我們清楚各個(gè)文件的安裝位置,因此刪除它們相對(duì)簡(jiǎn)單。
### 刪除內(nèi)核模塊
$ rm -rf /lib/modules/<kernel_release>-<localversion>
### 刪除設(shè)備樹(shù)二進(jìn)制文件
$ rm -rf /boot/dtb-<kernel_release>-<localversion>
### 刪除 Linux 內(nèi)核本身
$ rm -vf /boot/{config,System,vmlinuz}-<kernel_release>-<localversion>
總結(jié)
這個(gè)過(guò)程不是一次簡(jiǎn)單的旅程,是吧?但是現(xiàn)在,我們終于抵達(dá)了終點(diǎn)。我們一起學(xué)習(xí)了手動(dòng)編譯 Linux 內(nèi)核的全過(guò)程,包括安裝依賴、獲取和驗(yàn)證源碼、解壓源碼、配置 Linux 內(nèi)核、構(gòu)建內(nèi)核以及安裝內(nèi)核。
如果你喜歡這個(gè)詳細(xì)的步驟指南,請(qǐng)給我留言反饋。如果在操作過(guò)程中遇到問(wèn)題,也歡迎提出,讓我知道!