在操作系统开发领域,Linux内核以其开源、稳定和高度可配置的特性,成为了学习和构建自定义操作系统的绝佳起点。无论是出于学术研究、嵌入式系统开发,还是对计算机底层原理的深度探索,基于Linux内核进行操作系统开发都是一项极具价值的实践。然而,面对庞大的内核源码、复杂的编译流程和晦涩的底层概念,许多开发者常常感到无从下手,资料也往往零散不成体系。

本文旨在为你提供一份从零开始的、系统化的Linux内核操作系统开发实战指南。我们将从最基础的环境搭建和源码获取开始,逐步深入到内核配置、编译、安装,并最终运行一个自定义的最小化系统。文中将包含完整的命令、可复现的代码片段以及每一步的原理讲解,帮助你不仅知道“怎么做”,更理解“为什么这么做”。无论你是计算机专业的学生,还是希望深入理解系统原理的开发者,都能通过本文构建起清晰的实践路径。

1. 理解核心概念:Linux内核与操作系统

在动手之前,我们需要厘清几个核心概念,这有助于理解我们整个开发过程的目标和边界。

1.1 什么是Linux内核?

Linux内核(Linux Kernel)是操作系统的核心部分。它是一个用C语言和少量汇编语言编写的、管理计算机硬件资源的软件。你可以将它想象成计算机的“总管家”,负责以下核心事务:

  • 进程管理 :决定哪个程序(进程)在何时使用CPU。
  • 内存管理 :为每个进程分配和回收内存空间。
  • 设备驱动 :作为硬件(如磁盘、网卡、USB设备)和应用程序之间的翻译官。
  • 文件系统 :管理磁盘上的数据如何被组织、存储和检索。
  • 网络通信 :处理网络数据包的发送和接收。

我们常说的“Linux操作系统”(如Ubuntu, CentOS)实际上是“Linux内核”加上一系列外围软件(GNU工具、图形界面、应用软件等)共同构成的完整发行版。我们本次开发,聚焦于内核本身及其直接相关的启动和根文件系统。

1.2 基于Linux内核开发操作系统意味着什么?

这通常意味着我们不会从零开始写一个内核,而是以现有的、成熟的Linux内核源码为基础,进行以下工作:

  1. 获取与理解源码 :下载官方内核源码,阅读其关键部分的代码结构。
  2. 配置与定制 :根据目标硬件平台(如x86_64, ARM)和功能需求(如需要哪些驱动、支持哪些文件系统),对内核进行裁剪和配置。
  3. 编译与构建 :将配置好的源码编译成可执行的二进制文件——内核镜像(如 bzImage 或 zImage )。
  4. 制作根文件系统 :创建一个包含最基本工具(如 init 程序、 shell )的微型文件系统,供内核启动后挂载。
  5. 引导与测试 :使用引导程序(如GRUB)或模拟器(如QEMU)加载我们编译的内核和制作的根文件系统,启动一个完整的、可交互的操作系统环境。

这个过程的核心价值在于,你能亲身体验操作系统从源码到启动的完整生命周期,深刻理解硬件抽象层、驱动模型、系统调用等底层机制。

2. 环境准备与工具链搭建

工欲善其事,必先利其器。一个稳定、纯净的Linux开发环境是成功的第一步。为了避免对宿主系统造成影响,强烈建议在虚拟机中进行以下所有操作。

2.1 基础开发环境

我们选择Ubuntu 22.04 LTS作为开发宿主系统,因为它拥有完善的软件包管理和活跃的社区支持。其他主流Linux发行版(如Fedora, Arch)也可行,但包管理命令需相应调整。

首先,更新系统并安装必备的编译工具和依赖库:

# 更新软件包列表
sudo apt update
sudo apt upgrade -y

# 安装核心开发工具链(编译器、链接器、库等)
sudo apt install -y build-essential

# 安装内核编译特定依赖
sudo apt install -y libncurses-dev flex bison libssl-dev libelf-dev bc

# 安装QEMU虚拟机(用于测试编译好的内核)
sudo apt install -y qemu-system-x86

# 可选:安装git用于获取内核源码,或直接下载压缩包
sudo apt install -y git wget

关键工具说明 :

  • build-essential :包含了GCC编译器、GNU Make等构建C/C++项目的基础工具。
  • libncurses-dev :提供字符终端下的图形界面库,用于 make menuconfig 配置界面。
  • flex 和 bison :词法和语法分析器生成器,用于编译内核中的某些部分。
  • libssl-dev 和 libelf-dev :提供加密和ELF文件格式支持,是现代内核编译的必需库。
  • bc :任意精度计算器语言,内核编译脚本会用到。
  • qemu-system-x86 :一个纯软件实现的虚拟化工具,可以模拟x86硬件环境来引导我们的内核,无需重启物理机,非常安全便捷。

2.2 获取Linux内核源码

有两种主流方式获取内核源码:通过Git克隆或直接下载稳定版压缩包。对于初学者,推荐下载稳定版,避免处于开发中的“主线”版本可能带来的不稳定性。

方式一:使用Git克隆(获取最新开发版)

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
# 可以切换到某个稳定版本标签,例如 v6.1
# git checkout v6.1

方式二:下载稳定版压缩包(推荐) 访问 kernel.org 找到最新的稳定版(stable)。本文以当时一个长期支持版本 6.1.x 为例(请在实际操作时替换为更新的稳定版本号)。

# 使用wget下载(请将URL替换为官网最新的稳定版链接)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.82.tar.xz
# 解压源码
tar -xvf linux-6.1.82.tar.xz
# 进入源码目录
cd linux-6.1.82

进入解压后的目录,你将看到内核源码的完整结构,包括 arch/ (架构相关代码)、 drivers/ (设备驱动)、 fs/ (文件系统)、 kernel/ (核心内核代码)等关键目录。

3. 内核配置与编译详解

这是最核心的步骤,决定了你的内核包含哪些功能,能在什么硬件上运行。

3.1 内核配置系统

Linux内核使用 Kconfig 系统进行配置。配置结果保存在 .config 文件中。有几种配置界面:

  • make defconfig :生成针对当前主机架构的默认配置。
  • make menuconfig :基于ncurses的文本图形界面,最常用。
  • make xconfig :基于Qt的图形界面(需要安装相关依赖)。
  • make oldconfig :基于旧的 .config 文件更新配置。

对于为当前机器编译一个通用内核,可以从默认配置开始:

# 生成x86_64架构的默认配置
make x86_64_defconfig

执行后,当前目录下会生成一个 .config 文件。你可以用文本编辑器查看它,里面是成千上万个 CONFIG_XXX=y/n/m 的配置项。

3.2 使用menuconfig进行定制

运行 make menuconfig 进入交互式配置界面。这里可以启用或禁用特定功能。

make menuconfig

界面中, [ ] 表示未编译, [*] 表示编译进内核( =y ), [M] 表示编译为模块( =m ), < > 则表示该选项有子选项。使用方向键导航,空格键切换状态, / 键搜索。

对于最小化系统,几个关键配置建议 :

  1. 在 General setup 中,可以设置本地版本号( Local version ),如 -mycustomkernel ,以便在启动时区分。
  2. 在 Device Drivers 中,确保你需要的存储设备驱动(如 Block devices -> Virtio block driver ,用于QEMU虚拟磁盘)和文件系统(如 File systems -> The Extended 4 (ext4) filesystem )被启用( * )或编译为模块( M )。
  3. 对于在QEMU中测试,在 Device Drivers -> Network device support -> Ethernet driver support 中启用 Virtio network driver 。
  4. 为了支持后续制作 initramfs ,确保 General setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support 被启用。

配置完成后,选择 <Save> 保存到 .config 文件,然后退出。

3.3 编译内核

编译是一个耗时过程,取决于你的CPU核心数。使用 -j 参数可以并行编译以加快速度。

# nproc命令可以获取你CPU的逻辑核心数,例如是8
make -j$(nproc)

编译过程会输出大量信息。如果一切顺利,最终会在 arch/x86/boot/ 目录下生成内核镜像文件 bzImage 。这就是我们编译好的、可引导的内核。

常见编译问题与解决 :

  • 错误: openssl/opensslv.h: No such file or directory
    • 原因 :缺少OpenSSL开发库。
    • 解决 :确保已安装 libssl-dev 。
  • 错误: gcc: error: unrecognized command line option ‘-fstack-protector-strong’
    • 原因 :GCC版本过旧。
    • 解决 :升级GCC或使用系统默认的较新版本。Ubuntu 22.04的GCC通常是11或12,足够新。
  • 编译过程被中断,如何继续?
    • 直接再次运行 make -j$(nproc) ,Make工具会自动从上次中断的地方继续。

4. 制作最小根文件系统(initramfs)

内核启动后,需要挂载一个“根文件系统”( / )才能运行用户空间的程序(如 init 、 shell )。我们创建一个基于 busybox 的极小根文件系统。

4.1 编译BusyBox

BusyBox被称为“瑞士军刀”,它将许多常用的Unix工具(如 ls , cp , sh , mount )集成进一个单一的可执行文件,非常适合嵌入式或最小化系统。

# 1. 下载BusyBox源码(回到上级目录操作)
cd ..
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1

# 2. 配置BusyBox,选择静态编译,这样不依赖宿主机的动态库
make defconfig
# 进入menuconfig进行微调
make menuconfig

在BusyBox的 menuconfig 中,进入: Settings -> Build static binary (no shared libs) 选择 [*] (按空格键选中)。 然后保存退出。

# 3. 编译并安装到临时目录
make -j$(nproc)
make install CONFIG_PREFIX=../rootfs

编译安装后,在 ../rootfs 目录下会生成 bin , sbin , usr/bin 等目录,里面是BusyBox生成的各种工具链接。

4.2 构建根文件系统目录结构

我们需要手动创建一些必要的目录和设备节点。

# 进入rootfs目录
cd ../rootfs

# 创建Linux系统必需的目录
mkdir -p proc sys dev etc/init.d tmp

# 创建初始启动脚本 /etc/init.d/rcS
cat > etc/init.d/rcS << EOF
#!/bin/sh
# 挂载虚拟文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
# 设置主机名
hostname mylinux
# 打印欢迎信息
echo "Welcome to My Custom Linux!"
# 启动一个shell
/bin/sh
EOF

# 给启动脚本执行权限
chmod +x etc/init.d/rcS

# 在根目录创建init文件,它是指向busybox的链接
ln -s bin/busybox init

关键点解释 :

  • proc , sys , dev , tmp :这些是Linux内核运行时必需的虚拟文件系统或目录。
  • rcS :这是一个简单的启动脚本,内核启动后,会由 init 程序执行它来初始化系统环境。
  • init :内核启动完成后,寻找的第一个用户空间程序。我们让它指向 busybox ,因为busybox实现了 init 功能。

4.3 打包为initramfs

现在,我们将这个 rootfs 目录打包成 cpio 格式的归档文件,内核可以将其作为初始内存盘( initramfs )加载。

# 回到rootfs的父目录
cd ..
# 使用find和cpio命令打包,并用gzip压缩
find rootfs -print0 | cpio --null -ov --format=newc | gzip -9 > initramfs.cpio.gz

生成的 initramfs.cpio.gz 文件就是我们制作好的微型根文件系统。

5. 使用QEMU引导与测试

现在,我们有了编译好的内核 ( linux-6.1.82/arch/x86/boot/bzImage ) 和根文件系统 ( initramfs.cpio.gz )。使用QEMU将它们组合起来启动。

5.1 基本QEMU启动命令

确保你在包含内核镜像和initramfs文件的目录下(例如在 linux-6.1.82 的父目录)。

qemu-system-x86_64 \
    -kernel linux-6.1.82/arch/x86/boot/bzImage \
    -initrd initramfs.cpio.gz \
    -append "console=ttyS0 root=/dev/ram rdinit=/init" \
    -nographic \
    -m 512M

参数详解 :

  • -kernel :指定编译好的内核镜像路径。
  • -initrd :指定初始内存盘(我们的根文件系统)路径。
  • -append :传递给内核的命令行参数。
    • console=ttyS0 :将控制台输出重定向到串口0,以便在 -nographic 模式下看到输出。
    • root=/dev/ram :告诉内核根文件系统在RAM磁盘上。
    • rdinit=/init :指定初始内存盘中的初始化程序为 /init (即我们链接的busybox)。
  • -nographic :不使用图形界面,所有输出输入通过当前终端进行。
  • -m 512M :为虚拟机分配512MB内存。

5.2 启动过程与交互

执行上述命令后,QEMU会开始启动。你应该能看到内核解压、初始化硬件、加载驱动、挂载根文件系统,最后执行我们的 rcS 脚本,打印出“Welcome to My Custom Linux!”,并进入BusyBox提供的shell。

此时,你已成功进入一个完全由你配置和编译的Linux内核所驱动的微型操作系统!可以尝试运行一些基本命令:

# 在QEMU启动的shell中执行
ls /          # 查看根目录
cat /proc/cpuinfo # 查看CPU信息
ps           # 查看进程(应该只有init和sh)

要退出QEMU,先按 Ctrl+A ,然后松开再按 X 。

5.3 进阶测试:添加网络支持(可选)

如果在内核配置中启用了网络驱动,可以为QEMU添加虚拟网卡,让这个迷你系统具备网络能力。

qemu-system-x86_64 \
    -kernel linux-6.1.82/arch/x86/boot/bzImage \
    -initrd initramfs.cpio.gz \
    -append "console=ttyS0 root=/dev/ram rdinit=/init ip=dhcp" \
    -nographic \
    -m 512M \
    -netdev user,id=net0,hostfwd=tcp::2222-:22 \
    -device virtio-net-pci,netdev=net0
  • -netdev user,id=net0,... :创建一个用户模式的网络后端,并设置端口转发(将宿主机的2222端口转发到虚拟机的22端口)。
  • -device virtio-net-pci,netdev=net0 :为虚拟机添加一个Virtio网络设备,连接到刚才创建的网络后端。
  • ip=dhcp :内核命令行参数,告诉内核在启动时通过DHCP获取IP地址。

在BusyBox shell中,你可以使用 ifconfig 或 ip addr 查看获取到的IP,并使用 ping 测试网络连通性(BusyBox默认编译可能不包含 ping ,需在配置时启用)。

6. 常见问题与深度排查指南

在实践过程中,你可能会遇到各种问题。下面是一个系统化的排查清单。

问题现象 可能原因 排查步骤与解决方案
make menuconfig 无法打开图形界面 缺少 ncurses 开发库。 运行 sudo apt install libncurses-dev 。
编译内核时出现 recipe for target ‘…’ failed 错误 1. 依赖包缺失。
2. 源码不完整或损坏。
3. .config 配置冲突。
1. 根据错误信息安装对应开发包(如 libssl-dev , libelf-dev )。
2. 重新下载并解压内核源码。
3. 尝试 make clean 后,使用 make defconfig 重新配置。
QEMU启动后卡在 Kernel panic - not syncing: VFS: Unable to mount root fs 内核找不到或无法挂载根文件系统。 1. 检查 -initrd 路径 :确保initramfs文件路径正确且已生成。
2. 检查内核配置 :确保 CONFIG_BLK_DEV_INITRD=y 和 CONFIG_BLK_DEV_RAM=y 。
3. 检查内核命令行 : -append 中的 root= 和 rdinit= 参数是否正确。
QEMU启动后卡在 Kernel panic - not syncing: No working init found. 内核找到了根文件系统,但找不到或无法执行 init 程序。 1. 检查initramfs内容 :使用 cpio -it < initramfs.cpio.gz 查看打包的文件,确认根目录下存在可执行的 init 文件。
2. 检查BusyBox编译 :确认BusyBox是静态编译,使用 file rootfs/bin/busybox 查看是否显示 statically linked 。
3. 检查启动脚本权限 :确保 etc/init.d/rcS 有执行权限( chmod +x )。
系统启动后,键盘输入无反应 控制台配置问题。 1. 确保QEMU命令中包含了 console=ttyS0 和 -nographic 。
2. 尝试使用 -serial mon:stdio 替代 -nographic 。
自定义功能(如特定驱动)未生效 内核配置中未启用该功能或相关依赖。 1. 在 make menuconfig 中,使用 / 键搜索相关配置项,确保其状态为 [*] (编译进内核) 或 [M] (编译为模块)。
2. 如果编译为模块,需要将模块文件( .ko )也打包进initramfs,并在启动脚本中 insmod 。

深度调试技巧 :

  • 增加内核启动信息 :在 -append 参数中添加 loglevel=8 或 debug ,可以让内核打印更详细的启动日志。
  • 使用GDB调试内核 :在QEMU启动参数中添加 -S -s ,然后使用GDB连接进行源码级调试。这需要对内核符号表和GDB有较深了解,是高级调试手段。
  • 分析Initramfs :如果怀疑根文件系统有问题,可以解压检查: mkdir test && cd test && zcat ../initramfs.cpio.gz | cpio -idmv 。

7. 最佳实践与进阶方向

完成基础搭建后,你可以遵循以下最佳实践,并将项目推向更接近实际应用的方向。

7.1 开发流程与版本管理最佳实践

  1. 使用版本控制 :将你的内核配置文件( .config )、构建脚本、根文件系统构建脚本等纳入Git管理。内核源码本身体积巨大,可以将其作为Git子模块(submodule)或记录明确的版本号。
  2. 分离构建目录 :可以使用 make O=../build 命令将编译输出目录与源码目录分离,保持源码树的清洁。
  3. 增量编译 :修改配置或部分代码后,直接运行 make 即可进行增量编译,无需每次都 make clean 。
  4. 保留配置历史 :将每次稳定可用的 .config 文件备份为 config-版本号 ,便于回滚和对比。

7.2 从Initramfs过渡到磁盘镜像

目前我们的系统完全运行在内存中。一个更真实的系统需要从硬盘启动。

  1. 创建磁盘镜像 :使用 dd 和 mkfs 创建一个空的磁盘镜像文件并格式化为ext4。
  2. 安装根文件系统 :将 rootfs 目录中的所有文件复制到磁盘镜像中。
  3. 安装引导程序 :使用 grub-install 将GRUB安装到磁盘镜像。
  4. 配置GRUB :编写 grub.cfg ,指定内核和根文件系统所在分区。
  5. 修改QEMU启动命令 :使用 -drive file=disk.img,format=raw 参数加载磁盘镜像,并从硬盘启动( -boot c )。

7.3 内核模块开发与调试

将某些功能编译为模块( =m )是生产系统的常见做法。

  1. 编译模块 :在内核源码目录下, make modules 会编译所有标记为 M 的组件。
  2. 安装模块 : make modules_install INSTALL_MOD_PATH=../rootfs 可以将模块安装到你的根文件系统中。
  3. 动态加载 :在系统启动后,使用 insmod module.ko 加载模块, rmmod module 移除模块, lsmod 查看已加载模块。
  4. 编写简单模块 :你可以尝试编写一个最简单的“Hello World”内核模块,学习模块的初始化、退出函数以及 printk 日志输出。

7.4 性能分析与优化

  1. 内核大小优化 :使用 make menuconfig 仔细裁剪不需要的驱动和功能,特别是针对特定硬件(如嵌入式设备)时。关注 Kernel hacking 下的调试选项,生产环境应关闭它们。
  2. 启动时间分析 :在内核命令行添加 initcall_debug 参数,可以打印每个初始化函数的耗时,帮助分析启动瓶颈。
  3. 使用perf进行性能剖析 :在宿主系统上安装 linux-tools-generic ,可以对运行在QEMU中的内核进行性能监控(需要QEMU和内核支持)。

7.5 安全考量

  1. 最小权限原则 :在根文件系统中,只放置必要的可执行文件和库。移除不必要的setuid程序。
  2. 内核安全特性 :在配置中考虑启用安全选项,如 CONFIG_STRICT_DEVMEM , CONFIG_SECURITY , CONFIG_CC_STACKPROTECTOR 等。
  3. 及时更新 :关注内核安全公告(CVE),定期将你的自定义内核基线更新到最新的稳定版本,修补安全漏洞。

从编译一个标准内核,到制作最小文件系统,再到成功引导,这个闭环实践打通了操作系统从源码到运行的关键路径。掌握这个流程后,你可以更自信地阅读内核源码的特定子系统(如进程调度 kernel/sched/ 或内存管理 mm/ ),因为你知道如何将修改后的代码编译并运行起来进行验证。下一步,可以尝试为内核添加一个简单的系统调用,或者为一个虚拟设备编写一个简单的字符设备驱动,这将使你从“使用者”真正迈向“开发者”。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐