
操作系统实验报告里Linux 内核编译及运行这个实验几乎每个高校的操作系统课程都会安排。我第一次拿到实验一的任务书时第一反应是系统已经装好了为什么还要自己动手编译一遍内核直到真正走完从源码下载、内核配置、编译安装、更新引导到重启验证的完整链路之后才理解这个实验真正的意图——它逼着我们把“操作系统”从教科书上的抽象概念变成一台机器上实际跑起来、能看到、能验证的东西。这篇内容是我做实验一Linux 内核编译及运行的完整实操记录覆盖环境准备、内核版本选择、配置思路、编译全流程、常见报错排查和最终验证。适合正在做这门实验的在校同学参考也适合想在自己的机器上第一次手动编译内核的开发者。整个流程我在虚拟机里完整跑过三遍每一步都验证过包括那些实验指导书上不会写的坑都会一并整理出来。1. 实验目标拆解为什么要自己编译一次内核1.1 这个实验真正要验证的是什么先说结论实验一考的不是“你会不会敲 make 命令”而是你对操作系统内核、引导过程、模块机制这三件事有没有建立起正确的认识。平时我们用的发行版比如 Ubuntu、CentOS都是厂商已经帮你编译好的通用内核。你装完系统直接就能开机内核对你来说就是一个“看不见的底层东西”。但编译内核这件事强迫你面对一个现实内核不是魔法它也是一堆 C 语言源码通过配置、编译、链接之后才变成一个引导加载程序能识别的二进制文件。整个实验过程其实是在回答三个问题内核源码是怎么组织的你会看到arch/处理器架构相关、kernel/核心调度与进程管理、mm/内存管理、fs/文件系统、drivers/设备驱动这些目录。每个目录的职责跟操作系统课程里讲的模块一一对应。内核的功能是怎么被选出来的make menuconfig打开的是一个基于 Kconfig 的配置系统每一项都对应一个宏定义。你选择“支持某文件系统”或“不支持某个驱动”本质是在决定编译出来的内核包含哪些能力。内核和引导程序是怎么配合的编译完的内核放到/boot模块放到/lib/modules引导加载程序 GRUB 根据配置加载内核和 initramfs然后才把控制权交给内核。所以这个实验与其说是在“编译内核”不如说是在“拆开操作系统看内部结构”。这也是为什么很多学校把内核编译列为操作系统实验的第一个项目——它不涉及深刻的内核算法但能让你把整门课的坐标系建立起来。1.2 实验考核通常看哪几个点根据我做实验后的复盘实验一的评分或验收一般围绕这几项能否成功编译出一个新版本内核并通过uname -a或uname -r看到版本号变化能否说明自己的内核配置选择了哪些功能模块为什么这样选能否正确加载、卸载内核模块部分学校会安排一个简单的模块编写题作为附加现场提问时能否解释“y / m / n”三种配置状态的区别、make modules_install和make install各自做了什么。说白了实验结果能开机进新内核是基础分对过程的理解才是拿高分的关键。所以下面我会把每一步“为什么要这么做”讲清楚而不只是给你一串命令。2. 实验环境准备与版本选择2.1 虚拟机方案是首选项别冒险用物理机编译内核本身不会破坏系统但安装新内核、更新引导这一环如果配置出错可能导致开机失败。你要是用物理机做万一 GRUB 引导出问题至少也得靠 live CD 或者 recovery 模式才能救回来。对做实验的同学来说这个风险完全不值得冒。我自己的做法是用虚拟机装的是 Ubuntu Server 或带桌面版的 Ubuntu分配 4 核 CPU、8GB 内存、80GB 动态磁盘。这个配置编译 5.x 内核大概在 10 到 20 分钟之间完全够用。VMware 和 VirtualBox 都可以关键是一开始就做一次快照提示在编译内核之前先对虚拟机做一个干净系统的快照。后面不管内核怎么折腾只要恢复快照就能回到初始状态这是整个实验里性价比最高的一步。选择 Ubuntu 还有一个原因它的内核源码包和编译工具链在软件源里都很齐全下载安装方便遇到依赖问题用apt就能解决。对比起来如果你对包管理不熟某些 Red Hat 系的发行版在安装编译依赖时会稍微绕一点不是不行只是对新手没那么友好。2.2 内核版本怎么选不是越新越好实验要求一般是“自己选一个内核版本来编译”。很多同学第一反应是直接上 kernel.org 最新版但我不建议这样。原因有两个第一最新版内核可能跟你当前系统的工具链版本不完全匹配编译时容易出现一些奇奇怪怪的语法或配置兼容问题第二实验的重点是理解流程不是追新版本。选一个与当前系统内核同大版本、稍微新一点的稳定版是最稳的路线。具体操作先看看当前系统内核版本。uname -r假设输出是5.15.0-xx-generic那我推荐去 kernel.org 下载linux-5.15.y的版本y 表示比当前版本更新的小版本号或者选择 5.15 LTS 系列的最新补丁版本。这样做的好处是内核配置项与你系统当前处理器的支持情况基本一致硬件驱动也能直接复用大大降低编译后开不了机的概率。下载地址是https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.y.tar.xz选择tar.xz格式的源码包它体积比tar.gz小解压速度也不慢。下载完后记得做校验防止下载不完整导致的编译问题sha256sum linux-5.15.y.tar.xz去 kernel.org 对应页面把官方发布的 SHA256 校验值拿过来对比一致再继续。这个习惯在后续自己下载源码时也用得上。2.3 编译依赖一次装齐避免反复踩坑编译内核需要一组工具链和开发库。Ubuntu 下我建议先把这些一次性装好sudo apt update sudo apt install build-essential libncurses-dev flex bison libssl-dev bc逐一说一下这些包的作用因为实验报告里很可能要写build-essential包含 gcc、g、make 等基础编译工具。内核是用 C 写的编译器是刚需。libncurses-dev提供make menuconfig的文本交互界面库。少装这个一执行make menuconfig就会报ncurses相关错误。flex和bison词法分析和语法分析工具。内核构建过程中有一部分代码是自动生成的比如解析协议报文、配置语法的代码需要这两个工具参与。libssl-devOpenSSL 的开发头文件。用于内核模块签名相关的配置。如果内核配置打开了模块签名编译时找不到 openssl 头文件会直接中断。bc一个计算器程序。内核构建脚本里有用到它做数值判断不装会在编译中段报/bin/sh: bc: not found。如果你用的是 CentOS、Fedora 这类系统对应的包名稍有不同但逻辑一样sudo yum groupinstall Development Tools sudo yum install ncurses-devel flex bison openssl-devel bc依赖装完以后可以用一个简单命令验证 gcc 和 make 是否存在gcc --version make --version确认版本信息能正常输出环境就算准备好了。3. 核心流程内核编译的完整链路3.1 源码解压与目录预检源码包下载好后放到家目录或者/usr/src下都可以。我习惯放在/usr/src因为这是存放内核源码的传统位置但需要管理员权限放家目录则更自由不会因为权限问题导致编译过程别扭。无论放在哪解压后先进目录看一眼结构tar -xJf linux-5.15.y.tar.xz cd linux-5.15.y/ ls你会看到arch、block、crypto、drivers、fs、include、init、kernel、mm、net、sound等目录。建议花十分钟随便逛几个目录比如kernel/sched/下能看到调度器的核心代码文件fs/下能看到 ext4、btrfs 等文件系统实现。做实验的时候顺手打开几个文件看一眼头文件注释写报告时你会有细节可写老师也会觉得你确实动手看了源码而不是只复制了命令。3.2 内核配置y、m、n 三种状态的含义编译内核前必须生成一个叫.config的配置文件。这个文件决定了“哪些功能编进内核、哪些编成模块、哪些不编”。这里有一个新手最容易忽略的关键点配置状态不是简单的“有”或“没有”而是三种y功能直接编进内核主镜像也就是 vmlinuz 里。开机后一直常驻内存不需要额外加载。m功能编成一个独立的.ko模块文件安装到/lib/modules/版本/下用modprobe或insmod在需要时加载。n完全不编译这个功能。为什么需要区分 y 和 m因为内核镜像体积和内存占用是有限的把所有功能都编进内核会让镜像变得巨大而且很多驱动比如某个冷门网卡驱动你根本用不上没必要常驻内存。改成模块后系统启动时按需加载更灵活。课程里讲到“内核模块机制”时这个实验就是最好的例子。生成配置文件有三种常用途径第一make defconfig。用内核自带的默认配置生成.config适合快速验证编译链路通不通但不一定贴合你的实际硬件。第二make menuconfig。文本图形界面一步步选。学习阶段强烈推荐因为你能直观地看到每个功能项的说明能搜索、能修改保存。第三基于当前系统配置做增量。如果你的发行版内核还在可以直接复制现有配置作为起点cp /boot/config-$(uname -r) .config make olddefconfigolddefconfig会根据新内核源码的配置项自动把旧的配置项迁移过来新增的配置项用默认值。这是最稳妥的方式推荐的组合是先复制发行版配置再打开 menuconfig 做少量定制比如修改内核版本字符串、加一个小功能、或者关掉一个不必要的子系统。进入 menuconfig 的基本操作要知道方向键移动光标回车进入子菜单空格切 y / m / n 三种状态*表示 yM表示 m空表示 n/键按名称搜索配置项比如搜CONFIG_EXT4退出到顶层后选Save保存.config再选Exit退出。配置这一环我给的实操建议是不要为了“显得厉害”去手动改一堆配置。第一次做实验你只需要让配置能用、能编译过、能开机即可。改配置引起的启动失败是后面最浪费时间的问题。3.3 编译执行make 命令背后的分工配置保存后就可以开始编译了。我的命令顺序是make -j$(nproc)$(nproc)会自动读取 CPU 逻辑核心数比如 4 核就是make -j48 核就是make -j8。这个参数表示并行编译的任务数能大幅缩短编译时间。但注意并行度不是越高越好。如果你内存只有 4GB-j8可能会导致编译器把内存吃满触发 OOM 甚至卡死。保守一点的做法是make -j4或者make -j$(($(nproc) - 2))。第一次编译的时间取决于机器性能。在我那台 4 核 8GB 的虚拟机上编译 5.15 内核大约 12 分钟。期间屏幕上会滚动大量编译信息看到CC编译 C 文件、LD链接、OBJCOPY等关键字都是正常的。只要最后没有以Error结尾就算通过。这里要区分几个容易混的命令make编译内核主镜像和所有模块生成vmlinuz、System.map、modules等文件但此时结果还在源码目录里。make modules_install把编译好的模块安装到/lib/modules/5.15.y/目录。如果不执行这一步内核能启动但很多以模块形式存在的驱动比如部分文件系统、网卡驱动会缺失启动到一半就可能出问题。make install把编译好的内核镜像、System.map、.config复制到/boot同时自动调用引导更新工具把新内核写进 GRUB 菜单。常规流程是sudo make modules_install sudo make install两个命令都要用管理员权限执行。顺序不能反因为make install里生成的 initramfs 需要依赖模块目录里已经安装好的模块。安装完成后在/boot下你能看到新内核的文件ls /boot通常包括vmlinuz-5.15.y、System.map-5.15.y、config-5.15.y以及initrd.img-5.15.y这样的文件。如果你看不到initrd.img说明 initramfs 没有生成后面我会专门说这个问题的解决办法。3.4 更新引导并重启验证make install在多数发行版上会自动触发update-grub但为了保险我会手动再执行一次sudo update-grub执行后能看到输出中列出了检测到的内核镜像列表。确认里面包含新版本的内核后再重启。sudo reboot重启后在 GRUB 菜单里如果默认项就是新内核直接进系统。如果没有默认选中也可以在 GRUB 的高级选项里手动选择新内核进入。进入系统后第一件事就是验证内核是否真的变成新的了uname -a uname -r看到输出里的版本号与你编译的源码版本一致实验的核心目标就算达成。进一步验证还可以用cat /proc/version ls /lib/modules//proc/version输出里能直接看到内核版本和编译它的 gcc 版本这个信息很有用实验报告里通常需要记录。4. 实操中常见问题与排查实录4.1 配置界面和编译前置报错我做实验时和帮同学排查时遇到最多的前三类报错都在编译早期。执行make menuconfig时报找不到 curses.h。本质是没装libncurses-dev。执行上面第 2.3 节里的安装命令即可装完重新make menuconfig。执行make后立刻报/bin/sh: flex: command not found或bison: not found。说明词法语法工具缺失安装flex、bison后继续。注意这种情况不用从头开始直接再执行一次make构建系统会继续未完成的部分。报openssl/xxx.h: No such file or directory。说明缺 OpenSSL 开发头文件安装libssl-dev。这个一般发生在配置打开了模块签名相关选项时如果你用的是发行版自带配置遇到概率很高。这三个问题都可以在开始编译前通过一次性安装依赖避免。我把它们列出来是因为每个都有人卡住过而且报错信息都比较直白属于“看到就知道缺什么”的类型。4.2 磁盘空间不足导致编译中断编译内核需要的不只是源码那几百 MB编译过程产生的.o中间文件、最终的镜像和模块加起来建议预留至少 10GB 空间。虚拟机的磁盘如果当初分配得很小很容易在中途报No space left on device。解决办法分两层。临时办法是用df -h看一下哪个分区满了如果是/usr/src或家目录所在分区清理掉不需要的大文件后重新编译。长期办法是在虚拟机设置里扩展磁盘或者在创建虚拟机时直接把磁盘设为 60GB 以上。动态扩容的虚拟磁盘实际使用多少就占多少不用担心一开始白白占用空间。我个人的习惯是编译前先执行一次df -h /usr/src看到可用空间少于 15GB 就先去扩容或清理。这个预检只需要几秒钟能避免编到一半失败的重复劳动。4.3 编译过程卡死或内存耗尽编译阶段如果机器突然无响应或者强制结束任务后看到大量内存交换记录多半是并行编译数过高的锅。make -j的任务数会同时启动多个编译任务每个 gcc 都可能吃几百 MB 内存。4GB 内存的机器开-j8内存瓶颈几乎是必然的。解决方法就是降低并行度比如make -j2。另外虚拟机里可以留意一下任务管理器里的内存占用。如果编译时内存飙到 90% 以上果断 CtrlC 终止降并行度再继续。编译本身是支持断点续作的已经编译好的目标文件不会白费。4.4 重启后卡在开机界面或直接进不了新系统这是最吓人但最常见的问题。不要慌虚拟机有快照大不了恢复重来。但大部分情况下是可以修复的。先说一种典型症状重启后 GRUB 里能看到新内核但选择后屏幕停留在类似Loading Linux 5.15.y ...的位置后面没了动静。最常见原因是 initramfs内存文件系统镜像缺失或与内核版本不匹配。内核启动早期需要 initramfs 来加载必要的驱动然后挂载真正的根文件系统。如果你执行make install后/boot下没有initrd.img-5.15.y或者生成失败就会卡在这一步。解决办法是回到旧内核GRUB 菜单里选择旧版本进入系统后手动为新内核生成 initramfssudo update-initramfs -c -k 5.15.y其中-k参数指定内核版本。执行完再sudo update-grub重启选择新内核多半就能进了。另一种典型症状内核启动时直接出现Kernel panic - not syncing: VFS: Unable to mount root fs。这个报错的意思是在挂载根文件系统时找不到对应的驱动。原因是你的配置里没把根文件系统所需驱动编进内核比如根分区是 ext4而配置里 ext4 被设成了m模块但 initramfs 里又没包含这个模块导致内核启动时既没有内建支持也加载不了模块。解决办法是回到配置环节在make menuconfig里把根文件系统相关选项设为y内建。具体搜索一下你根分区的文件系统类型例如ext4File systems - The Extended 4 (ext4) filesystem设为*XFSFile systems - XFS filesystem support设为*。另外如果根分区是 NVMe 或 SATA 硬盘也要确认对应的磁盘控制器驱动编进了内核而不只是模块。课程里讲设备驱动时一定会提这个场景实验里遇到正好加深理解。4.5 模块加载签名和版本校验报错新内核启动后如果你尝试加载自己的模块或者某个第三方驱动可能会遇到类似module verification failed: signature and/or required key missing的错误。这是因为发行版的配置默认打开了模块签名校验CONFIG_MODULE_SIG_ENFORCE内核只加载有有效签名的模块。如果是做实验最简单的方式是在配置阶段把这个校验关掉。在make menuconfig里搜索CONFIG_MODULE_SIG把它设为n然后重新编译、重新安装。注意编译和安装都要重来不能只改配置不重编。这个操作要改就趁早改等到启动新内核后才意识到代价就是再等一次完整的编译时间。还有一种情况是加载模块报version magic xxx should be yyy或类似的不匹配错误。这是模块编译时用的内核头文件版本与当前运行内核不一致导致的。检查一下你编译模块时是不是用了源码目录里的头文件而不是当前运行内核的/usr/src/linux-headers-$(uname -r)。实验阶段一般不会写模块如果遇到了优先确认你是在当前运行的新内核环境下编译的模块。4.6 一份快速排查参考表现象可能原因快速解决办法menuconfig 报 ncurses 缺失缺libncurses-dev安装后重新执行编译报 flex/bison 缺失缺词法语法工具安装flex、bison编译报 openssl 头文件缺失缺libssl-dev安装后重新编译编译中报磁盘不足磁盘空间不够df -h检查并清理或扩容编译时系统卡死并行任务数过高降低-j数值开机卡在 Loading Linuxinitramfs 缺失或不匹配update-initramfs -c -k 版本Kernel panic VFS根文件系统驱动未内建把 fs 驱动改为y重编模块加载签名错误CONFIG_MODULE_SIG_ENFORCE开启配置中关闭后重编这张表是我踩过的坑浓缩出来的。方便的做法是把表存下来每次编译内核时对照检查能省下不少定位问题的时间。5. 验证实验成果与延伸思考5.1 从内核版本号到完整验证清单实验报告的“结果与分析”部分光写uname -r的输出是不够的。我自己整理了一个验证清单基本覆盖了实验要求里会问到的点uname -r确认当前内核版本号与编译的源码版本一致uname -a查看完整内核信息包括编译机器的主机名、编译时间、gcc 版本ls /boot确认新内核的vmlinuz、initrd.img、System.map、config文件都已安装ls /lib/modules/确认新内核版本目录存在且里面有kernel/目录和modules.dep等文件cat /proc/version从内核视角查看版本和编译器信息dmesg | tail -30查看内核启动日志末尾确认没有明显错误比如驱动加载失败、文件系统挂载失败等。这几项拍成截图放进实验报告比你写一千字描述都更有说服力。另外启动日志里其实隐藏着很多可写内容。你可以用下面的命令统计一下启动过程中初始化了哪些服务、加载了哪些驱动systemd-analyze systemd-analyze blame不过这只是加分项实验一的核心还是把编译流程跑通。5.2 一次编译实验能延伸出哪些东西做完实验一之后有几个方向值得继续动手既能让实验报告内容更充实也为后续实验打基础。第一个是尝试写一个最简单的内核模块。也就是经典的“hello module”一个.c文件包含module_init和module_exit配合一个Makefile编译出.ko文件然后insmod加载、rmmod卸载、dmesg看输出。这个实验虽然简单但能把“内核模块机制”“模块与内核版本匹配”“模块加载/卸载生命周期”这几个知识点串起来。第二个是修改一个内核配置项后重新编译对比两次编译结果和系统行为差异。比如把某个调度器相关选项从y改成n或者打开一个没用的驱动再关掉观察镜像体积的变化。这能帮你真正理解配置的作用而不只是机械地按菜单。第三个是去读一两篇源码注释。比如kernel/sched/core.c的开头或者init/main.c里start_kernel函数的流程。不需要读懂全部只要能把函数调用顺序和你学过的操作系统启动过程对应起来实验报告就会显得很有深度。6. 我的实操体会整轮实验做完最深的体会是编译内核这件事难度不在命令本身而在“出了问题之后你能不能定位”。命令就那么几条但系统在配置选择、工具链、模块机制、引导过程任何一个环节出错表现都可能不一样。第一部快照、预先装齐依赖、复制发行版配置而不是从零配置这几步帮我省掉了至少一半的重复劳动。再分享一个小技巧编译过程中如果某个.o文件编译失败想单独重编某个文件可以不用全量编译直接指定目标文件路径重新执行make构建系统会自动补齐依赖。但如果是.config改了配置那就必须重新执行一次完整的make因为依赖关系已经变化单文件重编无法覆盖这种变更。最后是关于心态的建议。第一次编译内核报错是常态一次通过反而是少数。我自己的三遍流程里第一遍卡在libssl-dev缺失第二遍卡在 initramfs 生成不全第三遍才做到全流程顺滑。每一次报错都对应一次对系统启动过程更深的理解。如果你做实验也卡住了别急着恢复快照先记录报错信息、分析原因、尝试修复这个过程比最终的成功结果更有价值。当然时间实在不够的时候快照兜底保住实验进度也完全没问题。