
1. 项目缘起为什么我们需要自己编译内核在Linux世界里内核Kernel是操作系统的绝对核心它负责管理硬件、内存、进程和所有系统资源。我们日常使用的Ubuntu发行版其内核是经过上游社区和Ubuntu团队精心配置、测试和打包的通用版本。这个“通用”版本为了兼容尽可能多的硬件和场景往往包含了大量的驱动模块和功能这既是它的优点也成了某些特定场景下的“负担”。你可能已经习惯了sudo apt install linux-image-generic然后重启就完成了内核更新。但如果你遇到以下情况通用内核可能就不够用了硬件驱动问题你有一块最新的网卡、声卡或显卡但Ubuntu官方仓库的内核版本尚未包含其驱动。或者你为了追求极致性能需要启用某个实验性的驱动特性。内核功能裁剪你正在为一个资源受限的嵌入式设备比如基于RK3568的开发板或虚拟机环境构建系统。通用内核里那些用不上的无线网卡驱动、老旧SCSI控制器驱动、冗余的文件系统支持都在白白占用宝贵的内存和存储空间。性能调优与安全加固你需要针对特定的工作负载如数据库、Web服务器调整内核参数或者打上某个尚未被主线合并的安全补丁、性能优化补丁。学习与深度定制你想真正理解Linux内核的构建过程、模块机制或者需要修改内核源码来实现特定的功能。自己动手编译内核就是从源码开始按照你的需求“量体裁衣”打造一个专属的操作系统心脏。这个过程听起来很“硬核”但一旦走通你对Linux系统的掌控力将提升一个维度。它不仅仅是生成一个vmlinuz文件更是一个包含内核镜像、模块、头文件以及启动配置的完整“交付物”。接下来我将以一个Ubuntu 22.04 LTS环境为例带你完整走一遍从获取源码、配置、编译到制作可启动镜像的全过程并分享我踩过的那些坑和总结出的实用技巧。2. 环境准备搭建稳定高效的编译工场内核编译是一个计算和I/O密集型的任务一个稳定、干净且资源充足的编译环境至关重要。盲目开始很容易在中间步骤遇到各种依赖缺失或环境冲突问题。2.1 系统与资源要求首先确保你有一个可用的Ubuntu系统。我强烈建议在一个物理机或分配了足够资源的虚拟机VMware/VirtualBox中进行而不是在WSLWindows Subsystem for Linux下进行首次尝试。虽然WSL2理论上可行但其虚拟化层和文件系统性能特别是NTFS到ext4的转换可能会显著拉长编译时间并且在一些涉及底层设备或启动的步骤上可能遇到意外问题。资源建议CPU至少4核越多越好。编译过程可以高度并行化。内存至少8GB推荐16GB或以上。内存不足会导致编译过程中止甚至系统卡死。磁盘空间为内核源码、编译中间文件和最终产出预留至少30GB的剩余空间。源码本身约1GB但编译目录build/会膨胀到20GB以上。注意如果你是在虚拟机中操作请确保虚拟磁盘不是“动态分配”且快满的状态。编译过程中产生的大量小文件读写在空间紧张的动态磁盘上性能极差。2.2 安装必备的编译工具链与依赖包这是避免“编译到一半报错”的关键一步。我们需要安装编译器、链接器、库文件以及内核编译相关的工具。打开终端执行以下命令更新软件源并安装核心工具sudo apt update sudo apt upgrade -y接下来安装编译必需的基础工具和库。这个命令列表是我多次实践后总结的比较全面sudo apt install -y build-essential libncurses-dev libssl-dev bc \ flex bison libelf-dev dwarves zstd rsync git kmod cpio \ fakeroot initramfs-tools u-boot-tools逐项解释一下这些包的作用build-essential: 包含GCC编译器、make等核心编译工具。libncurses-dev: 提供make menuconfig图形化配置界面所需的库。libssl-devbc: 内核配置和构建过程中某些脚本会用到。flexbison: 语法分析器生成器用于处理内核构建系统中的某些语法文件。libelf-devdwarves: 处理ELF格式文件和DWARF调试信息pahole工具需要对于生成正确的模块依赖关系modules.order很重要。zstd: 高效的压缩算法现代内核镜像常用它进行压缩。rsyncgit: 用于同步源码或获取补丁。kmod: 管理内核模块的工具集。cpiofakeroot: 在制作initramfs和打包时需要用到的工具。initramfs-tools: 制作初始内存文件系统的工具对于从硬盘启动至关重要。u-boot-tools: 如果你是为嵌入式设备如RK3568制作镜像可能需要用到mkimage等工具来封装U-Boot可识别的镜像格式。安装完成后可以通过gcc --version和make --version验证基础工具是否就位。3. 获取与准备内核源码有几种方式可以获取Linux内核源码选择哪种取决于你的需求。3.1 选择源码来源Ubuntu官方内核源码包推荐给Ubuntu用户 这是最贴近你当前系统运行内核的源码兼容性最好。使用apt获取apt source linux-source执行后会在当前目录下解压出一个类似linux-5.15.0的目录并附带Ubuntu应用的补丁。这是为当前Ubuntu发行版定制编译的最安全起点。主线内核kernel.org 如果你想尝试最新的特性或者你的硬件需要非常新的驱动可以从 https://www.kernel.org 下载稳定版stable源码。使用wget或git克隆wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.9.tar.xz tar -xf linux-6.9.tar.xz或者git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git为特定开发板获取如RK3568 嵌入式芯片厂商通常会提供适配其SoC的BSPBoard Support Package内核。你可能需要从Rockchip的GitHub仓库或芯片供应商的SDK中获取特定的分支。我的建议初次尝试请使用方法一Ubuntu官方源码。这能最大程度保证编译出的内核与你系统中的用户态库如glibc和内核模块依赖完美兼容避免出现“内核panic”或模块无法加载的问题。3.2 清理与准备工作目录假设我们使用Ubuntu官方源码解压后进入目录cd linux-5.15.0在开始配置前进行一次彻底的清理是个好习惯可以避免残留的旧配置或中间文件干扰make mrproper这个命令会删除所有的编译产出文件以及配置文件.config将目录恢复到源码刚解压的状态。注意make mrproper非常彻底。如果你之前已经辛苦配置好了一个.config文件请务必先备份它cp .config .config.bak否则这个命令会将其删除。另一个温和的清理命令是make clean它只删除编译产出保留配置文件。4. 内核配置量身定制的艺术这是整个过程中最具技术含量也最耗时的一步。你需要决定内核包含哪些功能、驱动和子系统。.config文件就是这份“建造蓝图”。4.1 配置方法的选择有几种方式生成.config文件make defconfig: 生成一个基于当前架构x86_64的默认配置。这是一个功能相对完整的起点。make olddefconfig: 如果你有一个旧的.config文件或者使用发行版提供的配置作为基础这个命令可以基于旧配置和新的内核源码以默认值def静默地处理所有新增的配置项非常有用。make menuconfig: 基于文本的图形化菜单配置界面需要libncurses-dev。这是最常用、最直观的方式可以逐项浏览和修改。make xconfig/make gconfig: 基于Qt或GTK的图形界面配置工具需要桌面环境支持。对于新手和大多数情况我推荐使用make menuconfig。它不需要图形桌面在终端中即可操作界面清晰。4.2 使用menuconfig进行高效配置执行make menuconfig后你会进入一个蓝底黄字的菜单界面。使用方向键导航Enter键进入子菜单或选择选项Y键编译进内核*N键排除[ ]M键编译为可加载模块M/键搜索Esc键返回上级?键查看帮助。核心配置策略与经验获取当前运行内核的配置作为黄金起点 最安全、最省力的方法是直接复用你当前正在运行的内核配置。它已经被Ubuntu团队优化过能保证你的硬件基本工作。cp /boot/config-$(uname -r) .config make olddefconfig第一条命令将当前内核的配置复制到源码根目录。第二条命令让内核构建系统根据新的源码树自动为所有新增的配置项设置合理的默认值通常是N或模块化并保持你原有的选择。这能解决90%的配置问题。关键配置区域检查General setup - Local version在这里你可以设置一个自定义后缀比如-mycustom。这样编译出的内核版本号会是5.15.0-mycustom便于与你系统原有的内核区分。Processor type and features确保这里的CPU类型与你硬件匹配如Intel/AMD以及特定的微架构特性。对于虚拟机通常选择Generic x86_64即可。Device Drivers这是最大的部分。你可以在这里精简掉绝对用不上的驱动比如老旧的ISA声卡、不存在的RAID卡、特定的工业总线等。但务必小心如果你不确定某个驱动是否需要尤其是存储控制器SCSISATANVMe、文件系统ext4BtrfsVFAT、网络设备EthernetWireless和USB主控的驱动最好保持为模块M或直接编译进内核*。驱动缺失可能导致无法启动或找不到根文件系统。File systems确保你的根文件系统类型通常是ext4被支持最好是*而不是M。DOS/FAT/NT支持、Userspace xattr等可以根据需要选择。Kernel hacking对于生产环境可以关闭这里的调试选项以减少内核大小和性能开销。对于学习可以保留KGDB等。为嵌入式设备配置 如果你是为RK3568这样的ARM设备编译情况更特殊。你通常需要指定正确的架构make ARCHarm64 defconfigRK3568是arm64。使用厂商提供的默认配置文件make ARCHarm64 rockchip_linux_defconfig具体名称需查厂商文档。重点关注Device Tree的支持因为ARM设备普遍使用设备树来描述硬件。配置完成后连续按Esc键退出并选择Yes保存配置到.config文件。5. 编译与安装从源码到可执行文件配置完成后就可以开始编译了。这个过程耗时很长取决于你的CPU核心数和性能。5.1 启动编译进程使用-j参数指定并行编译的作业数通常设置为CPU核心数的1到2倍以充分利用多核性能。你可以用nproc命令查看核心数。make -j$(nproc)或者如果你不确定直接使用-j$(nproc)make -j$(nproc)现在泡杯咖啡等待编译完成。终端会滚动输出大量的编译信息。如果一切顺利最终你会看到内核镜像vmlinuz、模块等文件生成。5.2 处理编译中的常见错误编译过程并非总是一帆风顺以下是几个我常遇到的坑fatal error: openssl/opensslv.h: No such file or directory 这通常是libssl-dev没装好。重新安装它sudo apt install --reinstall libssl-dev。pahole not found或DWARF相关错误 现代内核构建需要pahole工具来处理BTFBPF Type Format信息。确保你安装了dwarves包。如果还报错可以尝试暂时禁用CONFIG_DEBUG_INFO_BTF。在.config文件中找到这一行将其改为CONFIG_DEBUG_INFO_BTFn然后重新编译。但这会牺牲一些BPF相关的调试功能。内存耗尽OOM Killer 编译过程极其消耗内存。如果系统开始卡顿终端进程突然消失可能是OOM Killer杀掉了编译进程。尝试减少并行作业数make -j2或者增加虚拟内存交换分区。5.3 安装编译产出编译成功后需要安装内核模块和内核镜像。安装模块 将编译好的内核模块.ko文件安装到系统的/lib/modules/$(uname -r)-mycustom/目录下。务必使用sudo因为需要写入系统目录。sudo make modules_install这个命令会根据.config中的CONFIG_LOCALVERSION我们之前设置的-mycustom创建对应的模块目录。安装内核镜像 将内核镜像文件arch/x86/boot/bzImage复制到/boot目录并为其生成一个initramfs。sudo make install这个make install脚本通常会做三件事将bzImage复制为/boot/vmlinuz-5.15.0-mycustom。调用update-initramfs生成对应的initramfs镜像/boot/initrd.img-5.15.0-mycustom。更新GRUB引导菜单。执行后检查/boot目录下是否出现了带有你自定义版本号的新文件。重要提示make install依赖于你系统上的installkernel脚本。在某些最小化系统或自定义环境中这个脚本可能不存在或行为不同。如果make install失败你需要手动完成上述步骤sudo cp arch/x86/boot/bzImage /boot/vmlinuz-5.15.0-mycustom sudo mkinitramfs -o /boot/initrd.img-5.15.0-mycustom 5.15.0-mycustom sudo update-grub6. 制作可启动镜像从文件到可部署的实体“安装”步骤已经为当前主机生成了可启动的组件。但“镜像制作”通常指的是创建一个独立的、可以写入磁盘或烧录到嵌入式设备存储的完整系统镜像文件。这比单纯的主机安装更进一步。6.1 针对本地PC/虚拟机制作可引导的ISO或磁盘镜像有时你需要制作一个包含自定义内核的Live CD/USB或虚拟机磁盘镜像。方法A使用debootstrap构建根文件系统并打包这是一种从零开始构建一个最小化Ubuntu根文件系统的方法非常干净。创建并挂载镜像文件dd if/dev/zero ofmyrootfs.img bs1M count2048 # 创建2GB的空镜像文件 mkfs.ext4 myrootfs.img # 格式化为ext4 sudo mkdir -p /mnt/myroot sudo mount -o loop myrootfs.img /mnt/myroot使用debootstrap安装基本系统sudo debootstrap jammy /mnt/myroot http://archive.ubuntu.com/ubuntu/jammy是Ubuntu 22.04的代号将自定义内核安装到该根文件系统中 这比较繁琐需要chroot进这个新系统环境进行操作。你需要将编译好的内核模块、镜像、头文件等复制进去并在chroot后运行update-initramfs和update-grub。安装引导程序GRUB 同样需要在chroot环境中操作或者使用grub-install并指定目标设备。卸载并打包sudo umount /mnt/myroot现在myrootfs.img就是一个包含你自定义内核的根文件系统镜像了。你可以将其作为虚拟机的虚拟磁盘或者进一步与引导扇区结合制作成完整磁盘镜像。方法B修改现有发行版ISO更实用对于大多数想测试自定义内核的开发者一个更简单的方法是直接修改Ubuntu的官方ISO。解压ISO文件。将ISO中casper/目录下的vmlinuz和initrd替换为你自己编译的。重新计算ISO的MD5校验和md5sum.txt。使用mkisofs或genisoimage重新打包成ISO。这个过程涉及对引导流程的深入理解且不同发行版的ISO结构不同容易出错。对于快速测试我更推荐直接在虚拟机里编译安装。6.2 针对嵌入式设备如RK3568制作可烧录的SD卡/eMMC镜像嵌入式设备的镜像制作是另一个专业领域通常需要厂商提供的打包工具。获取Bootloader通常是U-Boot。你需要为你的具体板型编译或获取预编译的U-Boot镜像idbloader.imgu-boot.itb等。准备内核与设备树编译内核时除了ImageARM64的内核镜像文件还会生成一个或多个设备树二进制文件.dtb。这些文件描述了板载硬件。构建根文件系统可以使用debootstrap构建最小Debian/Ubuntu或使用Buildroot、Yocto等工具构建更精简的系统。使用厂商工具打包Rockchip提供了rkdeveloptool或基于dd命令的特定偏移量打包脚本。一个典型的SD卡镜像布局可能是偏移量 0x0000: Bootloader分区1偏移量 0x4000: Bootloader分区2偏移量 0x8000: 内核与设备树分区FAT32格式包含Image和.dtb后续偏移量: 根文件系统分区ext4格式制作命令可能类似于sudo dd ifidbloader.img of/dev/sdX seek64 sudo dd ifu-boot.itb of/dev/sdX seek16384 sudo mkfs.vfat /dev/sdX1 # 创建并格式化内核分区 sudo mount /dev/sdX1 /mnt sudo cp Image rockchip.dtb /mnt/ sudo umount /mnt sudo mkfs.ext4 /dev/sdX2 # 创建并格式化根文件系统分区 sudo mount /dev/sdX2 /mnt sudo tar -xpf rootfs.tar.gz -C /mnt # 解压根文件系统 sudo umount /mnt/dev/sdX是你的SD卡设备操作前务必确认无误7. 测试、引导与故障排查新内核制作完成后真正的挑战才刚刚开始——让它成功跑起来。7.1 更新GRUB并引导测试针对本地PC在主机上安装新内核后GRUB引导菜单通常会自动更新。重启系统在GRUB菜单界面可能需要按Shift或Esc键调出选择你新编译的内核条目名称中会包含你的本地版本号如Ubuntu with Linux 5.15.0-mycustom。如果启动成功登录后使用uname -r命令应该能看到你的自定义内核版本号。7.2 常见启动失败与排查手段如果系统卡住、黑屏或进入紧急模式emergency mode别慌按以下思路排查查看引导日志在GRUB菜单界面选中你的内核条目按e键进入编辑模式。找到以linux开头的那一行在行尾在quiet和splash参数之后如果有的话添加以下参数loglevel7 ignore_loglevel按CtrlX或F10启动。这样会打印出详细的内核启动日志到屏幕。分析日志关键信息Kernel panic 通常伴随错误信息如“Unable to mount root fs”。这很可能是因为内核缺少必要的文件系统驱动如ext4或存储控制器驱动如NVMeAHCI。找不到根设备 日志中显示根设备UUID或路径无法挂载。检查.config中CONFIG_BLK_DEV、CONFIG_ATA、CONFIG_NVME等存储相关驱动是否启用。同时确认initramfs是否包含了对应的模块。可以尝试在GRUB参数中添加root/dev/sdXY如root/dev/nvme0n1p2直接指定设备。Initramfs相关错误 如果initramfs制作失败或损坏系统无法在挂载根文件系统前加载必要的模块。可以尝试在旧内核中重新生成sudo update-initramfs -c -k 5.15.0-mycustom。使用旧内核回退 这是最重要的安全网。在GRUB菜单中选择之前正常工作的旧内核启动。进入系统后你可以检查/var/log/kern.log或使用journalctl -k查看上次启动失败的内核日志获得更完整的错误信息。精简配置重新编译 如果问题指向特定驱动缺失最根本的解决方法是回到.config确保相关选项被正确启用*或M。对于不确定的、非关键的驱动可以先设为模块M让initramfs去加载。initramfs工具update-initramfs会自动将/lib/modules/下对应版本内核的所有模块打包进去。7.3 嵌入式设备启动排查嵌入式设备没有GRUB通常通过串口调试UART来查看启动日志。你需要一个USB转TTL串口线连接开发板的调试口。在PC上使用串口终端工具如minicompicocomscreen打开对应的串口设备如/dev/ttyUSB0设置正确的波特率常见如1500000。给开发板上电终端会打印出U-Boot和内核的启动信息。排查思路与PC类似观察内核是否解压成功、设备树DTB是否加载正确、根文件系统是否挂载。常见的嵌入式启动失败原因包括设备树文件不对、内核镜像格式不对未用mkimage封装、根文件系统路径或格式错误、存储驱动缺失等。自己编译Linux内核并制作镜像是一个从理解到实践的完整闭环。它强迫你去关注那些在普通使用中被隐藏起来的细节引导流程、硬件抽象、模块依赖、文件系统构建。第一次成功启动自己编译的内核时那种成就感是无可替代的。这个过程里最大的经验就是永远从已知可工作的配置开始如当前系统配置每次只做小的、目标明确的修改并做好每一步的备份。当遇到启动失败时详细的日志是你的最佳伙伴而一个可回退的旧内核则是你的安全绳。