跳到正文

目录

Linux 内核开发指南:从源码结构到参与上游社区

目标读者:有一定 C 语言和操作系统基础,想系统掌握 Linux 内核开发路径的工程师 关键问题:内核源码如何组织?如何本地编译验证?如何向上游提交第一个补丁? 难度:⭐⭐⭐⭐(高级系统编程) 预计阅读时间:25 分钟


§0 三分钟速览

如果你现在只想先判断这篇文章值不值得继续读,先记住下面 3 点:

  1. Linux 内核是宏内核架构,所有内核代码运行在同一地址空间,系统调用是用户空间与内核空间的唯一合法接口。
  2. 内核采用基于邮件列表的开发模型,而非 GitHub PR 流程;提交必须包含 Signed-off-by: 表示同意 DCO(开发者原产地证书)。
  3. 新人入门建议:从修复编译警告或改进文档开始,不要试图一次性理解整个内核。

如果你带着不同目标阅读,可以直接按下面的顺序跳读:

  • 想快速了解内核结构:先看 §1§2
  • 想动手编译内核:先看 §3
  • 想编写设备驱动:先看 §4
  • 想参与社区贡献:先看 §5§6

§1 本文覆盖范围

通过本文,你会了解:

  1. Linux 内核源码的目录结构与组织原则
  2. 内核空间与用户空间的隔离设计
  3. 如何从零开始编译和安装自定义内核
  4. 如何编写、加载和测试一个简单的字符设备驱动
  5. 内核社区的开发流程和提交规范
  6. Rust 融入内核的现状与 BPF 动态追踪技术

§2 仓库结构与源码组织

2.1 顶层目录一览

克隆仓库后(注意仓库体积超过 6GB),你会看到如下顶层目录结构:

linux/
├── arch/          # 架构支持代码,每种 CPU 架构一个子目录
├── block/         # 块设备层
├── certs/         # 证书与签名相关
├── crypto/        # 加密算法框架
├── Documentation/ # 内核文档(重)
├── drivers/       # 设备驱动(最庞大)
├── fs/            # 文件系统实现
├── include/       # 公共头文件
├── init/          # 内核初始化代码
├── ipc/           # 进程间通信
├── kernel/        # 核心子系统(调度、进程管理等)
├── lib/           # 通用库函数
├── mm/            # 内存管理子系统
├── net/           # 网络协议栈
├── rust/          # Rust 内核支持(6.1 起新增,6.12 起 Rust 驱动可用)
├── samples/       # 示例代码
├── scripts/       # 构建与工具脚本
├── security/      # 安全模块(SELinux、smack 等)
├── sound/         # 音频子系统
├── tools/         # 调试与性能分析工具
├── usr/           # initramfs 相关
└── virt/          # 虚拟化支持(KVM 等)

理解顶层目录是导航内核源码的第一步。最核心的学习路径是按子系统逐层深入:想理解进程调度看 kernel/sched/,想理解内存管理看 mm/,想写设备驱动则深入 drivers/ 下的对应子目录。

2.2 架构目录的特殊性

arch/ 目录存放各 CPU 架构的平台代码,例如:

  • arch/x86/ — Intel/AMD x86 架构(32 位和 64 位)
  • arch/arm/ — ARM 32 位架构
  • arch/arm64/ — ARM 64 位架构(也称 AArch64)
  • arch/riscv/ — RISC-V 架构
  • arch/loongarch/ — 龙芯 LoongArch 架构(5.19 起合并)
  • arch/powerpc/ — PowerPC 架构
  • arch/s390/ — IBM System z 大型机架构

每种架构下通常包含:boot/(启动相关)、kernel/(架构级内核代码)、mm/(架构级内存管理)、lib/(架构级汇编优化)等。这种组织方式体现了 Linux 对硬件抽象的核心设计思想:公共代码放在 kernel/mm/ 等通用目录,架构专用代码隔离在 arch/

2.3 语言构成

内核源码的语言构成(按 OpenHub 对主线仓库的统计,2026 年 9 月):

语言代码行数(大致)占比
C~3710 万行~96%
Assembly~26 万行~0.7%
Shell~26 万行~0.7%
Rust~12 万行~0.3%
Python~12 万行~0.3%
Makefile构建系统

Rust 的引入是近年来内核社区最重要的变化。内核从 6.1(2022 年底)起正式支持 Rust,2023 年底的 6.7 合并了第一批 Rust 驱动,2024 年底的 6.12 又加入了 Nova GPU 驱动原型。到 2025 年 12 月的内核维护者峰会上,Rust 被正式认定为内核的核心语言,不再是实验特性。对想进入内核开发的工程师来说,C 仍然是前提,但 Rust 驱动的需求正在快速增长。

2.4 Documentation 目录

内核文档体系极为完整,位于 Documentation/ 目录。核心文档包括:

  • process/ — 开发流程、提交规范、社区行为守则
  • admin-guide/ — 系统管理指南
  • dev-tools/ — 开发工具链文档
  • filesystems/ — 各文件系统设计文档
  • driver-api/ — 驱动编写 API 参考

学习内核开发,最权威的第一手资料就是这里。所有关于"我这样写驱动对不对"的问题,答案往往可以在 Documentation/ 中找到。


§3 核心概念与架构分析

3.1 内核空间与用户空间

Linux 采用宏内核(monolithic kernel)架构,所有内核代码运行在同一个地址空间(内核空间),与用户空间完全隔离。系统调用是两者之间的唯一合法接口。

┌─────────────────────────┐
│      User Space         │  ← 用户态进程
│   (glibc / musl 等)     │
└────────────┬────────────┘
             │ syscall (如 read, write, mmap...)
             ▼
┌─────────────────────────┐
│      Kernel Space       │  ← 内核代码运行区
│   (系统调用、驱动、     │
│    网络协议栈、文件系统) │
└─────────────────────────┘

这种设计的优势是性能高(内核内部函数调用无跨进程边界开销),代价是内核崩溃会导致整个系统崩溃。正因如此,内核代码对稳定性和防御性要求极高。

3.2 子系统层次结构

从高到低,内核可以划分为:

  1. 系统调用接口(SCI)— 规定用户空间与内核的契约,每个系统调用有明确的编号
  2. 进程管理 — 调度、创建、销毁进程。调度器从早期的 O(1)、CFS(完全公平调度器,2.6.23 起服役约 16 年)演进到 6.6 起默认的 EEVDF(最早合格虚拟截止时间优先调度器),后者的核心思路是给每个任务算一个虚拟截止时间,优先执行截止时间最早者
  3. 内存管理 — 虚拟内存管理、页面置换、kmalloc/slab 分配器
  4. 文件系统(VFS)— 通用虚拟文件系统层,之下是 ext4、Btrfs、XFS 等具体实现
  5. 设备驱动 — 字符设备、块设备、网络设备驱动框架
  6. 网络协议栈 — TCP/IP、UDP、Socket 编程接口
  7. 架构层 — 汇编级初始化、页表设置、特权模式切换

理解这个层次,对于定位问题重要:网络超时既可能是协议栈问题,也可能是驱动问题;文件系统报错既可能是 VFS 层 bug,也可能是底层 ext4 实现的问题。

3.3 内核配置系统(Kconfig / Makefile)

内核使用 Kconfig 语言描述配置项,Makefile 描述构建规则。学习内核源码时,理解这两个系统是绕不开的:

# drivers/usb/Makefile 简化示例
obj-y += core.o
obj-$(CONFIG_USB) += host/
obj-$(CONFIG_USB) += storage/
obj-$(CONFIG_USB_ACM) += cdc_acm.o
# drivers/usb/Kconfig 简化示例
config USB
    bool "USB support"
    default y
    help
      This option adds support for Universal Serial Bus.

config USB_ACM
    bool "USB Modem (CDC ACM) support"
    depends on USB
    help
      Say Y here if you want to use USB modems.

配置系统通过 make menuconfig(或 nconfigxconfig)提供交互式配置界面,配置结果写入 .config 文件。CONFIG_XXX 宏在源码中控制代码的编译与链接。

实战经验:第一次编译内核,推荐使用 make localmodconfig 从当前运行系统的 .config 出发,只保留当前硬件需要的配置项,可大幅缩短编译时间。

3.4 设备驱动模型

Linux 统一了设备抽象,核心概念包括:

  • bus_type — 总线类型(PCI、USB、Platform 等),负责匹配驱动与设备
  • device — 设备结构体,描述硬件信息
  • driver — 驱动结构体,包含探测与操作函数
  • class — 设备类别(input、net、block 等)

现代驱动通常使用设备树(Device Tree,.dts 文件)描述硬件平台,替代了早期硬编码的平台设备。设备树编译后生成 DTB(Device Tree Blob),在启动时传递给内核。


§4 环境搭建与源码编译

4.1 获取源码

# 克隆仓库(~6GB,注意网络和磁盘空间)
git clone https://github.com/torvalds/linux.git
cd linux

# 查看版本标签(内核使用 tag 而非 branch 标记稳定版本)
git tag | grep -E '^v[0-9]+\.[0-9]+$' | sort -V | tail -20

注意:master 分支是开发中的前沿代码,生产环境应使用稳定版本标签或长期支持版(LTS)。截至 2026 年 9 月,最新稳定版是 7.2.x,仍受支持的 LTS 分支有 6.18.x、6.12.x(支持到 2028 年底)、6.6.x、6.1.x。对新手来说,6.12 LTS 是最稳妥的选择,Ubuntu、Debian 等发行版也围绕它构建。切换到特定版本:

# 例如切到 6.12 LTS 分支的最新小版本
git checkout v6.12.108

4.2 配置内核

方法一:从发行版配置出发(最常用):

# 复制当前系统配置(适合自己编译内核用途)
cp /boot/config-$(uname -r) .config

# 自动移除旧配置中已不存在的选项,加载新选项默认值
make olddefconfig

方法二:从头配置:

make menuconfig   # 交互式 TUI 配置(需要 ncurses)
# 或
make nconfig
# 或
make xconfig      # 需要 Qt

方法三:从当前硬件快速生成:

make localmodconfig   # 仅编译当前已加载的模块(推荐笔记本用户)

4.3 编译内核

编译过程根据硬件不同可能需要 30 分钟到数小时。基础编译命令:

# 并行编译(-j 后数字建议为 CPU 核心数的 1.5-2 倍)
make -j$(nproc)

# 如果编译 KVM 等模块需要完整编译,可选
make -j$(nproc) modules

编译 bzImage(压缩内核镜像):

make bzImage

编译模块(独立 .ko 文件):

make modules

4.4 安装与引导

# 安装模块(需要 root)
sudo make modules_install

# 安装内核镜像(复制到 /boot)
sudo make install

# 更新引导程序
sudo update-grub   # Debian/Ubuntu
# 或
sudo grub2-mkconfig -o /boot/grub2/grub.cfg   # RHEL/Fedora

注意:make install 只在部分架构(x86、arm 等)有实现,其他架构需要手动复制镜像文件。如果使用非标准引导工具(如 EFISTUB),也可以手动复制:

cp arch/x86/boot/bzImage /boot/vmlinuz-6.12-custom
cp System.map /boot/System.map-6.12-custom

4.5 验证内核版本

重启后验证:

uname -r          # 查看运行中的内核版本
cat /proc/version
dmesg | grep "Linux version"

§5 实战演示:编写第一个字符设备驱动

5.1 基本原理

Linux 的设备分为字符设备(character device)和块设备(block device)。字符设备以字节流形式访问,典型如键盘、鼠标、串口。本节演示如何编写一个最简单的字符设备驱动,内核版本 6.x 兼容。

5.2 完整示例代码

创建文件 hello_char.c

// SPDX-License-Identifier: GPL-2.0
/*
 * hello_char.c - 简单的字符设备驱动示例
 * 适用于 Linux 内核 6.x
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "hello_dev"
#define CLASS_NAME  "hello_class"

static dev_t dev_num;          // 设备号(主+次)
static struct cdev hello_cdev; // 字符设备结构
static struct class *hello_class;
static struct device *hello_device;

static char kernel_buf[256] = "Hello from kernel!\n";

static int hello_open(struct inode *inode, struct file *filp)
{
    pr_info("hello_char: device opened\n");
    return 0;
}

static int hello_release(struct inode *inode, struct file *filp)
{
    pr_info("hello_char: device closed\n");
    return 0;
}

static ssize_t hello_read(struct file *filp, char __user *buf,
                          size_t count, loff_t *ppos)
{
    int ret;
    if (*ppos >= strlen(kernel_buf))
        return 0;
    if (*ppos + count > strlen(kernel_buf))
        count = strlen(kernel_buf) - *ppos;
    ret = copy_to_user(buf, kernel_buf + *ppos, count);
    if (ret)
        return -EFAULT;
    *ppos += count;
    return count;
}

static ssize_t hello_write(struct file *filp, const char __user *buf,
                           size_t count, loff_t *ppos)
{
    int ret;
    if (count > 255)
        count = 255;
    ret = copy_from_user(kernel_buf, buf, count);
    if (ret)
        return -EFAULT;
    kernel_buf[count] = '\0';
    return count;
}

static const struct file_operations hello_fops = {
    .owner   = THIS_MODULE,
    .open    = hello_open,
    .release = hello_release,
    .read    = hello_read,
    .write   = hello_write,
};

static int __init hello_init(void)
{
    int ret;

    /* 动态分配设备号 */
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        pr_err("hello_char: failed to allocate device number\n");
        return ret;
    }
    pr_info("hello_char: major=%d minor=%d\n",
            MAJOR(dev_num), MINOR(dev_num));

    /* 初始化字符设备并注册 */
    cdev_init(&hello_cdev, &hello_fops);
    hello_cdev.owner = THIS_MODULE;
    ret = cdev_add(&hello_cdev, dev_num, 1);
    if (ret < 0) {
        unregister_chrdev_region(dev_num, 1);
        return ret;
    }

    /* 在 /sys/class/ 下创建类 */
    hello_class = class_create(CLASS_NAME);
    if (IS_ERR(hello_class)) {
        cdev_del(&hello_cdev);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(hello_class);
    }

    /* 创建设备节点 /dev/hello_dev */
    hello_device = device_create(hello_class, NULL, dev_num,
                                  NULL, DEVICE_NAME);
    if (IS_ERR(hello_device)) {
        class_destroy(hello_class);
        cdev_del(&hello_cdev);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(hello_device);
    }

    pr_info("hello_char: module loaded\n");
    return 0;
}

static void __exit hello_exit(void)
{
    device_destroy(hello_class, dev_num);
    class_destroy(hello_class);
    cdev_del(&hello_cdev);
    unregister_chrdev_region(dev_num, 1);
    pr_info("hello_char: module unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_VERSION("1.0");

5.3 Makefile

在同一目录下编写 Makefile:

obj-m += hello_char.o

KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

install:
	$(MAKE) -C $(KDIR) M=$(PWD) modules_install

5.4 编译与测试

# 编译驱动
make

# 查看生成的 .ko 文件
ls -lh hello_char.ko
modinfo hello_char.ko

# 加载模块(需要 root)
sudo insmod hello_char.ko

# 检查加载状态
lsmod | grep hello_char
dmesg | tail

# 查看自动创建的设备节点
ls -l /dev/hello_dev
cat /dev/hello_dev      # 读设备
echo "world" > /dev/hello_dev  # 写设备

# 卸载模块
sudo rmmod hello_char

完整操作流程的预期输出:

# insmod 后 dmesg
[  xxx] hello_char: major=244 minor=0
[  xxx] hello_char: module loaded

# cat /dev/hello_dev
Hello from kernel!

# echo world > /dev/hello_dev 后,再 cat
# cat /dev/hello_dev
world

注意 echo 写入时自带一个换行符,驱动把它连同内容一起存进缓冲区,因此 cat 读回的就是 world 加换行;写操作会把缓冲区里的旧内容一并覆盖。

5.5 内核日志

本驱动使用 pr_info()pr_err() 输出日志,这些消息通过 printk() 写入内核环形缓冲区。使用 dmesg 命令查看:

dmesg | grep hello_char
dmesg -w   # 实时监控

如果希望消息同时出现在控制台(而不只是 dmesg),可以临时调低控制台日志级别。/proc/sys/kernel/printk 含四个值,第一个是控制台日志级别,默认通常为 4(只打印 KERN_WARNING 及以上)。pr_info 的级别是 6,需要临时放宽:

sudo sh -c 'echo 8 > /proc/sys/kernel/printk'   # 8 = 全部打印到控制台

调试完建议恢复默认值。注意这只是运行时开关,重启即失效。


§6 内核开发流程与社区参与

6.1 内核开发模型

Linux 内核采用基于邮件列表的分散式开发模型,而非 GitHub PR 式的集中评审流程。核心工作流如下:

开发者 → 本地 Git → 生成 Patch → 发送至邮件列表
                ↓
          维护者 Review(通过邮件)
                ↓
          通过 → 进入-next 分支
                ↓
          Linus 拉取 → 合并至主线

Linus Torvalds 负责主线仓库(torvalds/linux)的最终合并。子系统维护者(如 MM 子系统、Net 子系统)各自维护分支,通过邮件与开发者沟通,最终各分支汇聚到 Linus 处。

6.2 提交规范

内核对提交信息有严格要求。一个合法的提交信息格式如下:

subsystem: brief one-line summary (up to 70 chars)

Detailed explanation here. This can span multiple lines but should
keep each line under 75 characters. Explain WHAT was changed and
WHY this change was made.

Signed-off-by: Your Name <your@email.com>

核心规范

  • 第一行:subsystem: description,不超过 70 字符
  • 正文解释动机和影响,描述"为什么"而非"做了什么"
  • 每行不超过 75 字符
  • 必须包含 Signed-off-by:(表示开发者同意 Developer Certificate of Origin,DCO)

DCO(Developer Certificate of Origin)是参与内核的准入门槛,签署 DCO 表示你有权贡献该代码并愿意在 GPLv2 下开源。

6.3 工具链与脚本

内核提供了完善的脚本工具链:

命令用途
scripts/checkpatch.pl检查提交信息格式
scripts/get_maintainer.pl查询代码对应的维护者和邮件列表
git send-email通过 Git 发送补丁到邮件列表
scripts/kernel-doc从源码注释生成 API 文档
make htmldocs生成内核文档 HTML

使用示例:

# 检查补丁是否符合规范
./scripts/checkpatch.pl --strict commit-file.patch

# 查找补丁应该发给谁
./scripts/get_maintainer.pl path/to/changed/file.c

# 发送补丁
git send-email --to <maintainer@example.com> commit-file.patch

6.4 测试框架

内核测试分两层:KUnit 单元测试跑在内核内部,kselftest 自测以用户空间进程方式运行。

KUnit 从 5.5 起内置,面向内核内部单元测试。测试文件以 _kunit.c 结尾、放在被测代码旁(如 lib/list-test.c),输出机器可解析的 TAP 格式。官方封装脚本会把内核构建成 User Mode Linux(UML,一种把内核编译成普通可执行文件的架构),在宿主机上直接启动并解析结果,无需真实硬件或虚拟机:

./tools/testing/kunit/kunit.py run

想手动走一遍 UML 流程也可以:

make ARCH=um defconfig
make ARCH=um -j$(nproc)
./linux        # UML 内核就是普通可执行文件,运行后输出测试结果

kselftest 覆盖系统调用、文件系统、网络、调度等子系统,测试编译成用户空间程序运行:

make -C tools/testing/selftests     # 构建全部自测
make kselftest                      # 构建并运行(部分测试需要 root)
make TARGETS="mm timers" kselftest  # 只构建并运行指定子系统

6.5 如何找到第一个贡献点

对于新人,以下是低门槛的贡献方向:

  1. 修复编译警告 — 内核代码库庞大,编译时总会有些警告。找到自己熟悉子系统的警告并修复,是最简单有效的起步。
  2. 文档改进Documentation/ 目录中很多描述已经过时,帮助更新文档是纯粹的文字工作,不需要硬件环境。
  3. testsuite 扩展 — 为现有测试添加新测试用例,是理解代码库的好方法。
  4. 回复邮件列表中的简单问题 — 社区需要帮助者回答问题。

无论哪种方式,第一步永远是阅读相关子系统的 MAINTAINERS 文件,找到负责维护者,再订阅对应的邮件列表。


§7 进阶主题

7.1 Rust 融入内核

Linux 6.1(2022 年底)正式引入 Rust 支持,这是内核社区近十年最重大的技术变化。2025 年 12 月的维护者峰会上,Rust 被从实验特性转为内核核心语言,社区对其定位是:现有约 3700 万行 C 代码不重写,新写的驱动和子系统优先考虑 Rust。Rust 的引入目标是:

  • 内存安全:在驱动层面减少 use-after-free、空指针解引用等常见 bug
  • 类型安全:通过类型系统约束 API 使用错误
  • 并发安全:编译期减少数据竞争

Rust 驱动示例(samples/rust/)展示了如何用 Rust 编写平台驱动和字符设备。目前 Rust 代码约 12 万行,占比不到 0.5%,但增长很快,Nova GPU 驱动、Apple AGX 驱动等新项目都选择了 Rust。对熟悉 Rust 的工程师,这是切入内核开发的一条新赛道,社区也在持续招募 Rust 驱动作者。

7.2 BPF(Berkeley Packet Filter)

BPF 是近年来 Linux 最强大的动态追踪工具。通过 llvm/clang 编译的 BPF 程序可以在内核运行时动态注入,监控任意函数调用、追踪性能瓶颈,而无需重新编译内核或重启服务:

# 安装 bpftool
sudo apt install bpftool   # Debian/Ubuntu

# 查看当前加载的 BPF 程序
bpftool prog list
bpftool map list

# 示例:跟踪 openat 系统调用次数
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm]++; }'

BPF 的完整生态(bcc、libbpf、bpftrace)是每个内核工程师值得深入的方向。

7.3 调试技术

内核调试的主要手段:

  • printk / pr_debug:最朴素的调试方式,通过日志输出状态
  • WARN_ON() / BUG_ON():触发 panic 用于捕获严重错误
  • kgdb:内核级 GDB 调试,需要串口或网络连接
  • kprobe:动态追踪任意内核函数调用
  • ftrace:函数级追踪框架,/sys/kernel/debug/tracing/ 提供接口
  • crash:分析 vmcore 转储文件
# 启用 ftrace(需要 root)
echo function > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace

§8 常见问题与注意事项

问题一:编译时间过长怎么办?

使用 ccache 缓存编译结果:

sudo apt install ccache
export CCACHE_DIR=/tmp/ccache
make -j$(nproc) CC="ccache gcc"

问题二:模块加载失败,提示 “Unknown symbol in module”

运行 dmesg | tail 查看具体缺失的符号,再通过 grep 在内核源码中找到提供该符号的模块,调查是否依赖关系配置错误。

问题三:QEMU 模拟运行测试

无需真实硬件,可以用 QEMU 运行自定义内核:

# 编译 x86_64 架构内核(需要在 x86_64 主机上)
make -j$(nproc) bzImage

# 用 QEMU 运行(需要先准备一个 initramfs,见下方说明)
qemu-system-x86_64 -kernel arch/x86/boot/bzImage \
    -initrd /path/to/initramfs.img \
    -nographic -append "console=ttyS0"

注意:内核启动后必须能找到根文件系统,否则会以 VFS: Unable to mount root fs panic 收场——这不是内核坏了,而是没有给它根。最简单的方式是准备一个最小 initramfs(例如用 busybox 制作,或用发行版的 /boot/initrd.img-$(uname -r) 临时顶替)。只想验证启动流程时,用发行版自带的 initramfs 就够:

qemu-system-x86_64 -kernel arch/x86/boot/bzImage \
    -initrd /boot/initrd.img-$(uname -r) \
    -nographic -append "console=ttyS0"

注意事项

  • 不要在 master 分支上直接开发:创建一个自己的分支 my-dev-branch,在独立分支上工作
  • 保持与主线同步:定期 git fetch origin && git rebase origin/master,避免合并债务
  • 从浅到深:不要试图一次性理解整个内核,从一个子系统出发,吃透再换方向
  • 邮件列表是正式场合:遵守社区规范,有理有据,不要人身攻击

§9 动手练习

为了把本文真正学扎实,建议你完成下面三组练习。

9.1 理解型练习

回答下面三个问题:

  1. 为什么宏内核架构的性能高,但稳定性风险也高?
  2. 为什么内核社区使用邮件列表而不是 GitHub PR?
  3. 为什么提交信息要解释"为什么"而不是"做了什么"?

如果你能把这三个问题讲清楚,说明你已经抓住了内核开发的核心思想。

9.2 应用型练习

尝试完成以下操作:

  1. 克隆 Linux 内核源码仓库(如果磁盘空间允许)
  2. 使用 make localmodconfig 生成最小配置
  3. 编译内核并在虚拟机或 QEMU 中启动
  4. 编写并加载本文的 hello_char.c 驱动
  5. 使用 ftrace 追踪一个系统调用的执行路径

9.3 社区参与练习

  1. 订阅一个内核子系统的邮件列表(如 linux-kernel@vger.kernel.org
  2. 阅读最近一周的邮件,了解社区讨论的技术问题
  3. 找到一个文档错误或过时的描述,准备一个补丁
  4. 使用 scripts/checkpatch.pl 检查你的补丁格式
  5. 使用 git send-email 发送你的第一个补丁(可以先发到自己的邮箱测试)

§10 自测清单

在关闭本文前,检查你是否已经能回答下面这些问题:

  • 我知道内核源码的主要目录结构
  • 我知道内核空间与用户空间的区别
  • 我知道如何配置、编译和安装自定义内核
  • 我知道如何编写一个简单的字符设备驱动
  • 我知道内核社区的开发流程和提交规范
  • 我知道如何找到适合自己的第一个贡献点
  • 我知道 Rust 融入内核的现状和意义

如果以上 7 项你都能确认,说明你已经掌握了 Linux 内核开发的基础知识。


§11 进阶路径

如果你准备继续深入,建议按这个顺序进阶:

  1. 子系统深入:选择一个子系统(如网络、内存管理、文件系统)深入学习
  2. 调试技术:掌握 ftracekprobebpftrace 等动态追踪工具
  3. 性能优化:学习内核性能分析方法(perf、flamegraph)
  4. Rust 驱动:尝试用 Rust 编写内核驱动
  5. 社区贡献:持续参与邮件列表讨论,提交补丁

总结

Linux 内核是开源世界里技术深度最深、参与门槛最高、影响力最广的项目之一。本文从仓库结构出发,依次覆盖了核心架构、编译构建、驱动编写实战、开发流程与社区参与路径,以及 Rust 融合、BPF 等进阶方向。

建议只有两个字:动手。克隆仓库、编译内核、写一个字符设备、给内核邮件列表发第一个补丁——这是真正进入内核开发的唯一路径。文档和书籍是地图,真正的旅程从你开始编译第一个内核开始。


本文由钳岳星君撰写,更新于 2026-06-27。

参与讨论

使用 GitHub 登录。欢迎补充事实、异议与实践。