2. ARM平台与Linux内核基础

好,咱们正式开始动手。这一章我带你搞定内核源码的获取、版本管理,以及最基础的配置体系。说白了,就是先把“食材”准备好,再搞懂“厨房”的结构。

2.1 Linux内核版本管理——别选错了分支

Linux内核版本号,看着简单,其实有门道。比如 5.10.168:

  • 5 —— 主版本号,大改版才变
  • 10 —— 次版本号,以前偶数表示稳定版,奇数表示开发版。现在这个规则弱化了,但很多老项目还在用
  • 168 —— 补丁级别,也就是小修小补

我个人习惯,做产品选型时,优先选 LTS(长期支持) 版本。比如 5.10、5.15、6.1 这些。为什么?因为社区会维护好几年,你不需要频繁跟着主线升级。

我的经验: 有一次我图新鲜,用了当时最新的非LTS内核做项目。结果半年后,社区停止维护,遇到一个安全漏洞只能自己打补丁。从那以后,我选内核只认LTS。

怎么看一个版本是不是LTS?去 kernel.org 看,LTS版本会有黄色标签。或者用命令:

git tag -l 'v5.10.*' | tail -5

2.2 获取源码:git clone vs tarball下载

两种方式,各有各的适用场景。我列个表,你一看就明白:

方式 命令/操作 适用场景 大小
git clone git clone --depth=1 git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git 需要跟踪历史、切换分支、打补丁 ~2GB(完整) / ~200MB(浅克隆)
tarball下载 wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.168.tar.xz 只需要固定版本,不关心历史 ~150MB(压缩) / ~1GB(解压后)

git clone 的坑: 完整克隆整个内核仓库,大概2GB,网速慢的话能等一两个小时。我建议用 --depth=1 做浅克隆,只拉取最新代码。如果你后面需要切换分支,再用 git fetch --unshallow 补全历史。

注意: 浅克隆(--depth=1)无法直接切换到一个不在当前分支历史中的标签。如果你需要频繁切换版本,建议做完整克隆,或者用tarball。

tarball 的坑: 下载完记得校验签名。内核社区提供 .tar.sign 文件,用 gpg --verify 验证。我曾经遇到过下载的包被中间人篡改,编译出来的内核有后门。嗯,从那以后,我每次下载都校验。

# 下载并校验
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.168.tar.xz
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.168.tar.sign
xz -cd linux-5.10.168.tar.xz | gpg --verify linux-5.10.168.tar.sign -

2.3 内核源码目录结构解析——别迷路

解压完源码,进去一看,几十个目录。别慌,核心目录其实就这几个:

Linux内核源码顶层目录结构 linux-5.10.168/ arch/ drivers/ fs/ include/ kernel/ arch/arm/ drivers/net/ fs/ext4/ include/linux/ 各目录作用: • arch/ —— 体系结构相关代码。ARM平台主要看 arch/arm/ 和 arch/arm64/ • drivers/ —— 设备驱动。我们做驱动移植主要在这里折腾 • fs/ —— 文件系统。ext4、vfat、procfs 都在这里 • include/ —— 头文件。内核API定义,驱动开发经常引用 • kernel/ —— 核心调度、进程管理、中断等 • mm/ —— 内存管理 • net/ —— 网络协议栈 • scripts/ —— 编译脚本、配置工具

做ARM平台移植,你打交道最多的就是 arch/arm/ 和 arch/arm64/。里面按芯片厂商分目录:mach-* 是板级代码,plat-* 是平台通用代码。

关键点: 内核裁剪,说白了就是删掉 drivers/ 里你用不到的驱动,以及 arch/ 里不需要的板级支持。每删一个,镜像就小一点,启动就快一点。

2.4 Kconfig与Makefile入门——配置系统的骨架

内核的配置系统,核心就两个文件:Kconfig 和 Makefile。它们成对出现,几乎每个子目录都有。

2.4.1 Kconfig——定义“有什么可以选”

Kconfig 文件定义了配置选项。比如 drivers/net/Kconfig 里的一段:

config DM9000
    tristate "DM9000 support"
    depends on ARM || MIPS
    select CRC32
    help
      Support for DM9000 series Ethernet controllers.
      Say Y here if you have such a device.

解释一下:

  • config DM9000 —— 选项名,最终会变成 CONFIG_DM9000
  • tristate —— 三态:N(不编译)、Y(编译进内核)、M(编译成模块)
  • depends on —— 依赖条件,只有ARM或MIPS架构才显示这个选项
  • select —— 反向依赖,选了DM9000就自动选上CRC32

我个人习惯,写驱动时一定把 depends on 写清楚。不然别人在x86上配你的驱动,编译报错还找不到原因。

2.4.2 Makefile——定义“怎么编译”

同目录下的 Makefile 告诉内核怎么编译:

obj-$(CONFIG_DM9000) += dm9000.o

这行意思是:如果 CONFIG_DM9000 的值是 y,就把 dm9000.c 编译进内核;如果是 m,就编译成 dm9000.ko 模块;如果是 n,就不编译。

避坑指南: 我曾经犯过一个错——在Makefile里写 obj-y += dm9000.o,结果这个驱动不管配置选没选,都被强制编译进内核。后来查了半天才发现是少写了 $(CONFIG_...) 条件。

2.4.3 配置操作流程

搞懂了Kconfig和Makefile,配置操作就简单了:

  1. 生成默认配置: make ARCH=arm defconfig —— 生成一个基于当前架构的默认配置
  2. 菜单配置: make ARCH=arm menuconfig —— 图形化菜单,按空格切换Y/M/N
  3. 保存配置: 退出时自动保存到 .config 文件
  4. 查看最终配置: grep CONFIG_DM9000 .config

你想想看,整个配置系统就像一个巨大的开关面板。Kconfig 定义了每个开关的标签和依赖关系,Makefile 定义了每个开关控制哪盏灯。你通过 menuconfig 拨动开关,最终生成一个 .config 文件来记录所有开关状态。

注意: 不要手动编辑 .config 文件!虽然可以,但很容易引入依赖不一致的问题。比如你手动把 CONFIG_DM9000=y,但它的依赖 CONFIG_CRC32 没开,编译就会报错。用 make menuconfig 会自动处理这些依赖。