
操作系统课程第一个实验就是 Linux 内核编译及运行这个实验放在很多高校的第一周看起来就是“下载源码、make menuconfig、make、重启”实际操作起来却能把人折腾到怀疑人生。这篇文章把我自己做这个实验的全过程、踩过的坑、排查思路都记录下来希望能给准备做这个实验或者正在做的小伙伴一个完整的参考路径也算是对实验一的一次复盘。1. 实验目的与环境准备1.1 实验目的弄懂内核编译的全链路很多同学一开始会疑惑操作系统课为什么要从编译内核开始直接用发行版不香吗答案在于这个实验真正要训练的不是“会敲命令”而是理解操作系统的构建过程。所谓内核编译就是把一份 Linux 内核源码通过工具链生成可执行的二进制内核镜像和大量可加载模块。这个过程涉及源码树的组织方式、Kconfig 配置体系、Makefile 构建逻辑、模块机制以及 grub 引导流程。可以说一个实验把操作系统课程里“进程管理、内存管理、文件系统、设备驱动”这些大概念背后的工程实现串起来了。实验中你会看到内核不是一整块巨大的程序而是一个由上千个目录、几万个文件组成的工程集合。它的构建系统会按照 .config 文件里的配置项决定哪些功能编译进内核*、哪些编译成模块M、哪些干脆不编译 。这和你平时写一个小项目直接 make all 完全是两码事也正因如此它才能成为操作系统课第一个实验的理想起点。1.2 实验环境搭建我是用虚拟机完成实验的方案如下宿主机普通 Windows 笔记本开启硬件虚拟化虚拟机VMware Workstation分配 4 核 CPU、8 GB 内存虚拟磁盘80 GB动态分配系统镜像Ubuntu 22.04 LTS这里特别提醒一下磁盘问题。内核源码解压后大约 1.2 GB但编译过程中会产生大量的 .o 目标文件和临时文件整个 /usr/src/linux 目录编译完能膨胀到 13~15 GB。如果虚拟机初始只给了 20 GB 系统盘那基本必挂。建议至少给 60 GB我甚至见过有人因为磁盘不够把实验拖了一周才做完。进入系统后先把基础工具链装上sudo apt update sudo apt install build-essential libncurses-dev libssl-dev flex bison bc dwarves这些包各司其职build-essential包含 gcc、make、ld 等编译必备工具libncurses-dev提供 menuconfig 图形界面所依赖的 ncurses 库flex / bison内核源码构建时需要生成的词法、语法分析工具libssl-dev编译内核模块时需要的 OpenSSL 头文件bc内核配置阶段计算依赖时用到的计算器工具dwarves生成 BTF 信息部分新内核默认启用这个依赖还有一个比较容易忽略的点编译工具版本要和内核版本匹配。Ubuntu 22.04 自带的 gcc 是 11.x如果下载特别老的 Linux 内核比如 4.x 时代的老版本可能会遇到头文件不兼容、API 变更导致编译失败。所以我建议直接选 5.15.y 或者 6.1.y 这些 LTS 版本社区文档多遇到的问题网上也基本都有答案。提示做任何操作前先用uname -r看一下当前系统内核版本然后选择一个比它新的内核来编译避免出现版本倒退或者覆盖当前可用内核的风险。2. 内核源码获取与内核配置2.1 获取内核源码三种方式怎么选实验的第一步是拿到内核源码。我实践下来主要有三种方式各自的使用场景不一样。第一种从 kernel.org 下载稳定版 tar.xz 源码包。这是最推荐的实验方式wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.119.tar.xz tar -xf linux-6.1.119.tar.xz cd linux-6.1.119优点很直接一条命令拿到完整源码版本固定不会因为网络拉取中断导致源码树不完整。缺点是下载要花一点时间取决于网速。第二种用 git 克隆官方的 stable 仓库git clone --depth 1 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git加了--depth 1只拉取最新提交能省不少流量。但要注意浅克隆仓库里的版本号不像 tar 包那样固定在 6.1.119 这种 tag 上实验时如果想对齐某个版本还得手动 checkout 指定 tag稍微麻烦一点。如果你后续想做内核开发、改代码提交那 git 方式是必要的单纯做实验tar 包更省心。第三种用 apt 的源码管理机制从系统源里拉取需要提前添加 deb-src 源。这种方式受发行版维护周期影响拉到的可能不是最新版而且配置起来比较繁琐实验里我个人不推荐。我实验用的是 Linux 6.1.119这是 6.1 LTS 系列的一个版本。选它没有太多玄学就是看重长期维护这个特点出了问题也好查资料。2.2 配置内核menuconfig 的正确打开方式拿到源码后很多人第一反应就是直接 make。错大错特错。编译前必须先配置内核否则 make 会提示找不到 .config 文件或者使用一个默认的不适合你环境的配置。内核提供几档配置工具我分别说下体验make config命令行逐项问答式配置几百个选项全部答完大概需要一两个小时非常折磨人。make menuconfig基于 ncurses 的图形化菜单上下键选择、空格切换、回车进入子菜单也是实验中最推荐的一种。make defconfig直接采用内核默认配置省事但不对你当前系统做任何定制适合快速验证流程不适合做深入实验。make olddefconfig基于现有 .config 生成新配置通常在升级内核配置时用。实验环境里建议直接 menuconfig。但在此之前先执行一次彻底清理make mrpropermake mrproper比make clean更彻底它会清掉源码树中所有之前生成的配置、编译产物、依赖文件保证你从一个干净的状态开始。我第一次没执行这一步结果后面反复出现奇怪的头文件依赖错误后来养成习惯每次重新编译前先 mrproper。执行make menuconfig进去之后你会看到 General setup、Processor type and features、Power management、Device Drivers、File systems 等分类。重点理解这三个符号的含义*编入内核即直接整合到 vmlinuz 镜像里开机常驻内存M编译成模块以 .ko 文件形式存在需要时通过 modprobe/insmod 加载 不编译完全不包含该功能对于实验来说你可以在 Device Drivers 下面找到块设备、网络设备、显卡驱动等选项观察哪些是内建、哪些是模块。如果想明确看到效果可以特意把一个驱动从*改成M后面用 modprobe 手动加载会非常有成就感。配置完成后退出会询问是否保存新配置。选 Yes生成的配置文件叫 .config。可以查看验证grep CONFIG_BLK_DEV_SD .config这条命令能确认常见 SCSI 磁盘驱动是否被编译。2.3 配置阶段容易掉进的坑我在实验时遇到三个问题提前帮大家避开。第一个坑menuconfig 打不开提示缺少 ncurses。这纯粹是依赖安装不完整导致的执行sudo apt install libncurses-dev即可解决。第二个坑.config 文件被覆盖或丢失。这是新手的重灾区。如果你先用了 defconfig再打开 menuconfig 改了配置但最后退出时没保存那么你之前所有修改全部作废。我建议在改动前先留一份备份cp .config .config.bak第三个坑新内核的 Kconfig 体系对配置依赖非常敏感某些选项如果依赖其他被关闭的选项会直接置灰无法勾选。这时候不要硬选更不要手动往 .config 里强行加没被支持的 CONFIG_XX否则编译时大概率出现找不到头文件或 undefined reference 的报错。保险的做法是把当前系统内核的配置作为起点cp /boot/config-$(uname -r) .config然后基于这份配置增量修改这样能最大限度保留对现有硬件的支持避免把系统“配成一个启动不了的形态”。实验做完后我发现很多同学出问题都是因为从零开始配内核结果漏掉了自己虚拟机的磁盘控制器驱动、网卡驱动重启后直接失联。3. 内核编译、安装与引导3.1 编译过程与关键产物解读配置完成后进入最耗时的阶段编译。命令很简单make -j4这里的-j4表示 4 个并发编译任务。如果直接写-j$(nproc)系统自动用全部 CPU 线程数。但虚拟机上如果本身内存不大并发开太高容易把内存吃满出现 OOMOut of Memory。我的虚拟机分到 4 核实测-j4比较稳-j8虽然快一点但内存占用会显著增加还出现过一次编译崩溃。所以如果你不是专门为了拼速度老老实实用 4 并发就好。编译过程中你会看到大量 C 源文件被编译成 .o再被链接成各子系统的目标文件。重点关注这几个产物vmlinux未压缩的内核镜像带符号表体积很大通常位于源码根目录arch/x86/boot/bzImage压缩后的内核镜像是引导加载器真正加载的文件System.map内核符号表调试内核和模块时使用.ko模块文件分散在各级目录中后续会被安装到 /lib/modules当终端里出现这句话时说明内核本体编译成功Kernel: arch/x86/boot/bzImage is ready我第一次看到这行提示的时候还挺激动的以为实验马上结束了结果后面安装和引导还有一堆事。关于编译时长我的实测数据是4 核虚拟机、8 GB 内存、默认配置基础上增加少量选项编译完大约花了 35 分钟。如果配置里勾选了大量驱动、文件系统支持或者把并发调低耗时可能到 1 小时以上。建议实验安排时留足时间别等到实验课快下课才开始编译。3.2 模块安装与内核安装编译完成后先把模块安装到系统目录sudo make modules_install这一步会把所有 .ko 文件拷贝到/lib/modules/$(uname -r)/下。注意这里的$(uname -r)是你在内核源码顶层 Makefile 里设置的版本号不是当前运行系统的版本号。在执行前建议在源码顶层目录看一下版本变量make kernelrelease这个命令会打印出即将安装的内核版本确保和你预期一致。接下来安装内核镜像本身sudo make install这一步会执行几个动作把 bzImage 拷贝为 /boot/vmlinuz-版本号生成 /boot/initrd.img-版本号拷贝 System.map 到 /boot并自动调用 update-grub 更新引导菜单。如果你这个时候/boot分区空间不足会直接报错中断具体解决方式后面单独讲。安装完成后检查两个目录确认文件真的到位了ls -lh /boot/vmlinuz-* ls -lh /boot/initrd.img-*正常情况下你应该能看到新版本号对应的 vmlinuz 和 initrd.img。3.3 更新引导配置与重启验证虽然 make install 大概率已经自动执行了 update-grub但为保险起见我习惯手动再执行一次sudo update-grub这个命令会扫描 /boot 目录把所有可用内核写入 grub 菜单。执行后重启sudo reboot重启时grub 菜单会列出多个内核版本选择带新版本号的项进入。如果没看到选择菜单按住 Shift 或按 Esc 进入 grub 菜单具体按键取决于你的 grub 版本。进入系统后第一件事验证uname -r显示的结果如果是你编译的新内核版本说明你已经成功让系统运行在一个“自己编译出来的内核”上了。这一步是整个实验最核心的验证点。还可以更进一步dmesg | grep Linux version启动日志里会显示内核编译时间、使用的 gcc 版本、源码版本号这些信息能辅助确认你启动的确实是刚刚编译的内核而不仅仅是看起来版本号相同。3.4 实测数据参考我把个人实验的关键数据整理成一个表方便后面同学对照项目参考值虚拟机配置4 vCPU / 8 GB 内存 / 80 GB 动态磁盘内核版本Linux 6.1.119 LTS配置方式复制系统默认配置 menuconfig 增量修改编译命令make -j4源码解压后体积约 1.2 GB编译完成后源码目录体积约 13~15 GB完整编译耗时约 35 分钟生成的 bzImage 大小约 12~15 MB/boot 新增占用约 60 MB这些数值和具体配置选择、硬件性能有很大关系只能作为参考方向不必过分纠结具体数字。但磁盘空间和编译时间这两项确实是最容易影响实验进度的变量。4. 实验过程中遇到的坑与排查记录这部分是整份实验报告里我觉得价值最高的地方。内核编译的报错五花八门很多问题不亲手踩一遍光看文档真的想象不到。4.1 编译刚开始就报错bison/flex 缺失我自己第一次编译时没有装 flex 和 bison。执行 make 后不久终端直接报错/usr/bin/ld: scripts/mod/devicetable-offsets.s: No such file or directory yacc: command not found表面看是缺 yacc实际是 bison 没装。补齐依赖之后还要注意一个细节如果编译中途失败不要直接再跑一遍 make最好先make clean然后重新编译因为中途产生的某些生成文件可能不完整直接复跑会引发奇奇怪怪的链接错误。4.2 /boot 分区空间不足我用的虚拟机当时 /boot 分区只有 1 GB装过两次内核之后空间消耗殆尽。make install到一半直接报No space left on device df -h /boot排查思路主要有三条清理旧内核文件比如 /boot 下之前版本的 vmlinuz、initrd.img、System.map删除 /boot 下不再使用的 abi-、config-文件扩容 /boot 分区但在虚拟机里操作比较麻烦不建议实验中途做我最终选择了删除旧的 vmlinuz-5.15 和对应 initrd然后重新执行sudo make install sudo update-grub空间腾出来后安装顺利完成。这里给个建议平时养成保持两个可用内核的习惯别为了省空间把旧内核全删了新内核一旦启动失败你连退回的机会都没有。4.3 启动卡在 initrd进 BusyBox这是整个实验里最折磨人的一个问题。编译和安装都很顺利重启后却卡在 loading initial ramdisk或者直接掉进 initramfs 的 BusyBox 界面命令行前缀变成了(initramfs)。这个问题的本质是内核无法挂载根文件系统。大多数情况是因为根文件系统对应的驱动被配置成了模块M而不是内建*。系统启动早期内核还没有加载模块的能力如果磁盘控制器驱动、文件系统驱动不在内核镜像里自然就找不到根分区。排查手段是先在 initramfs 环境里看一下(initramfs) ls /lib/modules/$(uname -r)如果发现 ext4 或 xfs 这类文件系统驱动确实缺失回到 menuconfig把CONFIG_EXT4_FS这类关键选项从M改成*make menuconfig # 进入 File systems - * The Extended 4 (ext4) filesystem make -j4 sudo make modules_install sudo make install sudo update-grub重新编译并安装后重启问题通常能解决。这个坑特别容易出现在“从零配置内核”的同学身上所以我前面才反复强调最好基于系统自带 config 修改不要自己从头配。4.4 编译报错速查表我一共整理了八类常见的编译安装问题做成速查表报错现象可能原因解决措施fatal error: libelf.h: No such file缺少 libelf 开发库sudo apt install libelf-devyacc: command not found缺少 bison安装 bison 后make clean重编/bin/sh: 1: bison: not found缺少 bison安装 bison 并重新配置No rule to make target debian/certs/...源码目录被污染make mrproper后重新配置Unable to find the ncurses libraries缺少 libncurses-dev安装 ncurses 开发库后重开 menuconfiginsufficient memory并发过高或内存不足减少-j并发数或增加虚拟机内存No space left on device/boot 分区或根分区满清理旧内核文件或扩容gcc: error: elf.h: No such file缺少 libelf 头文件安装 libelf-dev 并重新编译这张表我实验时是贴在桌面上的每遇到一个问题就对应排查一遍效率高很多。4.5 实用的恢复技巧实验里的保命技巧就一条grub 菜单里保留一个旧内核作为保底。我图省事删过旧内核结果新内核启动失败那一刻真的欲哭无泪最后只能拿 live CD 进去修。如果你正在做实验无论如何都别急着删旧内核。等新内核连续重启两三次都稳定了确认模块、驱动、网络都正常再考虑清理工作。另外如果修改配置后想快速返回上一个可用状态可以在 grub 菜单里选 Advanced options for Ubuntu里面会列出所有已安装的内核版本选旧版本进入即可。5. 实验心得与进阶方向建议5.1 给后面做实验的同学几条掏心窝的建议首先时间规划上这个实验至少留两到三个晚上。第一晚理解源码结构和配置体系第二晚集中编译安装第三晚排查问题写报告。不要天真地以为“一节课就能搞定”实际编译失败、重来、再失败是常态。其次实验报告不要只截图要记录关键改动。我见过太多人实验做了报告里却只写“用 menuconfig 配置”“make 编译”几句流水账。答辩时被问“你改了什么配置”“编译参数用的什么”“遇到过什么错”直接哑口无言。我的做法是把 .config 里的关键修改项、make 命令的参数、每次报错的信息和解决过程都记录下来这些才是实验报告真正的价值。最后不要害怕报错。内核编译的报错信息其实是很好的学习材料它逼着你去查依赖关系、看源代码、理解构建系统。这一套流程走完你再回去看操作系统教材里的“系统启动”“模块加载”这些章节理解会完全不一样。5.2 实验之后还能怎么玩如果你对这个方向产生了兴趣后续有几个很好的进阶点第一个把某个驱动单独编译成模块然后手动 insmod/modprobe 加载和卸载观察 dmesg 里的驱动加载日志。这比单纯看教程理解“内核模块”更直观。第二个修改内核源码里的调度算法参数比如 CFS 调度器的某些默认值重新编译后跑一个 CPU 密集型程序对比修改前后的运行表现。这个实验能让你对“操作系统调度”从抽象概念变成可触摸的工程实践。第三个尝试用 menuconfig 把内核裁剪到最小做成一个精简内核。嵌入式开发里经常要这么干把不需要的驱动、虚拟化支持、蓝牙、Wi-Fi 全裁掉最终得到几 MB 大小的镜像。这个方向特别适合对嵌入式感兴趣的同学。第四个如果你愿意花点时间可以顺着源码里的arch/x86/kernel、kernel/sched这些目录去读一部分源码配合 System.map 和调试工具看看系统调用到底是怎么从用户态走进内核态的。这一步走通之后你对“操作系统”四个字的理解会远超课本上的定义。就我个人而言这个实验最大的收获是第一次意识到操作系统不是安装起来就完事的黑盒它是有血有肉、可以被亲手修改和重建的工程系统。编译内核这件事看起来只是课程的第一关但它建立的“源码—配置—构建—部署—验证”的完整心智模型后面读源码、写驱动、做系统优化都用得上。如果你正在为这个实验头疼别慌按我记录的这些步骤一步步来重点放在配置和排障上最后一定能看到自己编译的内核成功启动。那个瞬间你会觉得之前所有折腾都值了。