第1章:Linux内核源码结构分析

做内核移植这些年,我最大的感受就是——你得先看懂源码在哪儿,才能动手改。很多人一上来就make menuconfig,结果配了半天连文件系统都挂不上。说白了,内核源码的目录结构就是你的地图,Kconfig和Makefile就是你的工具。今天咱们就把这些基础打牢。

1.1 内核源码目录结构

先看看顶层目录。你解压完内核源码,第一眼看到的就是这些文件夹。我习惯把它们分成三类:核心代码、架构相关、工具脚本。

目录 作用 我的经验
arch/ 架构相关代码 移植时90%的修改都在这里
drivers/ 设备驱动 我踩坑最多的地方
fs/ 文件系统 根文件系统挂载常出问题
include/ 头文件 宏定义和结构体都在这里
kernel/ 核心调度、进程管理 一般不动,除非你改调度器
mm/ 内存管理 MMU相关的坑不少
net/ 网络协议栈 网卡驱动调通后看这里
scripts/ 编译脚本、配置工具 Kconfig语法不懂时翻这里

嗯,这里有个重点:arch/arm/arch/arm64/ 是我们做嵌入式移植的主战场。我刚开始做i.MX6ULL移植时,光在arch/arm/mach-imx/目录下就折腾了两周。每个SoC厂商都会在这里放自己的板级文件。

核心目录速记口诀:

arch看架构,drivers找驱动,fs管文件,include藏定义。

1.2 Kconfig与Makefile机制

这两个文件是内核配置系统的灵魂。Kconfig负责「有什么选项可以选」,Makefile负责「选了之后怎么编译」。你想想看,没有它们,内核就成了一个巨大的不可配置的怪物。

Kconfig语法速览

我见过很多新手直接复制别人的Kconfig,结果编译报错一脸懵。其实语法很简单:

config MY_DRIVER
    bool "Enable My Driver"
    depends on ARM
    select GPIO
    help
      This is a sample driver for learning.

这里要注意:depends on 是依赖关系,select 是反向选择。我曾经犯过一个错——用select强制选中了一个还没初始化的子系统,结果内核启动到一半就panic了。

Makefile的两种写法

驱动编译有两种方式:

  • 编译进内核obj-y += my_driver.o
  • 编译成模块obj-m += my_driver.o

我个人习惯在开发阶段用obj-m,这样不用每次都烧写整个内核。调试好了再改成obj-y

小技巧: 在drivers/目录下新建一个自己的驱动文件夹,比如drivers/my_drv/,然后在父目录的Kconfig和Makefile里加上对应的路径。这样不会污染原有代码,也方便后续维护。

1.3 内核配置系统(menuconfig)

make menuconfig 本质上是一个图形化的Kconfig解析器。它读取所有Kconfig文件,生成一个配置菜单。你选中的选项会写入.config文件。

我遇到过最坑的事:有一次我改了.config文件里的一个选项,但没执行make oldconfig,结果编译出来的内核死活不认我的网卡。后来才发现,手动改.config会导致依赖关系错乱。

警告: 永远不要手动编辑.config文件!用make menuconfig、make defconfig或者make savedefconfig来管理配置。否则你会陷入「改了没生效」的噩梦。

常用的配置命令:

  • make menuconfig:图形化配置
  • make defconfig:使用默认配置
  • make savedefconfig:生成最小化配置
  • make oldconfig:基于现有.config更新

1.4 内核编译流程

编译流程说白了就三步:配置、编译、安装。但每一步都有讲究。

标准编译步骤

# 第一步:配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig

# 第二步:编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

# 第三步:安装模块
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_install INSTALL_MOD_PATH=./rootfs

这里-j4是并行编译,我建议根据你的CPU核心数来设。比如8核的机器用-j8,但别超过物理核心数,否则反而会变慢。

编译流程背后的逻辑

当你执行make时,内核的顶层Makefile会做这些事情:

  1. 读取.config,确定哪些功能要编译
  2. 递归进入各个子目录,执行子Makefile
  3. 把所有的.o文件链接成vmlinux
  4. 生成压缩后的内核镜像zImageuImage
  5. 编译设备树.dtb文件

我记得第一次编译内核时,等了半小时结果报了个链接错误。后来发现是某个驱动没选对依赖。所以我的建议是:第一次编译用默认配置,先确保环境没问题,再慢慢加功能

避坑指南: 我曾经在编译时忘了指定ARCH和CROSS_COMPILE,结果用gcc编译ARM内核,生成了x86的镜像。烧到板子上当然起不来。所以这两个变量一定要写对,或者直接export到环境变量里。

知识体系总览

下面这张图是我自己总结的内核源码分析框架,你可以把它当作学习路线图:

Linux内核源码分析知识体系 目录结构 Kconfig/Makefile menuconfig 编译流程 arch/arm/ 板级文件 drivers/ 设备驱动 include/ 头文件 config / depends on obj-y / obj-m 编译 依赖关系管理 图形化配置界面 .config 生成 defconfig管理 交叉编译链 vmlinux → zImage 设备树编译 核心思路:先看懂目录 → 再理解配置 → 最后动手编译 移植工作 = 80% 配置 + 20% 代码修改

这张图把今天讲的内容串起来了。你从目录结构入手,搞清楚每个文件夹是干嘛的;然后学习Kconfig和Makefile,知道怎么添加自己的代码;接着用menuconfig配置内核;最后执行编译流程。每一步都有坑,但每一步也都有规律可循。

好了,这一章的内容就到这儿。记住:内核移植不是玄学,是工程。把基础打牢,后面的事情就顺了。


公众号:蓝海资料掘金营,微信deep3321