ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CentOS 6.9原生内核编译实操:从源码到grub引导

CentOS 6.9原生内核编译实操:从源码到grub引导 手里的机器还跑着 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 bcncurses-devel是给make menuconfig用的如果没有它menuconfig 会直接报错elfutils-libelf-devel提供编译过程中的 ELF 工具头文件openssl-devel是备用的某些内核模块或脚本会引用它的头文件bc用来处理内核构建脚本里的数学运算。这里有一个容易忽略的细节有些精简环境连/usr/include/linux头文件都不全可以顺手把kernel-devel也装上作为头文件兜底。不过注意kernel-devel不是完整源码它只是编译外部模块用的头文件目录和我们要的完整内核源码是两回事。2.2 准备 rpmbuild 目录并解包 src.rpmCentOS 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.specRHEL 系就是靠它来把源码构建成发行版内核 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 .config3.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 的处理头文件。遇到这种问题最直接的解法是换回系统自带 gccyum 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 或手动 CtrlC 中断了不用慌只要之前已经生成过大量 .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 上我实测过它有时不会完整生成 initramfsgrub.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 生成工具是mkinitrdmkinitrd -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-customSystem.map 主要给调试用没有它系统也能启动但建议保留。提示mkinitrd 生成 initramfs 时要求/lib/modules/版本目录完整。有一次我跳过了modules_install直接跑 mkinitrd结果提示找不到模块目录。所以这步之前务必确认模块安装已完成。5.3 修改 /boot/grub/grub.confCentOS 6 用的还是老式 grub 0.97配置文件是/boot/grub/grub.conf。新内核入口通常会被自动追加到文件末尾。我不放心一般会手动加上一个 entry并且把 default 指向它default0然后把新内核段落放在文件前面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_LVvg_centos/lv_root quiet initrd /initramfs-2.6.32-696.el6.x86_64-custom.imgroot (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在虚拟机里把整套流程跑通再回到物理机执行。虚拟机的快照功能可以让你随便折腾拍个快照再编译失败了一键还原。这套流程我在虚拟机里先走了两遍到物理机时基本没遇到额外问题。
返回列表