
1. 項目概述與核心價值最近在折騰一個嵌入式項目需要為一塊特定的開發板定制一個輕量級的Linux系統。官方的Ubuntu Server鏡像雖然方便但內核版本固定驅動支持不全還帶了一堆我用不上的服務和軟件包占用了寶貴的存儲空間和內存。于是我決定從最底層開始自己動手編譯一個精簡的Linux內核并以此為基礎制作一個完全貼合我需求的系統鏡像。這個過程說白了就是“內核編譯”加“鏡像制作”兩件大事。對于很多從應用層開發轉向底層系統或者從事嵌入式、IoT、云計算基礎設施工作的朋友來說掌握這套流程是進階的必經之路。它不僅能讓你徹底理解一個Linux系統是如何從源代碼“生長”出來的更能讓你獲得對系統的絕對控制權你可以裁剪掉所有冗余功能只保留必需的驅動和子系統讓系統跑得更快、更省資源你可以打上最新的安全補丁或者集成還未被主流發行版收錄的硬件驅動你甚至可以為特定的應用場景比如高實時性、高安全性調整內核的成百上千個配置參數。自己編譯內核并制作鏡像就像是為你的硬件和應用量身定制一套最合身的“操作系統西裝”而不是去商場買一件可能袖子長、腰身松的成衣。接下來我將以Ubuntu 22.04 LTS作為編譯環境詳細拆解從獲取內核源碼、配置、編譯到創建根文件系統、打包成可啟動鏡像的全過程。我會重點分享我在這個過程中踩過的坑和總結出的實用技巧目標是讓你看完后能獨立完成一次完整的內核定制與鏡像構建。2. 編譯環境準備與內核源碼獲取工欲善其事必先利其器。編譯內核是個資源消耗型任務尤其是鏈接階段對內存和CPU要求較高。一個穩定、純凈的編譯環境能避免很多稀奇古怪的問題。2.1 基礎編譯環境搭建我選擇在一臺物理機上的Ubuntu 22.04 LTS中進行操作當然在配置足夠的虛擬機中也可以。首先我們需要安裝所有必要的開發工具和庫文件。打開終端執行以下命令來更新軟件包列表并安裝基礎編譯工具鏈sudo apt update sudo apt upgrade -y sudo apt install -y build-essential libncurses-dev libssl-dev bc \ flex bison libelf-dev dwarves zstd cpio git這里解釋一下幾個關鍵包的作用build-essential: 提供了gcc,g,make等最核心的編譯工具。libncurses-dev: 用于支持make menuconfig這種文本圖形化配置界面沒有它你就只能面對純文本配置文件或者用make xconfig需要圖形界面。libssl-devbc: 內核配置和編譯過程中某些環節如生成證書會用到。flexbison: 語法分析器生成器編譯內核時需要。libelf-dev: 處理ELF可執行與可鏈接格式文件所需的庫對現代內核構建至關重要。dwarves: 包含pahole工具用于優化內核數據結構布局對生成更高效的內核有幫助。zstd: 一種高效的壓縮算法內核鏡像壓縮可能會用到。注意如果你的Ubuntu版本較新或較舊某些包名可能有細微差別。安裝完成后可以用gcc --version和make --version驗證工具鏈是否就緒。2.2 獲取內核源碼獲取內核源碼主要有兩種途徑一是從Linux內核官方倉庫下載二是使用你的發行版如Ubuntu提供的內核源碼包。方案一從 kernel.org 獲取推薦用于深度定制這是最直接、最上游的方式。你可以訪問 https://www.kernel.org/ 查看最新穩定版stable或長期支持版longterm。使用wget下載例如下載6.6版本cd ~ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz tar -xvf linux-6.6.tar.xz cd linux-6.6方案二使用Ubuntu提供的內核源碼這種方式獲取的內核已經包含了Ubuntu特定的補丁和配置如果你只是想基于當前系統內核進行微調這可能更方便。sudo apt install linux-source-$(uname -r)安裝后源碼包通常是tar.xz格式會放在/usr/src/目錄下需要解壓。我個人更傾向于方案一因為它版本清晰沒有額外的補丁干擾更適合學習原理和進行干凈的定制。本次演示我們就使用從kernel.org下載的6.6版本源碼。實操心得在解壓源碼的目錄外單獨建立一個編譯輸出目錄build是很好的實踐可以將源碼樹和編譯生成的文件分離保持源碼目錄的整潔也方便進行多種配置的編譯。我們可以這樣操作mkdir -p ~/kernel_build cd ~/linux-6.6 # 后續的編譯輸出將指向 ~/kernel_build 目錄3. 內核配置定制的藝術這是整個過程中最核心、最考驗經驗的一步。內核有上萬個配置選項決定了最終內核支持什么硬件、包含什么功能、性能如何。3.1 配置界面與基礎方法進入內核源碼目錄我們有幾種配置方式make defconfig: 生成一個基于當前架構x86_64的默認配置。這是起點。make olddefconfig: 在已有.config文件基礎上用新內核版本的默認值來更新并自動解決新出現的選項。從舊配置升級時常用。make menuconfig: 基于ncurses的文本圖形化界面最常用不需要完整的圖形桌面環境。make xconfig: 基于Qt的圖形化配置界面更直觀但需要安裝Qt庫和圖形界面。make gconfig: 基于GTK的圖形化配置界面。對于服務器環境make menuconfig是首選。我們先生成一個默認配置然后進入定制界面cd ~/linux-6.6 make O~/kernel_build defconfig # 使用O指定輸出目錄 make O~/kernel_build menuconfig執行menuconfig后你會看到一個分層級的菜單界面。使用方向鍵導航空格鍵選擇[*]表示編譯進內核[M]表示編譯為模塊[ ]表示不編譯/鍵可以搜索配置項?鍵查看幫助。3.2 關鍵配置項解析與裁剪策略面對海量選項新手容易無從下手。我的策略是先繼承后裁剪再特化。繼承現有配置一個非常高效的方法是使用當前運行系統的配置作為起點。這能最大程度保證新內核能驅動你現有的硬件。cp /boot/config-$(uname -r) ~/kernel_build/.config make O~/kernel_build olddefconfig這條命令復制當前系統的配置并用新內核的默認值填充新增的選項。核心裁剪方向文件系統如果你只為特定設備制作鏡像可以只保留它需要的文件系統。例如只用于SD卡或eMMC的嵌入式設備可以主要保留ext4去掉btrfs,xfs,ntfs等。在File systems菜單下操作。設備驅動這是裁剪的大頭。在Device Drivers菜單下你可以根據目標硬件移除所有不相關的驅動。比如你的目標設備是ARM架構的嵌入式板沒有USB攝像頭、沒有特定的PCIe網卡就可以大膽移除。但要小心一些基礎驅動如ATA/ATAPI/MFM/RLL support,SCSI device support即使你用不到SCSI但很多子系統依賴它可能需要保留。網絡支持如果設備不需要網絡可以在Networking support中徹底禁用。如果需要則只啟用必要的協議如TCP/IP和網絡設備驅動如具體的網卡型號。調試與性能分析Kernel hacking下的很多調試選項會顯著增大內核體積并影響性能在生成最終產品鏡像時應關閉。內核壓縮模式在General setup-Kernel compression mode中可以選擇壓縮算法。zstd壓縮率好且解壓速度快是比較現代的選擇。一個重要的技巧使用localmodconfig如果你是為當前編譯的這臺機器定制內核有一個神器make O~/kernel_build localmodconfig這個命令會分析當前系統加載了哪些內核模塊lsmod的輸出然后修改.config文件只將當前正在使用的驅動和功能編譯進內核或模塊其他的全部禁用。這能生成一個非常精簡的配置。執行后記得再運行make olddefconfig處理一下依賴。踩坑記錄曾經有一次我裁剪得太激進把CONFIG_BLK_DEV_INITRD初始RAM磁盤支持給關掉了。結果內核無法加載initramfs導致系統啟動失敗。對于通用系統一些看似無關的基礎功能如initramfs支持、EFI支持、/dev和/proc文件系統支持一定要確認是否需要。配置完成后保存退出。配置文件會保存在~/kernel_build/.config。強烈建議備份這個文件。4. 內核編譯與安裝配置搞定接下來就是相對自動化的編譯過程了。4.1 啟動編譯過程使用make命令進行編譯。為了充分利用多核CPU加速編譯可以加上-j參數指定并行任務數。通常設置為CPU核心數 1或CPU核心數 * 2。你可以用nproc命令查看核心數。cd ~/linux-6.6 make O~/kernel_build -j$(($(nproc)1))這個過程視硬件性能而定可能需要十幾分鐘到一小時以上。你會看到大量的編譯信息滾動。如果中途出現錯誤通常是缺少某個依賴庫根據錯誤信息安裝對應的-dev包即可。4.2 安裝編譯產物編譯成功后我們需要安裝內核模塊和內核鏡像文件。安裝模塊模塊會被安裝到指定的目錄我們通常將其安裝到一個臨時目錄以便后續打包進根文件系統。sudo make O~/kernel_build modules_install INSTALL_MOD_PATH~/rootfs這里INSTALL_MOD_PATH~/rootfs指定了安裝路徑~/rootfs是我們接下來要構建的根文件系統目錄。如果不指定默認會安裝到宿主系統的/lib/modules下這可能會干擾現有系統。準備內核鏡像編譯生成的核心文件是vmlinuz壓縮的內核映像和System.map內核符號表。我們將其復制出來。cp ~/kernel_build/arch/x86/boot/bzImage ~/my_custom_kernel/vmlinuz-6.6.0-custom cp ~/kernel_build/System.map ~/my_custom_kernel/ cp ~/kernel_build/.config ~/my_custom_kernel/config-6.6.0-custom注意如果你是ARM等其它架構bzImage的路徑會是arch/arm/boot/zImage等。至此內核部分的工作就完成了。我們得到了一個定制化的內核文件vmlinuz-6.6.0-custom和對應的模塊位于~/rootfs/lib/modules/下。5. 構建根文件系統內核負責管理硬件和進程但一個可用的系統還需要用戶空間userspace的程序比如基本的shell、工具集ls,cp,mkdir等、初始化系統。這些就構成了根文件系統rootfs。5.1 使用debootstrap構建最小Ubuntu根文件系統對于基于Debian/Ubuntu的環境debootstrap是構建根文件系統的標準工具。它可以安裝一個最小的Ubuntu系統到指定目錄。sudo apt install debootstrap sudo mkdir -p ~/rootfs sudo debootstrap --archamd64 jammy ~/rootfs http://archive.ubuntu.com/ubuntu/--archamd64: 指定架構。jammy: Ubuntu 22.04的代號。~/rootfs: 目標目錄。http://archive.ubuntu.com/ubuntu/: 軟件源地址。這個過程會下載并安裝最基礎的包。完成后~/rootfs目錄下就是一個干凈的Ubuntu最小系統。5.2 基礎系統配置與必要軟件安裝現在我們需要chroot進入這個新系統進行一些配置。# 掛載必要的虛擬文件系統 sudo mount --bind /dev ~/rootfs/dev sudo mount --bind /dev/pts ~/rootfs/dev/pts sudo mount -t proc proc ~/rootfs/proc sudo mount -t sysfs sysfs ~/rootfs/sys # 使用chroot進入新環境 sudo chroot ~/rootfs /bin/bash現在你的終端環境已經切換到了新的根文件系統內。設置基礎信息# 設置主機名 echo my-custom-ubuntu /etc/hostname # 設置root密碼 passwd # 配置軟件源可選保持默認也可 # cat /etc/apt/sources.list EOF # deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse # deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe multiverse # deb http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse # EOF apt update安裝必要軟件根據你的需求安裝。至少需要apt install -y systemd-sysv udev sudo net-tools iproute2 iputils-ping vimsystemd-sysv: 提供systemd并替換sysvinit。udev: 動態設備管理。sudo: 權限管理。網絡工具用于調試。創建普通用戶可選但推薦adduser myuser usermod -aG sudo myuser配置網絡最簡單的配置是使用DHCP。cat /etc/netplan/01-netcfg.yaml EOF network: version: 2 renderer: networkd ethernets: eth0: dhcp4: yes EOF配置串口終端對于無顯示器的嵌入式設備很重要編輯/etc/systemd/system/getty.target.wants/gettytty1.service但更簡單的方法是創建一個systemd的覆蓋或直接修改inittab的替代品。對于使用systemd的系統可以啟用串口gettysystemctl enable serial-gettyttyS0.service # 將ttyS0替換為你的實際串口設備如ttyAMA0樹莓派退出chroot并卸載exit # 退出chroot環境 sudo umount ~/rootfs/dev/pts sudo umount ~/rootfs/dev sudo umount ~/rootfs/proc sudo umount ~/rootfs/sys5.3 整合自定義內核模塊還記得我們之前把模塊安裝到~/rootfs了嗎現在它們已經在正確的位置了~/rootfs/lib/modules/6.6.0-custom/。你需要確保模塊的依賴關系正確生成# 回到宿主環境在rootfs目錄外操作 sudo chroot ~/rootfs depmod -a 6.6.0-custom6. 制作可啟動磁盤鏡像現在我們有內核vmlinuz和根文件系統rootfs需要把它們打包成一個硬盤鏡像文件如img或qcow2這個鏡像可以被虛擬機直接啟動或者寫入到物理SD卡、硬盤中。6.1 創建空白鏡像并分區我們將創建一個1GB大小的RAW格式鏡像并分成兩個區一個小的boot分區存放內核和一個大的root分區存放根文件系統。cd ~ dd if/dev/zero ofmy-ubuntu.img bs1M count1024 # 創建1GB空白文件 sudo parted my-ubuntu.img --script mklabel msdos # 創建MBR分區表 sudo parted my-ubuntu.img --script mkpart primary fat32 1MiB 256MiB # 創建256M的FAT32 boot分區 sudo parted my-ubuntu.img --script mkpart primary ext4 256MiB 100% # 剩余空間創建ext4根分區6.2 掛載鏡像并格式化分區我們需要使用loop設備將鏡像文件虛擬成塊設備然后對分區進行格式化。# 查找可用的loop設備并關聯鏡像文件 LOOP_DEV$(sudo losetup --show -f -P my-ubuntu.img) echo $LOOP_DEV # 會輸出類似 /dev/loop0 # 格式化分區 sudo mkfs.vfat -F 32 ${LOOP_DEV}p1 # 格式化boot分區為FAT32 sudo mkfs.ext4 ${LOOP_DEV}p2 # 格式化root分區為ext4 # 創建掛載點并掛載 mkdir -p mnt/{boot,root} sudo mount ${LOOP_DEV}p1 mnt/boot sudo mount ${LOOP_DEV}p2 mnt/root6.3 復制文件系統與內核復制根文件系統使用rsync或cp復制rsync能更好地保留權限和屬性。sudo rsync -a ~/rootfs/ mnt/root/安裝內核與引導程序復制內核到boot分區sudo cp ~/my_custom_kernel/vmlinuz-6.6.0-custom mnt/boot/vmlinuz安裝引導程序這里以extlinux為例它比GRUB更輕量sudo apt install extlinux sudo extlinux --install mnt/boot/創建extlinux配置文件sudo tee mnt/boot/extlinux.conf EOF DEFAULT linux LABEL linux SAY Booting My Custom Ubuntu... KERNEL /vmlinuz APPEND root/dev/sda2 rw quiet splash # 注意root/dev/sda2 指定了根分區。在虛擬機或實際設備中這個設備名可能會變如/dev/mmcblk0p2。 # 更穩妥的做法是使用UUID或PARTUUID。 EOF獲取root分區的UUIDsudo blkid ${LOOP_DEV}p2然后將root/dev/sda2替換為rootUUIDxxxx-xxxx。6.4 收尾工作# 同步數據并卸載 sync sudo umount mnt/boot sudo umount mnt/root sudo losetup -d $LOOP_DEV # 解除loop設備關聯 # 清理掛載點 rm -rf mnt現在my-ubuntu.img就是一個包含了你的自定義內核和根文件系統的可啟動磁盤鏡像了。你可以用qemu虛擬機測試它qemu-system-x86_64 -drive filemy-ubuntu.img,formatraw -m 10247. 常見問題與排查技巧實錄在整個過程中你可能會遇到各種問題。這里記錄幾個我遇到過的典型問題及解決方法。7.1 編譯階段錯誤錯誤fatal error: openssl/opensslv.h: No such file or directory原因缺少OpenSSL開發頭文件。解決安裝libssl-dev包。sudo apt install libssl-dev錯誤recipe for target ‘scripts/mod/empty.o’ failed原因通常是因為bc計算器工具未安裝或版本問題。解決確保已安裝bc。sudo apt install bc錯誤No rule to make target ‘debian/canonical-certs.pem’原因這是Ubuntu內核源碼特有的簽名問題。使用上游kernel.org源碼通常不會遇到。解決在.config中關閉CONFIG_SYSTEM_TRUSTED_KEYS和CONFIG_SYSTEM_REVOCATION_KEYS將其值設為空字符串。7.2 啟動階段錯誤現象內核panic提示VFS: Unable to mount root fs on unknown-block(0,0)原因內核找不到根文件系統。這是最常見的問題。排查檢查內核配置確認CONFIG_BLK_DEV_INITRD如果需要initramfs和對應文件系統如CONFIG_EXT4_FSy已編譯進內核y而不是模塊m。檢查引導參數檢查extlinux.conf或grub.cfg中的root參數。設備名如/dev/sda2在虛擬機、物理機、不同控制器下會變化。強烈建議使用UUID。獲取UUIDsudo blkid /dev/sdX2(或你的分區)。修改引導配置rootUUIDxxxx-xxxx檢查根文件系統完整性確保rootfs復制完整且必要的目錄/dev,/proc,/sys,/run在啟動后能被正確掛載由systemd或init腳本完成。現象啟動后卡住無法進入登錄提示符原因可能是初始化系統systemd或getty服務出現問題。排查在引導參數的root后面加上init/bin/bash例如root/dev/sda2 rw init/bin/bash。這會讓內核直接啟動bashshell而不是systemd。如果能進入bash檢查/var/log/boot.log或journalctl的日志如果journal可用。檢查/etc/systemd/system/getty.target.wants/下的服務是否正常。可以嘗試手動啟動/lib/systemd/systemd-getty tty1。7.3 鏡像相關與測試問題QEMU啟動報錯Could not read from CD-ROM (code 0003)原因QEMU默認會嘗試從CD-ROM啟動。解決在QEMU命令中顯式指定從硬盤啟動并關閉CD-ROM-boot orderc -cdrom none制作的鏡像文件太大原因dd創建的鏡像文件是“稀疏文件”的相反它實實在在地寫入了1GB的零數據。優化可以使用truncate創建稀疏文件或者編譯完成后用qemu-img convert轉換并壓縮為qcow2格式后者支持動態分配空間更省磁盤。qemu-img convert -f raw -O qcow2 my-ubuntu.img my-ubuntu.qcow2使用qemu-img啟動時指定-drive filemy-ubuntu.qcow2,formatqcow2。整個流程走下來雖然步驟繁多但每一步都有其明確的目的。從內核配置的精細打磨到根文件系統的精心組裝再到鏡像的最終合成你獲得的不僅僅是一個可啟動的鏡像更是對Linux系統從內核到用戶空間的完整認知。當你第一次看到屏幕上滾動著自己編譯的內核的啟動信息并成功登錄到自己構建的系統時那種成就感是無可替代的。這為你后續進行更深入的性能調優、安全加固或嵌入式定制打下了堅實的基礎。