kernel源码
333.3.6kernel源码
#!/bin/bash
# ↑ 指定由 Bash 解释器执行课程配套的内核构建脚本。
export ARCH=arm
# ↑ 把目标体系结构设为 ARM,并导出给 make 及其子进程。
#export CROSS_COMPILE=/usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-none-linux-gnueabi-
# ↑ 这是原脚本中已经被“#”注释的旧工具链前缀,不会执行。
export CROSS_COMPILE=arm-linux-gnueabihf-
# ↑ 指定当前使用的交叉工具链前缀;末尾的连字符属于前缀本身。
#make mrproper # means CLEAN
# ↑ 彻底清理命令在原脚本中已被注释,因此每次执行 create.sh 时并不会自动清理源码树。
make imx_v7_defconfig
# ↑ 以该 NXP/Freescale ARMv7 厂商内核提供的默认配置生成本次构建使用的 .config。
#if [ "$1" = "nand" ]
# ↑ 从这里开始的介质判断整体已被注释,只是原作者保留的旧方案。
#then
# ↑ 这是已注释条件分支的 then,不会执行。
# cp arch/arm/boot/dts/imx6ul-14x14-evk_nand.dts arch/arm/boot/dts/imx6ul-14x14-evk.dts
# ↑ 旧方案原本会在传入 nand 时,用 NAND 版 DTS 覆盖公共 DTS;当前不会执行。
#else
# ↑ 这是已注释条件分支的 else,不会执行。
# cp arch/arm/boot/dts/imx6ul-14x14-evk_emmc.dts arch/arm/boot/dts/imx6ul-14x14-evk.dts
# ↑ 旧方案原本会在其他情况下复制 eMMC 版 DTS;当前不会执行。
#fi
# ↑ 这是已注释条件分支的结束标记,不会执行。
make uImage LOADADDR=0x10008000 -j8
# ↑ 生成 legacy uImage,把加载/入口地址写为 0x10008000,并允许最多 8 个构建任务并行;视频随后把 -j8 改为 -j16 后实际执行。
make modules
# ↑ 单独构建配置为模块的目标,通常生成可加载内核模块 .ko;这一行没有沿用前一行的 -j8/-j16。
make topeet_emmc_4_3.dtb
# ↑ 编译 eMMC 板型的 4.3 英寸显示配置设备树。
make topeet_emmc_5_0.dtb
# ↑ 编译 eMMC 板型的 5.0 英寸显示配置设备树。
make topeet_emmc_7_0.dtb
# ↑ 编译 eMMC 板型的 7.0 英寸显示配置设备树。
make topeet_emmc_1024x600.dtb
# ↑ 编译 eMMC 板型的 1024×600 显示配置设备树。
make topeet_emmc_9_7.dtb
# ↑ 编译 eMMC 板型的 9.7 英寸显示配置设备树。
make topeet_emmc_10_1.dtb
# ↑ 编译 eMMC 板型的 10.1 英寸显示配置设备树。
make topeet_emmc_hdmi.dtb
# ↑ 编译 eMMC 板型的 HDMI 显示配置设备树。
make topeet_nand_4_3.dtb
# ↑ 编译 NAND 板型的 4.3 英寸显示配置设备树。
make topeet_nand_5_0.dtb
# ↑ 编译 NAND 板型的 5.0 英寸显示配置设备树。
make topeet_nand_7_0.dtb
# ↑ 编译 NAND 板型的 7.0 英寸显示配置设备树。
make topeet_nand_1024x600.dtb
# ↑ 编译 NAND 板型的 1024×600 显示配置设备树。
make topeet_nand_9_7.dtb
# ↑ 编译 NAND 板型的 9.7 英寸显示配置设备树。
make topeet_nand_10_1.dtb
# ↑ 编译 NAND 板型的 10.1 英寸显示配置设备树。
make topeet_nand_hdmi.dtb
# ↑ 编译 NAND 板型的 HDMI 显示配置设备树。
cd ./arch/arm/boot/dts/
# ↑ 进入设备树输出目录,为最后的厂商定制打包步骤准备相对路径。
./create_dtb imx6ul_topeet_nand.dtb topeet_nand_4_3.dtb topeet_nand_7_0.dtb topeet_nand_10_1.dtb topeet_nand_1024x600.dtb topeet_nand_5_0.dtb topeet_nand_9_7.dtb topeet_nand_hdmi.dtb
# ↑ 调用课程包自带的二进制工具,把七个 NAND 显示版本作为输入,生成厂商使用的汇总文件 imx6ul_topeet_nand.dtb;其内部格式不应仅凭命令名臆测。
本节围绕课程配套的 Linux 4.1.15 NXP/Freescale 厂商内核展开:先在 Ubuntu 中解压、交叉编译,再认识内核目录、三种 ARM 内核镜像、设备树产物,最后用 Source Insight 4.0 建立只读索引工程。顶部代码是压缩包中真实的 create.sh;视频先显示原始 -j8,随后用 Vim 改成 -j16 并保存执行,其余源码行未改。
需要先建立一个准确概念:内核构建不是“把压缩包里的所有源码编成一个文件”。Kconfig 选择的内建对象会参与链接 vmlinux,配置为模块的对象另行生成 .ko,设备树又单独编译成 .dtb;Image、zImage、uImage 是从内核链接结果继续派生的不同启动镜像。
一、内核
1.1 编译过程
课程中的操作链可归纳为:
建立目录 → 传输并解压压缩包 → 检查/修改 create.sh → 生成配置 → 构建 uImage 与 modules → 构建 14 个板级 DTB → 生成 NAND 汇总 DTB
本地课程包 linux-imx-rel_imx_4.1.15_2.1.0_ga_20200903.tar.gz 的实际大小为 261,322,038 B(249.22 MiB)。顶层 Makefile 中的版本字段为 4.1.15。压缩包还带有 .o、.cmd、Image、zImage、uImage 等既有构建产物,因此它不是一份完全洁净的上游源码树;“压缩包比 U-Boot 大”既反映 Linux 支持范围更广,也受到这些已有产物影响,不能只凭压缩包大小衡量源码复杂度。
1.1.1 拷⻉源代码
- 视频在
~/Linux下新建kernel,让它与之前的uboot目录并列。这只是课程采用的工作区组织方式,不是内核构建系统规定的固定路径。 - 用 FileZilla 通过虚拟机 IP、用户名和密码传输压缩包时,主机与虚拟机必须网络可达;IP 和账号应以自己的虚拟机配置为准,不能照抄视频。
- 推荐保留一份未改动的原压缩包或洁净副本。源码树既用于编译又用于 Windows 索引时,分开保留“Linux 构建副本”和“Windows 阅读副本”,可避免权限、符号链接和生成文件相互干扰。
1.1.2 解压编译生成文件
视频使用 tar -xvf <压缩包名> 解包:-x 表示提取,-v 显示文件名,-f 表示下一参数是归档文件。由于文件很多,详细输出和解压时间都明显长于 U-Boot。
解压后先用 ls 确认新目录,再进入 linux-imx-rel_imx_4.1.15_2.1.0_ga。若要确认版本,不要只看目录名,可检查顶层 Makefile 的 VERSION、PATCHLEVEL、SUBLEVEL;本包组合结果为 4.1.15。
这里的“解压”和“编译”是两个阶段:tar 只还原归档内容,不会运行 Makefile;真正的配置与构建由后续 ./create.sh 中的一系列 make 命令完成。
1.1.3 编译
先执行 cat create.sh 审核脚本,再决定是否运行。该脚本的关键行为如下:
| 阶段 | 命令 | 准确含义 |
|---|---|---|
| 目标平台 | export ARCH=arm | 选择 32 位 ARM 架构构建规则 |
| 工具链 | export CROSS_COMPILE=arm-linux-gnueabihf- | 让构建系统组合出 arm-linux-gnueabihf-gcc、ld、objcopy 等工具名 |
| 配置 | make imx_v7_defconfig | 生成/更新本次构建使用的 .config |
| 内核镜像 | make uImage LOADADDR=0x10008000 -j8 | 生成 legacy uImage,并设置构建并行度与 uImage 头中的地址 |
| 模块 | make modules | 构建被配置为 m 的可加载模块 |
| 设备树 | 14 次 make *.dtb | 分别生成 eMMC/NAND 与不同显示配置的 DTB |
| 厂商汇总 | create_dtb ... | 由课程包的定制工具生成 imx6ul_topeet_nand.dtb |
-j8 的 8 是 GNU Make 的 job slots(并行任务槽),不是“启动 8 个 CPU 核”。它限制可同时执行的 recipe 数量;CPU 核/线程数、内存、磁盘性能和虚拟机负载共同决定合适值。视频虚拟机显示 16 个逻辑处理器,于是把这一个命令改为 -j16:
make uImage LOADADDR=0x10008000 -j16
这项修改只影响 make uImage。脚本后面的 make modules 和 14 条 DTB 命令没有 -j16,而且彼此仍按 Shell 行序串行调用。GNU Make 官方手册将 -jN 明确定义为同时运行的 job 数量。
还有三点排错时很重要:
make mrproper前有#,实际不会执行;本次构建可能复用依赖仍有效的旧产物。- 脚本没有
set -e,命令之间也没有用&&串联。中间某个make失败后,Bash 仍可能继续执行后续命令,所以“脚本跑到结尾”不等于全部成功。 LOADADDR=0x10008000在本包最终展开为mkimage的-a与-e参数,即写入 legacy uImage 的加载地址和入口地址;它不是源文件路径,也不是-j参数的一部分。
判断构建结果时应向上检查首次出现的 error,并核对 arch/arm/boot/uImage、所需 .ko 以及目标板对应的 .dtb 是否重新生成,而不能只看终端最后一行。
1.2 内核源码
课程通过 Samba 映射目录查看编译中的源码树。Samba、FileZilla 都只是访问/传输方式,不参与编译。构建仍在 Linux 文件系统中由交叉工具链完成。
内核的 Kbuild/Kconfig 只选择当前配置需要的代码:Linux 官方 Kbuild 文档说明 obj-y 中的对象会被归入内建目标并最终链接进 vmlinux,而模块目标与内建目标分开。因此原笔记“所有源码文件最终编译成一个文件”不准确。
1.2.1 内核源码目录结构
1.2.1.1 文件夹分析
课程画面重点展示了下列顶层目录:
| 目录 | 主要职责 | 阅读提示 |
|---|---|---|
arch/ | 各 CPU 架构的启动、异常、内存布局及板级相关代码 | 本课程重点路径为 arch/arm/;不是所有架构都会进入本次构建 |
block/ | 通用块 I/O 层、请求调度等 | 与具体 eMMC/NAND 控制器驱动不是同一层 |
drivers/ | 设备驱动及总线/子系统实现 | GPIO、I2C、网络、MMC、MTD、USB 等驱动的主要入口 |
fs/ | VFS 与各文件系统实现 | 既包含通用文件系统层,也包含 ext4、NFS 等具体实现 |
include/ | 公共内核头文件和架构相关头文件入口 | 用于查结构体、宏、接口声明,不能只凭声明判断实现位置 |
init/ | 内核早期初始化和 start_kernel() 所在区域 | 适合建立启动主线,但不等于全部启动代码都在此目录 |
ipc/ | System V IPC、POSIX 消息队列等 IPC 子系统代码 | 管道等其他进程通信机制可能位于别处,不能把所有 IPC 都归到这里 |
net/ | 网络协议栈和网络核心子系统 | TCP/IP、socket、协议族等主要在此,不等同于网卡硬件驱动 |
drivers/ 对驱动开发最直接,但不应孤立阅读。例如一个 I2C 外设通常同时涉及设备树节点、I2C 核心、控制器驱动、具体 client 驱动以及相关头文件。课程最后用 Source Insight 打开的 drivers/i2c/i2c-core.c,正是 Linux 4.1.15 中 I2C 核心层文件。
1.2.1.2 网络协议相关目录
要区分两个容易混淆的路径:
drivers/net/:面向以太网、无线网等硬件设备的驱动,负责和控制器/PHY 等硬件交互。net/:网络协议栈与通用网络子系统,例如 socket 层、IPv4/IPv6 和各种协议实现。
排查“网卡不通”时,问题可能落在设备树、时钟/引脚、MAC/PHY 驱动,也可能落在 IP 配置和协议栈,不能只搜索 net/。学习大型内核源码也不需要记住全部目录;更有效的方法是以一个现象为起点,沿“设备树 → 总线/设备模型 → 驱动 probe → 子系统接口”逐层追踪。
1.2.1.3 应用案例
课程编译后的两大类启动产物是 内核镜像 与 设备树二进制,此外脚本还明确构建了内核模块。
本课程压缩包中已有产物的精确值为:
| 产物 | 路径 | 字节数 | 含义 |
|---|---|---|---|
Image | arch/arm/boot/Image | 13,754,368 B | 去除 ELF 元数据后的未压缩 ARM 内核二进制 |
zImage | arch/arm/boot/zImage | 7,186,568 B | 可自解压启动的压缩内核镜像 |
uImage | arch/arm/boot/uImage | 7,186,632 B | 本课程 U-Boot 使用的 legacy 镜像:64 B 头部 + 同一份 zImage 载荷 |
| 单板 DTB | arch/arm/boot/dts/*.dtb | 约 39~41 KiB/个 | 某一存储介质与显示配置的硬件描述 |
| NAND 汇总文件 | arch/arm/boot/dts/imx6ul_topeet_nand.dtb | 358,400 B | 课程厂商工具由七个 NAND 显示版本生成的汇总产物 |
视频资源管理器显示的舍入值为 Image 13,432 KB、zImage 7,019 KB、uImage 7,019 KB。其中 13,432 KiB 约为 13.12 MiB,不是 1.3 MiB;原转录中的小数点属于口误/OCR 错误。
设备树描述的是不可自动枚举或需要板级参数的硬件信息,例如内存、总线、GPIO、中断、时钟以及板载外设。.dts 是板级源文件,.dtsi 通常承载可复用的 SoC/公共片段,.dtb 是供引导程序和内核使用的扁平化二进制。设备树并不会自动提供驱动,它只是让内核按 compatible 等属性匹配并配置已有驱动;可参见 Linux 官方设备树使用模型。
本脚本同时生成 eMMC 与 NAND 各 7 个显示版本,并不表示目标板启动时要加载全部 DTB。实际部署必须按板卡存储介质和屏幕型号选择与之匹配的内核、设备树及 U-Boot 启动参数;错用 DTB 常见后果是显示、存储、网络或 GPIO 外设无法正常初始化。
1.2.2 Image与zImage的区别
三种文件的派生关系是:
vmlinux → objcopy → Image → ARM 压缩/自解压构建 → zImage → mkimage 添加 legacy 头 → uImage
Image不是源码,也不是完整 ELF 调试文件;它是从vmlinux导出的未压缩裸内核二进制。zImage是 ARM 可自解压启动镜像,不应把它简单理解为“把 Image 用普通 gzip 压成.gz文件”。引导程序跳入其入口后,前端解压代码会展开内核并继续启动。- 本课程的
uImage是 legacy U-Boot image,不是名称相近的 FIT 新格式。其头部记录魔数、时间、载荷大小、加载/入口地址、CRC、架构、操作系统、压缩类型和名称等信息。
对课程包做二进制校核可得到:uImage - zImage = 64 B,uImage 前 4 字节魔数为 0x27051956,剥掉前 64 B 后的载荷与 zImage 逐字节一致。这比仅凭十六进制比较器“看到前面多了一段”更严格,也解释了 Windows 资源管理器为何把二者都舍入显示为 7,019 KB。
课程板的构建和启动脚本沿用 legacy uImage,所以学习时要按这一版本操作;对新项目则不能据此推导“U-Boot 永远只能启动 uImage”。U-Boot 还能启动其他格式,且其当前官方文档已建议新设计优先使用可签名、可组合多镜像的 FIT,并把 legacy 格式定位为旧板支持,参见 U-Boot bootm 官方文档。
1.3 源代码查看软件
Source Insight 是 Windows 上的商业源码阅读与符号索引工具,适合建立函数、类型、宏和调用关系索引。课程选择它是因为内核文件量大、传统纯文本搜索不够方便;这不意味着 VS Code 必然卡顿,也不意味着 Source Insight 是唯一选择。实际性能取决于索引范围、插件、磁盘、内存和工程配置。
可以按环境选择:
- 继续复现课程:Source Insight 4.0。
- 跨平台且希望接近编译语义:VS Code/其他编辑器配合 clangd、
compile_commands.json。 - Linux 终端:
rg、cscope、ctags 等组合。
无论工具如何,源码版本必须与实际编译/运行的内核一致,否则跳转到同名函数也可能得到错误结论。
1.4 源码查看软件Source Insight安装
视频演示了第三方破解补丁、Keygen,并要求关闭杀毒软件。这一做法既不属于分析 Linux 源码的必要步骤,也有明显的安全与授权风险,不要照做。本文不保留破解执行流程。
安全做法是从 Source Insight 官方试用页下载安装包,选择官方 Trial,或使用合法购买的许可证。官方目前为 Source Insight 4 提供最长 30 天、无需信用卡的全功能试用。若不购买,也可改用前述开源工具。
不要为了运行来源不明的程序关闭 Windows Defender、添加目录排除项或放行告警。Microsoft 明确指出,实时保护关闭时打开或下载的文件不会接受实时扫描;其安全说明还特别指出 Keygen 经常与恶意软件同时出现,参见 Windows 病毒与威胁防护说明和 恶意软件感染途径。
1.5 使用源码查看软件Source Insight
1.5.1 内核源码的拷⻉与解压
视频先在 Linux 中解压/编译,再把解压后的目录复制到 Windows 本地盘供 Source Insight 建索引。这样做的重点是 Windows 副本只用于阅读:Linux 源码归档可能包含大小写敏感文件名、符号链接、可执行位和 Unix 权限,直接在 Windows 工具中解包可能无法等价保留这些语义。不能把 Windows 阅读副本再当成可靠的 Linux 构建树。
更稳妥的流程是:
- 在 Linux 文件系统中解压并保留正式构建树。
- 如只想减少阅读副本体积,可在可丢弃的副本中执行
make clean,或直接从洁净源码重新复制;不要先破坏唯一的可复现构建目录。 - 通过 FileZilla、Samba 或共享目录把源码复制到 Windows 本地 SSD。
- 在 Source Insight 中只对 Windows 副本建立索引,实际编译仍回到 Linux 构建树。
make clean 会删除大量 .o、.ko、生成镜像等构建产物,但通常保留 .config;make mrproper 才进一步删除包括 .config 在内的生成配置。因此“clean 后等于原始压缩包”也不准确,尤其本课程压缩包自身就预带构建产物。若直接索引 Samba 网络路径,功能上可以工作,但海量小文件的目录遍历和随机读取通常比本地盘慢。
1.5.2 Source Insight的安装与启动
安装官方版本后首次启动,按提示选择 Begin a Trial 或输入合法许可证。软件正常进入主界面即可;“出现已破解界面”不是正确的安装完成标准,也不应作为课程复现目标。
建立工程前先确认 Windows 阅读副本完整,路径尽量短且稳定。后续移动源码目录会使工程索引中的路径失效,需要重新定位或重建工程。
1.5.3 新建Source Insight工程
视频中的实际步骤是:
- 打开
Project → New Project。 - 工程名输入
kernel_class。 - “Where do you want to store the project data files?” 保持在本地工程数据目录,例如
C:\Users\Administrator\Documents\Source Insight 4.0\Projects\kernel_class。 - 点击
OK创建空工程。
这里要区分 工程数据目录 与 源码目录:前者保存 Source Insight 自己的数据库和配置,后者是下一步要添加的 Linux 内核树。视频并没有把这两个目录设置成同一路径;把工程数据库与源码分开也更便于删除或重建索引。
1.5.4 添加工程文件
在 Add and Remove Project Files 窗口中定位到 Windows 本地的 linux-imx-rel_imx_4.1.15_2.1.0_ga 根目录,然后:
- 点击
Add All。 - 弹窗中保留
Include top level sub-directories,并勾选Recursively add lower sub-directories;第二项决定是否继续递归到更深层目录,是内核工程不能遗漏的关键项。 - 点击
OK,等待添加与符号解析完成。 - 视频提示将加入 45,543 个文件;这个数量只对应当时那份已解压且带构建产物的课程目录,不是所有 Linux 4.1.15 源码树都固定有此数量。
- 返回文件窗口后点击
Close,不要把转录中的“Close OK”当作一个按钮名。
若只为阅读源代码,可排除 .o、.ko、镜像、临时命令文件等生成物以缩小索引;但复现视频时保留全部已知文件类型,会更接近其 45,543 个文件的结果。
二、源码的加载过程
“加载”在这里指把文件路径加入 Source Insight 工程并建立索引,不是把内核加载到开发板内存。两种“加载”必须分清:
- Source Insight 加载源码:主机侧读取文件、解析符号、建立数据库。
- U-Boot 加载内核:目标板把
uImage/zImage、DTB 等放入内存并跳转启动。
添加大量文件后,Source Insight 还需要扫描和解析;在解析完成前,文件名搜索可能可用,但符号跳转和交叉引用可能不完整。应等待后台状态稳定,再验证工程:能否在 Project Files 中找到已知文件、能否打开 drivers/i2c/i2c-core.c、能否从符号列表跳转到定义。
三、源码的使用方法
课程最后的真实示例不是转录中的“FNC”,而是搜索 i2c:
- 切到右侧
Project Files标签;其输入框提示快捷键为Ctrl+O。 - 输入
i2c后,列表显示文件名以i2c开头或相匹配的项目。 - 选中并打开
i2c-core.c,窗口标题显示路径为drivers\i2c。 - 左侧
Symbol Name窗格列出当前文件的 include、宏、结构体和函数等符号,界面提示快捷键为Alt+L。
文件名检索适合“知道大概叫什么”;符号跳转适合“知道函数/类型/宏名”;全文检索适合“只知道字符串或日志”。阅读驱动时不要停在单个函数:先看调用者和被调用者,再回查 Kconfig/Makefile 是否让该文件进入当前构建,并结合设备树 compatible 判断它是否会在目标板上匹配。
四、文件夹的使用方法
Folders 标签按目录层次浏览已加入工程的文件,操作观感类似资源管理器,但它展示的是 Source Insight 工程索引,不等同于实时、完整的磁盘文件系统。新建、删除或从 Linux 重新复制文件后,必要时要同步工程或重建索引。
对本课程内核,建议把目录浏览与问题链结合起来:
- 启动主线:
arch/arm/、init/。 - I2C 问题:板级 DTS/DTSI →
drivers/i2c/→ 具体外设驱动。 - 网卡问题:DTS →
drivers/net/→ PHY/MDIO →net/协议栈。 - 存储问题:DTS → eMMC 的 MMC 子系统或 NAND 的 MTD 子系统 → 文件系统层。
工具的价值是缩短定位路径,不能代替版本核对、配置核对和运行日志。最可靠的学习闭环仍是:找到源码 → 确认本配置确实编译它 → 在目标板日志或调试信息中验证它确实执行。