☰
CentOS 6.9原生内核编译实操:从源码到grub引导
2026/10/1 14:44:08 网站建设 项目流程

手里的机器还跑着 CentOS 6.9,想折腾内核又不敢直接上主线版本,最后我选择把 CentOS 的 PC 机原生内核代码拉下来自己编译了一遍——就是系统自带那一套 2.6.32-696.el6 的完整源码。整个过程从环境准备到 grub 引导大概花了一下午,中间踩了四五个坑,但好处是:编译产物和系统里的内核模块、工具链完全同源,装回去几乎不用额外适配。下面就用我这次实操的记录,把完整流程、关键命令和踩过的坑梳理出来,适合想在老系统上学习内核编译、或者打算深度定制 CentOS 6 内核的读者。

1. 先搞清楚:CentOS 6.9 的原生内核源码到底是什么

1.1 "原生"不是从 kernel.org 拉主线内核

很多新手一说到"编译内核",第一反应就是去 kernel.org 下载最新的 5.x/6.x tar.xz,然后在老机器上开编。这个思路放在 CentOS 6.9 上基本是自找麻烦:系统里的 glibc、gcc、util-linux 和引导工具都是围绕 2.6.32 时代的接口设计的,主线内核里的 Kconfig、构建脚本、头文件依赖早就换了写法,经常会遇到一些莫名其妙的"明明选项开着却编译不过"的问题。更关键的是,CentOS 6.9 上跑的软件环境和内核模块很多都依赖 2.6.32 系列导出的符号和 ABI,随便换一个主线内核,很可能导致系统工具、第三方内核模块无法加载。

我这里说的"CentOS 的 PC 机原生内核代码",指的是 CentOS 官方随发行版一起发布的、针对 x86_64 PC 架构构建的完整内核源码包,也就是 kernel-2.6.32-696.el6.src.rpm 里的那套源码。它带着 Red Hat/CentOS 的大量定制补丁和稳定的默认配置,和系统当前跑的内核几乎同源。用这套代码编译出来的东西,理论上和发行版自带内核行为一致,适合做定制修改,也适合第一遍练手。

1.2 先确认当前内核版本和架构

编译前第一步不是装工具,而是确认你当前系统到底跑的是什么。在终端执行:

uname -r

正常情况下 CentOS 6.9 的 64 位系统会输出类似2.6.32-696.el6.x86_64。后面的x86_64就是 PC 机的 64 位架构标识。这个信息在后面从/boot/config-*找配置、给自定义内核命名时都要用到。

还要顺手确认 CPU 核数和内存大小:

nproc free -m

我建议至少留 2 GB 内存和 10 GB 磁盘空间给编译产物。老内核源码解包后大概 600 MB 左右,编译过程会生成大量中间 .o 文件,整个目录树很快就超过 2 GB,如果加上模块,磁盘占用会更多。别小看这一步,很多人在编译到一半才遇到磁盘写满,清理起来非常麻烦。

1.3 为什么选原生源码而不是主线内核

我做个对比帮你看清楚:

对比项CentOS 原生内核源码kernel.org 主线内核
版本2.6.32-696.el64.x/5.x/6.x
编译器兼容性gcc 4.4.7 可直接编译老 gcc 经常报错
系统模块兼容与系统导出的符号一致第三方驱动可能要重编
默认配置带有 RHEL/CentOS 定制偏向通用功能
适合场景学习、定制、排查尝鲜、新硬件支持

对 CentOS 6.9 这种老系统来说,原生源码是风险和收益最平衡的选择。这也是标题里"原生"这两个字最重要的含义——不要把它理解成"从零手写",而是"和系统天然同源"。

2. 编译环境的搭建与源码解包

2.1 先安装编译工具链

CentOS 6.9 的 yum 仓库虽然老了,但该有的编译工具一套都不少。最省事的是直接装整个开发工具组:

yum groupinstall "Development Tools"

这个会把 gcc、make、binutils、flex、bison 等基础编译组件一起装好。之后还要补几个内核编译会用到的依赖:

yum install -y ncurses-devel elfutils-libelf-devel openssl-devel bc

ncurses-devel是给make menuconfig用的,如果没有它,menuconfig 会直接报错;elfutils-libelf-devel提供编译过程中的 ELF 工具头文件;openssl-devel是备用的,某些内核模块或脚本会引用它的头文件;bc用来处理内核构建脚本里的数学运算。

这里有一个容易忽略的细节:有些精简环境连/usr/include/linux头文件都不全,可以顺手把kernel-devel也装上作为头文件兜底。不过注意,kernel-devel不是完整源码,它只是编译外部模块用的头文件目录,和我们要的"完整内核源码"是两回事。

2.2 准备 rpmbuild 目录并解包 src.rpm

CentOS 6.9 的完整内核源码以 src.rpm 的形式分发,推荐从 CentOS Vault 源下载kernel-2.6.32-696.el6.src.rpm。下载后先创建标准的 rpmbuild 目录:

mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} rpm -i ~/kernel-2.6.32-696.el6.src.rpm

执行完rpm -i后,源码会散落到~/rpmbuild/SOURCES/和~/rpmbuild/SPECS/。SOURCES里通常会有一个类似linux-2.6.32-696.el6.tar.bz2的大压缩包,这就是完整内核源码;SPECS里是kernel.spec,RHEL 系就是靠它来把源码构建成发行版内核 RPM。

如果你不想用 rpm 工具,直接解包也可以:

rpm2cpio kernel-2.6.32-696.el6.src.rpm | cpio -idmv

两种方式都能拿到源码包。我个人更推荐第一种,因为还会生成SPECS/kernel.spec,后面如果想把修改过的内核打成 RPM,直接基于这个 spec 文件操作会方便很多。

2.3 解压源码并移到工作目录

拿到linux-2.6.32-696.el6.tar.bz2后,把它解压到一个专门的工作目录:

mkdir -p ~/kernel-build cd ~/kernel-build tar xjf ~/rpmbuild/SOURCES/linux-2.6.32-696.el6.tar.bz2 cd linux-2.6.32-696.el6.x86_64

解压后目录名一般是linux-2.6.32-696.el6.x86_64。注意千万别在~/rpmbuild/BUILD里直接开编,虽然技术上可行,但 rpmbuild 后续流程容易干扰,我自己试过在里面编译,最后 BUILDROOT 结构乱成一团。单独建一个编译目录是最干净的做法。

2.4 源码树里只需盯住这几个地方

进入源码目录后,先别急着执行命令,花几分钟熟悉结构。对这次编译任务来说,真正会频繁打交道的是这几个目录:

  • arch/:x86 架构相关的启动代码和构建脚本,编译完的.config、Makefile也会在这里体现。
  • drivers/:设备驱动源码,占整个源码树的体积大头,编译时间主要花在这里。
  • fs/:文件系统实现,ext4、xfs 都在这里。
  • include/:内核头文件,编译模块和很多底层工具都会引用。
  • init/:内核初始化流程,如果你改启动参数,大概率要碰这个目录。

不需要通读源码,但知道这些目录在哪里,出问题时定位会快很多。

3. 内核配置:别从零开始,用系统现成配置

3.1 从 /boot 复制当前配置

内核编译前必须有一份.config配置。我从第一次编译起就坚持一个原则:能基于系统现成配置起步,就不要从make menuconfig的空模板开始。原因很简单,发行版的内核配置经过厂商上千台机器的测试,默认开启的模块和内置项都是够用的,你自己从零选,很容易漏掉磁盘控制器、文件系统或者网卡驱动,导致编译出来的内核根本起不了机。

把当前运行内核的配置复制过来:

cp /boot/config-2.6.32-696.el6.x86_64 .config

3.2 make oldconfig:处理新增选项

CentOS 6.9 的源码包虽然是 2.6.32,但因为带了补丁,配置项和/boot/config-*不完全一致,直接拿旧配置编译时会多出一些"源码里有、旧配置里没定义"的选项。此时需要执行:

make oldconfig

它会逐个列出新增选项,让你选 y/m/n。如果是第一次操作,最省事的做法是全部回车使用默认值。别担心选项太多,绝大多数新增项默认值就是合理值。想完全自动化也可以:

make olddefconfig

它能直接用默认值补齐所有新选项,不需要人工应答,适合想快速出结果的人。但如果你想顺便研究一下内核里多了什么东西,oldconfig逐项确认反而是一次好的学习机会。

3.3 用 menuconfig 做几个必要调整

配置补齐后,我一般会再开一次图形配置界面确认关键功能:

make menuconfig

作为老系统,重点关注这几项:

  • Processor type and features里的Symmetric multi-processing support,确保 SMP 开启。
  • Device Drivers里的Serial ATA and Parallel ATA drivers,确认磁盘控制器驱动以*或m形式存在。
  • File systems里确保ext4、xfs至少有一个是内置(*),否则根文件系统可能挂不上。
  • 如果你编译后有nouveau或 NVIDIA 显卡需求,Device Drivers -> Graphics support下的相关项要提前选好。

改配置时记住一个思想:把根文件系统、磁盘控制器、系统盘所在总线的驱动尽量编成*(内置),而不是m(模块)。因为 initramfs 正常会加载模块,但如果你的 mkinitrd 命令或者 init 流程有问题,内置驱动能大概率兜底让你进入系统。

3.4 自定义版本字符串,避免覆盖原内核

这里有个非常实用的细节:在General setup里,有一项Local version - append to kernel release,默认是空的。你可以填一个比如-custom,这样编译出来的内核版本就是2.6.32-696.el6.x86_64-custom,和系统原有内核区分开。这个习惯在多次编译时特别重要,既不会覆盖/lib/modules里原有目录,也方便grub.conf里识别哪个是哪个。

3.5 不想进菜单?用 scripts/config 直接改

如果你在纯命令行环境,或者觉得 menuconfig 一层层翻目录太慢,可以用内核自带的scripts/config脚本做精确修改。比如把某个选项设为模块:

scripts/config -m CONFIG_XXX scripts/config --disable CONFIG_YYY scripts/config --enable CONFIG_ZZZ

改完再执行make olddefconfig让它把依赖关系重新理顺。这个方式在写自动化编译脚本时特别方便,我后面几轮编译基本都在用它。

4. 编译过程与常见报错实录

4.1 编译命令的正确顺序

配置就绪后,进入正式的编译阶段。我的推荐顺序是:

make -j4 bzImage make -j4 modules make modules_install

第一步make bzImage编译内核主体,产物是arch/x86/boot/bzImage;第二步make modules编译所有选为m的模块;第三步make modules_install把模块安装到/lib/modules/<kernel-version>/。

为什么不直接make -j4?因为make -j4会同时编译 vmlinux 和 modules,在老机器上更容易出现内存吃紧、日志被刷屏的问题。分开执行,每一步都更可控,报错也更容易定位。-j的数值一般取 CPU 核数的 1.5 到 2 倍,但在只有 2 GB 内存的老机器上,建议别超过 4,否则 OOM 是家常便饭。

4.2 报错一:gcc 版本与 compiler-gcc 头文件

CentOS 6.9 自带的 gcc 是 4.4.7,理论上编译 2.6.32 源码完全没问题。但我有一次为了图新,用 yum 装了个更新的 gcc 5.x,结果在内联函数和某些内建函数上报错。报错长这样:

include/linux/compiler-gcc5.h: No such file or directory

原因是内核源码里没有对应高版本 gcc 的处理头文件。遇到这种问题,最直接的解法是换回系统自带 gcc:

yum remove gcc-5.x yum install gcc-4.4.7

记住,在老内核上,编译器版本不是越新越好。2.6.32 时代的源码是为 gcc 4.x 写的,用 gcc 4.4.7 最稳妥。

4.3 报错二:No rule to make target

另一个常见报错是:

make: *** No rule to make target `arch/x86/tools/relocs_64.c', needed by `arch/x86/boot/compressed/relocs.o'. Stop.

这种一般是源码树不完整,或者你解压时只解了一部分。我遇到过一种情况:在 Windows 下用 WinRAR 解压 tar.bz2 后传到 Linux,结果某些特殊文件(比如带冒号或隐藏属性的文件)没解出来。解决方案很简单,回到 Linux 环境重新用tar解压一遍,不要跨平台解压源码包。

4.4 并行编译时的内存控制

如果你在编译过程中看到这种提示:

virtual memory exhausted: Cannot allocate memory

说明并行编译任务太多,内存不够。解决办法是把-j值调小,甚至退回到make -j1 bzImage。老机器上编译内核本来就是等待的过程,放点音乐,别急。

另一个小技巧是编译期间关闭图形桌面环境,把内存让给 gcc。服务器环境一般没这个问题,PC 机如果跑着 GNOME 这种桌面,4 GB 内存会明显紧张。

4.5 编译模块与 modules_install

模块编译阶段出错率其实比内核主体高,因为涉及大量第三方代码。常见错误集中在某些网卡、声卡、无线驱动的宏定义上。如果你定制过 menuconfig,优先怀疑自己新打开的选项,把它关回n或者m再试。如果是原生配置,基本一次能过。

make modules_install执行后,建议检查一下:

ls /lib/modules/

应该能看到新的内核版本目录。如果目录没生成,说明模块阶段出了问题,先回去解决再继续,不要强行进入安装步骤。

4.6 编译中途被杀掉的恢复

如果编译中途因为断电、OOM 或手动 Ctrl+C 中断了,不用慌,只要之前已经生成过大量 .o 文件,重新执行同样的 make 命令会从断点继续,它会根据时间戳自动跳过已完成的编译单元。但是如果中途改过.config,建议先make clean再重新编译,否则新旧配置混在一起,很容易出现"改了配置却没生效"或者链接错误。

判断是否需要 clean,最简单的标准:make clean会删除所有 .o 文件,代价是重新编译时间大大增加;所以只在你改过配置或源码时才用它,其他情况直接重跑 make 即可。

5. 安装内核、生成 initramfs 与引导配置

5.1 make install 在 CentOS 6 上会做什么

make install在老内核源码树里是个自动化脚本,正常会帮你完成:复制arch/x86/boot/bzImage到/boot/vmlinuz-*、复制System.map、生成 initramfs、修改 grub.conf。但 CentOS 6 上我实测过,它有时不会完整生成 initramfs,grub.conf 的 entry 也偶尔会写不全。所以我的做法是:不完全依赖它,手动把这些步骤都过一遍,确保可启动。

执行完make install后,到/boot看看:

ls -lh /boot

应该能看到vmlinuz-2.6.32-696.el6.x86_64-custom、System.map-*、initramfs-*这些文件。如果没有 initramfs,或者你对引导流程不放心,就手动生成。

5.2 手动生成 initramfs 与复制内核文件

CentOS 6 的 initramfs 生成工具是mkinitrd:

mkinitrd -v -f /boot/initramfs-2.6.32-696.el6.x86_64-custom.img 2.6.32-696.el6.x86_64-custom

第二个参数是内核版本号,就是/lib/modules/下新生成的目录名。这一步会把你根文件系统所需的磁盘控制器、文件系统驱动模块打进 initramfs,如果这一步失败,重启基本会卡在 kernel panic。

System.map也可以手动复制:

cp System.map /boot/System.map-2.6.32-696.el6.x86_64-custom

System.map 主要给调试用,没有它系统也能启动,但建议保留。

提示:mkinitrd 生成 initramfs 时,要求/lib/modules/<版本>目录完整。有一次我跳过了modules_install,直接跑 mkinitrd,结果提示找不到模块目录。所以这步之前,务必确认模块安装已完成。

5.3 修改 /boot/grub/grub.conf

CentOS 6 用的还是老式 grub 0.97,配置文件是/boot/grub/grub.conf。新内核入口通常会被自动追加到文件末尾。我不放心,一般会手动加上一个 entry,并且把 default 指向它:

default=0

然后把新内核段落放在文件前面:

title CentOS (2.6.32-696.el6.x86_64-custom) root (hd0,0) kernel /vmlinuz-2.6.32-696.el6.x86_64-custom ro root=/dev/mapper/vg_centos-lv_root rd_NO_LUKS rd_LVM_LV=vg_centos/lv_root quiet initrd /initramfs-2.6.32-696.el6.x86_64-custom.img

root (hd0,0)表示/boot所在分区,如果你的 /boot 是独立分区,这个值可能是(hd0,0)或者别的,参考原来的入口即可。root=/dev/mapper/...和后面rd_开头的参数,要跟你当前系统的原启动项保持一致,不能照抄网上别人的,否则根文件系统路径不对。

改完 grub.conf 后,用cat /boot/grub/grub.conf自己检查一遍是否有拼写错误。重点看vmlinuz和initramfs文件名到底存不存在,路径对不对。

5.4 首次重启与启动失败的最小救援流程

确认 grub.conf 没问题后,重启系统:

reboot

启动后第一时间验证:

uname -r

如果输出2.6.32-696.el6.x86_64-custom,说明内核已经跑起来了。接着检查模块加载是否正常:

lsmod | head

再检查根文件系统挂载:

df -h

如果启动时卡住,别慌,重启选择旧内核进入系统,然后重点排查三件事:第一,grub.conf 里的根分区路径是否正确;第二,initramfs 是否生成成功;第三,磁盘控制器驱动是否编进了内核或 initramfs。绝大多数启动失败都是这三块的问题。

6. 回滚、清理与老系统上玩内核的几条经验

6.1 永远保留一个可启动旧内核

这是我在老系统上折腾内核最大的心得。CentOS 6.9 的 grub 引导链相对脆弱,一旦新内核起不来,而你又把旧内核入口删了,就非常被动。所以我的底线原则是:在/boot/grub/grub.conf里永远保留至少一个系统原版内核入口,不去动它。默认启动项可以指向新内核,但回滚入口绝对不能丢。

6.2 内核模块、第三方驱动与 MODVERSIONS

如果你在 CentOS 6.9 上还装了 NVIDIA、VirtualBox 等第三方内核模块,必须清楚:换内核后,这些模块也要重新编译或重新安装。因为/lib/modules/<版本>/是全新的目录,旧模块不会自动迁移。

如果编译时开启了CONFIG_MODVERSIONS(默认通常是这样),模块加载时会校验版本符号。即使你从旧内核目录 cp 模块文件过来,也很可能因为符号 CRC 不一致而加载失败。所以正确做法是:走第三方驱动的官方安装脚本,让它们针对新内核重新编译。

6.3 与 yum 内核更新的关系

自定义编译的内核是独立于 yum 包管理的。之后如果执行yum update且有新内核包,yum 会安装一个新的官方内核,但不会覆盖你手动编译的内核。grub.conf 里会出现更多入口,默认启动项仍指向你设的那个。这个行为总体上是安全的,但要注意 /boot 空间:官方更新一次,手动编译一次,旧内核再不清理,200 MB 的 /boot 分区很快见底。

清理旧内核时,我习惯用 yum 的remove而不是手动删文件,因为 yum 会把 RPM 包装的内核和模块一起清理干净。手动编译的内核则需要自己删除 /boot 下对应文件、/lib/modules 下对应目录以及 grub.conf 里的对应 entry。

6.4 为什么我建议先跑虚拟机

在 CentOS 6.9 上编译原生内核的过程,表面上是练内核编译,实际上把磁盘空间、内存、工具链版本、grub 配置、模块体系全部检查了一遍。我自己编完之后,再回去看主线内核的配置和构建系统,很多概念都变得容易理解。如果你也对老系统、内核机制感兴趣,强烈建议先拿 CentOS 6 这种"老而稳"的发行版练手,比直接上最前沿的内核更容易建立信心。

不过我也要说句实在话:不要急着在物理机上操作。第一次编译时,用 VirtualBox 或 VMware 装一个最小化 CentOS 6.9,在虚拟机里把整套流程跑通,再回到物理机执行。虚拟机的快照功能可以让你随便折腾,拍个快照再编译,失败了一键还原。这套流程我在虚拟机里先走了两遍,到物理机时基本没遇到额外问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询