ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CentOS 6.9老系统自编译内核:为旧服务器添加新驱动实战

2026/9/30 11:45:21 拓冰建站 浏览量
CentOS 6.9老系统自编译内核:为旧服务器添加新驱动实战 前阵子处理过一台老掉牙的内网服务器CentOS 6.9内核还是 2.6.32-696跑着一个 2011 年就上线、谁都不敢动的业务系统。新买的阵列卡装上以后系统完全不认盘一查手头这个内核里根本没有新控制器的驱动厂商只给了驱动源码包要求你自己编内核模块。可 CentOS 6.9 连官方源都早就归档到 vault 了补丁和支持全部停更指望官方出新内核根本不现实。最后的出路就是自己动手拿 CentOS 官方的 SRPM 源码包在 PC 机x86_64 架构、MBR 引导上重新编译一份和当前运行内核同源的原生内核代码。这篇把我完整的操作过程、每一步背后的理由、还有踩过的坑都整理出来。适合谁看一是还在维护老系统、为驱动和内核特性发愁的运维同学二是想搞明白“发行版内核”到底怎么自编译、和 kernel.org 官方源码有什么区别的 Linux 爱好者。内容不涉及多高深的理论但每一步都能直接照着做。1. 编译前必须想明白的几个问题1.1 为什么非要用 CentOS 的 SRPM而不是 kernel.org 官方源码很多人第一反应是去 kernel.org 拉一个 2.6.32 的官方 tar 包来编这个思路其实不算错但编出来的东西大概率会给你添乱。CentOS/RHEL 所谓的“原生内核代码”指的是 Red Hat 在官方内核基础上打了大量补丁之后产出的完整源码树。这些补丁包括文件系统稳定性修复、众多厂商驱动的 backport、安全漏洞修复、以及 RHEL 自身的版本号体系。简单说你在 CentOS 6.9 上看到的 2.6.32-696.el6.x86_64 这个版本号并不对应 kernel.org 上某一个独立的官方 release而是 Red Hat 从上游挑选了 2.6.32 基线又不断把新代码向后移植进同一棵树的结果。所以最稳妥的做法就是找到和系统版本严格匹配的 kernel-2.6.32-696.el6.src.rpm从里面解出源码树。这个源码树已经包含了 CentOS 6.9 的全部补丁是一个“完整状态”不是需要你再手工去打几十个 patch 的半成品。这个区别非常关键我见过有人真去下了官方 2.6.32 内核再拿着 SRPM 里的 patch_xxx 系列补丁逐个打结果因为 patch 顺序不对、环境差异折腾一整天没编过。如果你只是需要一个和现有系统完全兼容、能承接近乎所有旧配置和驱动的内核直接用 SRPM 解出来的源码树是最省心的路径。它本身就是“CentOS 原生产物”。1.2 “PC 机原生内核”在这个场景里指什么标题里的“PC 机”在我们实际场景里就是最常见的 x86_64 平台台式机、PC 服务器、虚拟机宿主机这一类。它们用 Intel/AMD CPU通常是 BIOS/MBR 引导2011 年的机器基本没有 UEFI主板南桥芯片组是 PCH 那一代磁盘控制器主要是 AHCI 或者是老的 ata_piix。这意味着在编译配置时除了常规设置你尤其要关注这几项存储控制器驱动ata_piix、ahci、megaraid_sas、mpt2sas、virtio_blk网卡驱动e1000、e1000e、r8169、igb、virtio_net文件系统用于根分区的 ext4、xfs以及老环境常遇到的 ext3把这些驱动要么直接编进内核y要么编成模块并确认能打进 initramfs否则重启后大概率输在最后一步起不来系统。这不叫夸张老内核编译翻车案例里至少三成死在这。1.3 这次编译的目标是什么动手之前先把目标定清楚免得编到一半不知所措。我的目标很简单保留 CentOS 6.9 原生内核的绝大多数功能和驱动兼容性额外加入新阵列控制器的驱动或者打开原来没有的功能开关通过自定义 localversion 区分自编内核和原版内核不覆盖原内核随时可回滚目标定了之后所有操作都围绕这三条展开。尤其最后一条几乎是保命项。凡是自编内核强烈建议不要覆盖 /boot 里原有 vmlinuz、initramfs 和 System.map保留一个能启动的原内核入口永远是第一原则。2. 准备阶段环境检查、工具链与源码获取2.1 先确认你的底子是什么样的编译内核之前先把机器状态摸清楚。我建议至少检查这几项cat /etc/redhat-release uname -r uname -m grep -c processor /proc/cpuinfo free -h df -h /usr/src /boot gcc --version其中我特别看重的两个点CPU 核数决定了后面 make -j 用几路并行老机器常有 4 核 8 线程或者双路 6 核自己心里要有数/usr/src 和 /boot 的剩余空间编译一个 2.6.32 内核源码树展开大概占用 1GB 左右编译过程还需要临时空间建议 /usr/src 剩余至少 5GB/boot 剩余至少 200MBgcc 版本同样关键。CentOS 6.9 默认的 gcc 是 4.4.7这个版本编译 2.6.32 源码正好属于“老配老”的最佳组合。如果系统里装了 devtoolset 的高版本 gcc比如 7、8反而容易出兼容性问题我也不建议用高版本编译器去编老内核得不偿失。2.2 安装编译工具链和依赖确认好底子以后先把编译需要的工具装齐。推荐直接装开发工具组再补几个包yum groupinstall Development Tools -y yum install -y ncurses-devel bc rpm-build这里逐一解释下为什么需要这些Development Tools包含 make、gcc、binutils、flex、bison 等一套编译基础工具没有这个组后面每步都会缺东西ncurses-develmake menuconfig 的图形配置界面依赖这个库bc内核编译过程中很多脚本会用到 bc 做数值计算尤其是老内核没装会报奇怪的错误rpm-build不是编译内核本身必须的但我们要用 rpm2cpio 解包所以提前装好注意一点这台 CentOS 6.9 的 yum 源已经不能用默认的 mirror.centos.org 了需要提前配置 vault 源或者内网镜像源。这不是本文重点但没搞定源的话上面这些依赖全部装不上。装依赖前先yum makecache跑一下确认源可用再继续。2.3 获取与解压 SRPM 源码包CentOS 6.9 的系统对应的内核 SRPM 全名类似kernel-2.6.32-696.el6.src.rpm这个包可以从 vault.centos.org 的 os/Source 目录下找到也可以在内网镜像里搜。拿到的 src.rpm 不要用 rpm -ivh 直接装虽然也能装会在 /root/rpmbuild 下生成 SOURCES 等目录但有时候权限和路径会绕一点我习惯直接解包mkdir -p /root/kernel-src cd /root/kernel-src rpm2cpio kernel-2.6.32-696.el6.src.rpm | cpio -idmv解完以后能看到一堆文件这里面我们只关心 linux-2.6.32-696.el6.tar.bz2。这就是完整打过补丁的源码树压缩包。文件列表里的那些 patch_xxx、config-xxx 之类的文件暂时都不需要管。然后解压源码树到规范目录mkdir -p /usr/src/kernels tar xjf linux-2.6.32-696.el6.tar.bz2 -C /usr/src/kernels cd /usr/src/kernels/linux-2.6.32-696.el6之所以放到 /usr/src/kernels 下面是因为后面编译外部模块比如厂商驱动时/lib/modules/$(uname -r)/build 会指向这个路径放这里能少很多不必要的麻烦。这里有个经验我多说一句有人会偷懒不看文件直接rpm -ivh kernel-2.6.32-696.el6.src.rpm然后想去 rpmbuild 目录里找源码树结果发现只有 tar 包和补丁文件没有现成的展开目录。这是因为展开源码树的动作是 rpmbuild -bp 阶段才做的。如果你只是想编译一次测试手工展开最快根本不用走 rpmbuild 全流程。2.4 确保源码树是干净的解压完先看一眼目录ls -l /usr/src/kernels/linux-2.6.32-696.el6/ head -n 20 Makefile头部能看到版本定义比如VERSION 2 PATCHLEVEL 6 SUBLEVEL 32 EXTRAVERSION -696.el6看到这个就说明源码树对版了。这个 EXTRAVERSION 是 Red Hat 加的最终的内核版本字符串会由它拼出来。后续如果还想加自己的标识可以在 .config 里通过 CONFIG_LOCALVERSION 再扩展。一定要确认源码树是刚解压的、未编译过的干净状态。老内核编译时如果之前编过一半又切换过配置很容易残留 .o 文件和生成的头文件导致各种诡异报错。发现问题不要纠结直接重新解压一份比人肉清理靠谱得多。3. 内核配置站在当前内核的肩膀上3.1 直接把当前运行内核的配置拷贝过来编译内核最怕什么最怕从零开始刮一个 .config几百个选项点到手抽筋还容易漏掉关键驱动。我从来没这么干过。正确做法是拿当前系统运行内核的配置做基础在它上面做小范围修改。cp /boot/config-2.6.32-696.el6.x86_64 .config这个 config 文件是内核编译时装进去的CentOS 的 kernel rpm 会把它放在 /boot 下文件名就是 config-内核版本。如果哪台机器精简过、这个文件不存在也可以从 SRPM 里的 config-x86_64 文件恢复但直接拷 /boot 的通常最贴近当前系统的真实状态。拿到基础配置后接下来执行make oldconfig这个过程会把当前配置和新源码树里的选项做一次“对账”。2.6.32-696.el6 源码树和系统现有配置来自同一套体系理论上一路回车就能过但你还是会看到一些新问出来的选项或者被设为 N 的新特性。如果拿不准选默认即可不要乱开新功能。把 oldconfig 跑完之后x86_64 的平台配置、文件系统、绝大多数驱动就保持和当前内核一致了。这等于站在巨人肩膀上后面只需要做增量修改。3.2 修改内核版本标识避免覆盖原内核这一步不能省是防混淆的关键操作。运行 menuconfig 修改 CONFIG_LOCALVERSIONmake menuconfig路径一般在General setup - Local version - append to kernel release把默认的空值改成.custom或者.local。比如原来内核版本是 2.6.32-696.el6.x86_64加了之后就是2.6.32-696.el6.custom.x86_64这个后缀的好处编译安装后 /boot 下生成 vmlinuz-2.6.32-696.el6.custom.x86_64/lib/modules 下也是 2.6.32-696.el6.custom.x86_64和原内核完全隔离grub 里也能一眼认出哪个是自己编的。注意 aCONFIG_LOCALVERSION 开头的点号是有讲究的版本字符串会被拼接成“2.6.32-696.el6.custom”这个点号让你在 uname -r 里看到的结果清晰可读。如果忘了设编出来的版本和原系统完全相同安装时就会覆盖原内核文件回滚会很麻烦。3.3 按需调整驱动配置接下来就是这次编译的“定制”环节。以我碰到的新阵列控制器为例驱动厂商给的源码是针对内核模块的不用编进内核只需要确认内核源码树里包括足够的模块基础设施CONFIG_MODULESy然后外部单独编。但如果是要把某些驱动编进内核里menuconfig 里找到对应位置来修改。比较常见的调整路径Device Drivers - Network device support - Ethernet driver support Device Drivers - SCSI device support - SCSI low-level drivers Device Drivers - Serial ATA and Parallel ATA drivers实际操作中怎样判断该勾哪个先看硬件设备 ID。lspci -nn查一下你的磁盘控制器和网卡的 PCI ID比如lspci | grep -i ethernet lspci | grep -i raid然后在 menuconfig 里搜索对应的驱动名即可。这一点对 PC 机来说特别实用因为老 PC 最容易出现的情况就是新加了一张网卡或者 RAID 卡原内核没有对应驱动。改完保存退出menuconfig 会写回 .config。这时候一定要再确认一下grep CONFIG_MODULESy .config grep CONFIG_IKCONFIGy .configCONFIG_MODULES 必须打开否则整个 modules 阶段全白跑CONFIG_IKCONFIG 建议打开它会把配置信息编进内核方便日后排查模块和内核的匹配关系。3.4 配置阶段的三个常见误区第一个误区是贪新。老内核的源码树里确实有一些“新”驱动可以打开但你要清楚CentOS 6.9 的源码是定格在 2017 年的状态即便打开所有开关也支持不了太多新硬件。不要指望 2.6.32 老树开几个选项就能兼容 2024 年的新设备不可能的。第二个误区是乱关功能。硬盘空间紧张或者追求启动速度有人会把一堆驱动从模块改成不编译。这在 PC 机上风险很高因为你很难在一台已经跑着的系统上穷举出所有用到的驱动。尤其 SATA、USB、文件系统这类基础支撑保留原样最安全。第三个误区是忽略配置依赖。比如你想把某个驱动编成 y内置但它的依赖项是 m模块编译时内核会强制把它降级为 m或者干脆报错。遇到这种情况不要硬扛先看依赖关系。menuconfig 里遇到某个选项选不了直接按 ? 看它依赖哪些配置逐一把依赖开齐就好了。4. 编译与安装的完整实操流程4.1 正式编译前的一次清理第一次在一个干净源码树里编译理论上不需要清理。但如果你在配置阶段反复改动过保险起见先跑一次make clean注意不要用 make mrproper那个会把 .config 也删掉你想死的心都有了。make clean 只删掉编译过程生成的目标文件和大部分临时文件保留 .config够用了。然后重新确认 .config 存在并且有内容wc -l .config一般 2.6.32 的 .config 大约 3000 多行如果只有几行说明配置没保存成功打回去重来。4.2 编译内核与模块后台泡杯茶的功夫在 x86_64 平台编译分成两个主要步骤。先编内核本体make -j8 bzImage这里的 -j8 要和 CPU 核数匹配。如果你的机器是 4 核 8 线程用 -j8双路 6 核用 -j12。不要盲目开很大的并行数老机器内存不够时并行编译反而容易 OOM。内存 2GB 以下的机器建议 -j4 左右就好。bzImage 编完以后就是模块编译make -j8 modules这一步时间比内核本体长得多尤其是在驱动选项全开的情况下。以我那次为例2.6.32-696 的源码树8 线程并行大概跑了 30 到 40 分钟。这个速度受磁盘性能影响很大如果用机械硬盘耐心点。编译过程中如果发现屏幕最后阶段出现Kernel: arch/x86/boot/bzImage is ready说明内核本体编好了。模块阶段结束时屏幕上会显示类似“Building modules, stage 2”之类的提示没有 fatal error 就表示通过。4.3 安装模块与内核镜像make install 的真相两个安装动作顺序不能反make modules_install这一步会把所有 .ko 模块按目录结构复制到 /lib/modules/2.6.32-696.el6.custom.x86_64/ 下面。注意它只装模块不装内核镜像。然后执行make installmake install 会做几件事把 arch/x86/boot/bzImage 复制成 /boot/vmlinuz-2.6.32-696.el6.custom.x86_64把 System.map 复制成 /boot/System.map-2.6.32-696.el6.custom.x86_64还会尝试调用新内核的安装脚本更新 grub.conf。但这里有一个老系统上常见的坑make install 生成的 initramfs 不一定会成功或者说不一定包含你需要的驱动。所以我不推荐 100% 依赖 make install 的收尾最好手动再来一步。手工生成 initramfs 的命令如下按你的内核版本来dracut -f /boot/initramfs-2.6.32-696.el6.custom.x86_64.img 2.6.32-696.el6.custom.x86_64这一步为什么要手动跑因为 initramfs 是系统启动早期加载的内存文件系统里面必须包含根分区所在磁盘的驱动、文件系统支持、LVM 逻辑卷支持。make install 自动生成的 initramfs 有时会沿着旧内核的方式生成导致你新加的驱动没有打进去。手动指定版本号生成最可控。4.4 grub.conf 配置老系统没有 grub2只有 menu.lstCentOS 6 用的是 grub 1配置文件在 /boot/grub/grub.conf同时 /boot/grub/menu.lst 是指向它的软链。先看当前内容cat /boot/grub/grub.conf正常情况下你会看到类似结构default0 timeout5 title CentOS (2.6.32-696.el6.x86_64) root (hd0,0) kernel /vmlinuz-2.6.32-696.el6.x86_64 ro root/dev/mapper/vg_root-lv_root rd_NO_LUKS rd_NO_MD rd_LVM_LVvg_root/lv_root crashkernelauto ... initrd /initramfs-2.6.32-696.el6.x86_64.img这时需要在文件顶部或者合适位置加一个新的 title 块。我的做法是把原来的 title 块整体复制一遍改成新内核的名字并把 vmlinuz 和 initramfs 的文件名、root 参数原样保留default0 timeout5 title CentOS (2.6.32-696.el6.custom.x86_64) root (hd0,0) kernel /vmlinuz-2.6.32-696.el6.custom.x86_64 ro root/dev/mapper/vg_root-lv_root rd_NO_LUKS rd_NO_MD rd_LVM_LVvg_root/lv_root crashkernelauto ... initrd /initramfs-2.6.32-696.el6.custom.x86_64.img title CentOS (2.6.32-696.el6.x86_64) ...这里有一个非常关键的提示root 参数以及 rd_LVM_LV 这类参数绝对不能从零开始猜必须照着旧内核那一行原样抄。RHEL/CentOS 6 默认安装的根文件系统大多在 LVM 逻辑卷上如果 root 写成了 /dev/sdaX 这种设备节点一旦系统里的磁盘顺序有变化直接起不来。复制旧参数是保证成功率最高的方式。然后确认 default0也就是默认引导第一个 title。我把新内核放在第一个default0 就直接进新内核。如果你想先保守一点把 default 设成旧内核所在序号重启后手动选也行。我个人建议是第一次切换default 先指向旧内核或者干脆在 interactive 状态手动进新内核跑通了再改默认。一切配置好后执行sync reboot重启后如果顺利看到 grub 菜单选新内核进入系统后执行uname -r看到输出2.6.32-696.el6.custom.x86_64说明你已经成功让 CentOS 6.9 跑在自己编译的原生内核上了。4.5 如果厂商驱动是外部模块怎么编前面提到如果新硬件是 SCSI/RAID 控制器通常厂商会提供驱动源码包需要你在新内核上编译模块。编译外部模块的命令通常是这样make -C /lib/modules/2.6.32-696.el6.custom.x86_64/build M$(pwd) modules这里的 /lib/modules/xxx/build 是一个软链指向 /usr/src/kernels/linux-2.6.32-696.el6。前提是你源码树没删、配置没动否则外部模块编出来的 .ko 会因为 vermagic 不匹配而无法 insmod。编出来的 .ko 复制到 /lib/modules/2.6.32-696.el6.custom.x86_64/extra/ 下面然后depmod -a刷新模块依赖关系再 modprobe 测试。外部模块编不过、或者 insmod 报 version magic 不匹配九成原因就是内核源码树和当前运行内核不是同一个版本、同一份配置。这个点值得单独记住外部模块必须对应运行中的内核源码树编译不是随便拿个内核源码就能编的。我在这个环境里编过一次厂商提供的 megaraid 驱动就因为 build 软链指向旧内核源码试了三次才找准问题。5. 老系统编译内核的典型问题速查与化解方法5.1 我实际遇到过的问题汇总温习一下我这些年折腾老内核编译翻过车的几个场景做成表格就当速查卡了现象根因处理方法make 报 No rule to make target 或明显缺文件源码树不完整或二次编译残留重新解压 tar 包保证干净源码树编完启动报 “VFS: Unable to mount root fs”initramfs 缺磁盘/文件系统驱动手动跑 dracut确认 ahci、ata_piix、ext4、xfs 模块已打进 initramfsgrub 启动报 Error 15 File not foundgrub.conf 里的内核/initramfs 文件名写错或 /boot 空间不足对照 /boot 下实际文件名修改并检查 df -h /bootmodprobe 外部模块报 version magic mismatch.ko 与运行内核版本不匹配用 /lib/modules/$(uname -r)/build 这个软链重新编译外部模块编译中 OOM卡死或 gcc 进程被 kill并行任务太多内存不足降低 -j 参数比如 -j2 或 -j4关掉多余服务释放内存重启后新内核看不到网卡/磁盘驱动没开或没编进内核/initramfslspci 确认硬件 ID回 menuconfig 打开对应驱动重新编译装手工 make install 没有生成 initramfs老内核安装脚本行为不稳定不依赖它统一用 dracut 手动生成这套表里的问题几乎覆盖了老内核编译九成以上的翻车现场。5.2 编译失败后如何快速定位编译失败的时候不要盯着满屏滚动的输出愣神先抓最后一个 error 找关键字。最常见的一类是error: xxx undeclared (first use in this function)这类多半是 gcc 版本或者源码补丁不匹配的问题。2.6.32 老内核在 gcc 4.4.7 下编译如果没有动过补丁一般不会出现这种低级错误。真要出现了第一反应是源码树被污染了重新解压。另一种常见的是make[1]: *** No rule to make target arch/x86/...这种八成是缺平台配置或者是 config 没配对。比如在 x86_64 机器上却用了 i386 的 config就会缺一堆架构相关文件。确认 uname -m 是 x86_64然后重新拷贝对应架构的 config 再编。5.3 启动失败后怎么回滚启动新内核失败不可怕可怕的是没有回滚方案。只要你在 grub.conf 里保留了旧内核的 title重启时在 grub 菜单选择旧内核就能回到原来的系统。如果默认进的是新内核并且起不来但 grub 菜单在超时后只能倒计时愣进可以在启动倒计时时按任意键停在菜单手动选择旧内核进入再把 default 改回去。最极端情况是 grub.conf 写坏了连菜单都不出来。用系统安装光盘的 rescue 模式进到已安装系统手工编辑 /boot/grub/grub.conf 把 default 改回旧内核同时确认文件里的 title、root 参数没有被人为改坏。所以每次改 grub.conf 之前备份一下是绝对必要的cp /boot/grub/grub.conf /boot/grub/grub.conf.bak.$(date %F)这个备份习惯哪怕是最熟练的老运维也不要省因为 grub.conf 改坏之后一台无法引导的机器的救援成本远高于一条备份命令的成本。5.4 一个容易被忽略的重点/boot 空间PC 机 /boot 分区通常很小CentOS 默认安装可能只分 200MB 到 500MB。每多一个内核需要 vmlinuz约 4MB、initramfs约 15MB 到 30MB、System.map约 1MB 到 2MB。看起来不多但如果 /boot 本身只有 200MB 且已经用掉一大半再装一个内核就容易塞满。塞满之后最直接的后果是 initramfs 写入失败或者 grub.conf 写入失败然后启动不了。编译完成后检查一下df -h /boot ls -lh /boot/如果空间紧张编译前就先做好清理把旧内核文件删除或者挪走。删除时不要直接删 /boot 下的文件就完了最好通过 rpm 卸载对应 kernel 包免得 rpm 数据库里的文件记录对不上以后升级管理全乱套。6. 收尾几点实际操作下来的经验和体会这次从解包 SRPM 到新内核跑稳整个过程其实比想象中要顺但顺的前提是方向对。要说经验我最想强调的还是那句老话别贪新别莽给自己留退路。第一点内核源码树一定要干净。不要试图在一个编译过一半的目录里反复折腾老内核不像新内核那样容错高。换个源码树重新解压的成本永远比排查一个莫名其妙的编译错误低。你可能觉得重新解压浪费时间但被诡异报错卡三个小时的滋味更难受。第二点所有配置都以当前系统为准。拷贝当前运行内核的 /boot/config 做底这是保证“原生”兼容性的根本。如果你真想改某些驱动也用增量方式改而不是重新选一套全新配置。配置改动越少出新问题的概率就越低。第三点新内核首次启动之前务必备份原内核的 grub 入口。前面章节反复强调过这个动作不是啰嗦就是见过太多人在最后一步翻车。grub.conf 一般是纯文本但里面每一行都牵一发动全身尤其 root 和 LVM 参数。第四点手工 dracut 生成 initramfs 这个动作建议每次都做不要偷懒。新内核能不能起来很多时候不是内核本身的问题而是 initramfs 里的驱动不完整。dracut 参数里如果知道需要额外打进去驱动也可以加 --add-drivers 指定比如加 ahci、virtio_blk这样更稳妥dracut --add-drivers ahci virtio_blk megaraid_sas -f /boot/initramfs-2.6.32-696.el6.custom.x86_64.img 2.6.32-696.el6.custom.x86_64这台 CentOS 6.9 的内网服务器后来用自编内核跑了大半年新阵列控制器认盘稳定业务没有受任何影响。回过头看给老系统编译原生内核代码这件事其实难度不在编译本身而在于你选对了源码、保住了兼容性、把退路留够。把这三点想明白剩下的就是泡一杯茶等 make 跑完而已。