kernel编译脚本

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

334.3.7kernel编译脚本

#!/bin/bash
# ↑ 这是视频所讲 create.sh 的解释器行;直接执行脚本时,操作系统据此选择 /bin/bash。

export ARCH=arm
# ↑ 在脚本进程及其子进程中把目标体系结构设为 32 位 ARM。

#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
# ↑ 这一行同样被注释,所以运行脚本时不会自动清除旧配置与旧构建产物。

make imx_v7_defconfig
# ↑ 让 Kconfig 以 arch/arm/configs/imx_v7_defconfig 为默认输入,解析依赖和默认值后生成顶层 .config。

make uImage LOADADDR=0x10008000 -j16
# ↑ 这是视频工作副本中的实际行:构建 legacy uImage,设置其加载/入口地址,并允许最多 16 个任务并行。

make modules
# ↑ 构建当前 .config 中取值为 m 的内核模块;它只生成模块,不会自动安装或加载模块。

CONFIG_USB_RTL8150=m
# ↑ 这是视频在顶层 .config 中选中的真实配置:RTL8150 USB 网卡驱动选择模块方式。

obj-$(CONFIG_USB_RTL8150)	+= rtl8150.o
# ↑ 这是 drivers/net/usb/Makefile 的真实规则;上面的 m 代入后成为 obj-m += rtl8150.o,最终生成 rtl8150.ko。

本节继续分析课程配套 Linux 4.1.15 NXP/Freescale 厂商内核的 create.sh,重点不是重新背脚本,而是建立 defconfig → .config → Kbuild Makefile → 内建内核/模块 的完整关系。顶部只摘录了本节实际讲解的脚本行,以及视频逐项验证的 .config 和 drivers/net/usb/Makefile 原文;课程包中的 create.sh 原始并行参数是 -j8,上一节已在视频工作副本中改为 -j16,所以这里按本节画面保留 -j16。

最重要的构建链是:

Kconfig + imx_v7_defconfig → 生成 .config → 生成 auto.conf/配置头 → Kbuild 展开 obj-$(CONFIG_*) → obj-y 进入内建内核,obj-m 生成 .ko

一、内核源码的编译脚本

1.1 打开编译脚本

视频输入的命令是 gedit create.sh,不是转录中的“jd it”。gedit 是图形文本编辑器,适合在桌面环境中查看带行号和语法高亮的脚本;cat create.sh 只是把全文输出到终端,并不进入编辑界面。在无图形界面的服务器上,还可以使用 less create.sh 或终端编辑器查看。

打开脚本后应先分清三类行:

  • #!/bin/bash:位于文件第一行的解释器指示行。
  • export ...、make ...:会执行的命令。
  • #export ...、#make mrproper ...:普通注释,不会执行。

视频以第五行旧工具链配置为例解释 # 注释。判断一行是否生效不能只看文字内容:只要第一个非语法字符是 #,Bash 就把其余内容当注释;因此旧工具链路径与 make mrproper 都没有参与本次构建。

1.2 shell脚本开头#!/bin/bash的作用

#! 通常称为 shebang。当文件具有执行权限并以 ./create.sh 方式执行时,内核发现文件开头两个字节是 #!,便把其后的绝对路径 /bin/bash 作为解释器,再由 Bash 读取脚本。GNU Bash 的官方脚本文档也说明了这一执行机制。

课程说“第一行的 # 不是注释”是从操作系统启动脚本的角度做的简化。更严谨地说:

  • 对操作系统的脚本装载机制而言,行首 #! 用来指定解释器。
  • 脚本交给 Bash 后,这一行不会作为普通命令执行,Bash 会把它视作注释。
  • 如果显式运行 bash create.sh,解释器已经由命令指定,此时并不依赖 shebang。

还有一个对本节结尾非常关键的作用域问题:./create.sh 会在新的进程环境中执行。脚本内 export ARCH=arm 只传给脚本后续的 make 及其子进程,脚本结束后不会反向修改当前终端的父 Shell。因此稍后若直接在提示符输入 make menuconfig,不能假定 ARCH 仍然是 arm。

1.3 编译器和解释器

“编译器一次性编译、解释器逐行执行”可以帮助入门,但不是严格的技术边界。

工具本节中的角色实际过程
Bash命令语言解释器解析 Shell 语法、展开变量,然后按控制关系启动 make 等命令
GNU Make构建编排工具读取 Makefile/Kbuild 规则,判断依赖,调用编译器、链接器和其他工具
arm-linux-gnueabihf-gcc交叉编译器驱动把为 ARM 选择的 C 源码编译成目标文件,并协调汇编、链接等阶段
ld/objcopy/压缩工具/mkimage后处理工具链接 vmlinux,导出 Image,生成 zImage,再封装课程所用的 uImage

所以并不是“.c 文件被编译器直接编成 uImage”。内核要经历预处理、编译、汇编、链接和镜像后处理等多个阶段。.hex 常见于部分微控制器工具链,.elf 是一种可执行与可链接格式;它们也不能和 zImage、uImage 简单视为同一级别、同一种格式。

解释型与编译型也不是语言的绝对标签。例如 Bash 主要解析并执行命令,而 Python 实现通常会先生成字节码再由虚拟机执行。学习本节只需抓住:脚本负责组织构建步骤,交叉编译工具链负责产生目标架构代码。

1.4 内核源码

1.4.1 内核源码文件

视频中的命令与文件应准确写为:

  • 构建目标:make imx_v7_defconfig
  • 默认配置文件:arch/arm/configs/imx_v7_defconfig
  • 生成配置:源码顶层的 .config

原转录中的 iMax v7、arc/arm/config 都是识别错误。imx_v7_defconfig 也不是只为一颗 i.MX6UL 芯片写的配置;课程包中的文件同时启用了 i.MX6SX、i.MX6UL、i.MX6ULL、i.MX7 等多个同类平台选项,是面向 i.MX ARMv7 系列的厂商默认配置。

make imx_v7_defconfig 不是把该文件逐字复制为 .config。Kconfig 会读取 arch/arm/configs/imx_v7_defconfig、整个 Kconfig 描述树、默认值及依赖关系,再生成一份完整的 .config。后者在课程包中远大于 defconfig,并包含三类常见状态:=y、=m、is not set。

.config 的作用也不只是“决定哪些 .c 文件编译”:

  • 一些配置通过 Kbuild 决定某个 .o 进入 obj-y、obj-m,或完全不构建。
  • 一些配置转成 C 预处理宏,控制同一源文件内部的条件编译。
  • 一些配置是数值、字符串或地址参数,不直接对应一个源文件。
  • 配置之间存在 depends on、select 等依赖,不能只孤立看某一行。

.config 在 Unix 中以点开头,普通 ls 默认不显示,应使用 ls -a。视频通过 Samba 在 Windows 中浏览时,需要在“查看 → 显示 → 隐藏的项目”中显示它;是否被映射为 Windows 隐藏属性取决于共享服务配置,并不是所有以点开头的 Windows 文件都会天然隐藏。

1.4.2 .config文件

视频口述的“杠 y/杠 m”实际是 等于 y(=y) 与 等于 m(=m)。Kconfig 的三态含义如下:

.config 状态Kconfig 含义常见 Kbuild 结果
CONFIG_FOO=y启用并内建obj-$(CONFIG_FOO) 展开为 obj-y,对象参与内建内核链接
CONFIG_FOO=m启用为模块展开为 obj-m,经模块链接和 MODPOST 生成 .ko
# CONFIG_FOO is not set关闭,即 n对应目标通常不构建

只有 tristate 类型且模块总开关 CONFIG_MODULES=y、依赖条件满足的选项才能取 m;bool 类型只有 y/n。课程包的现成 .config 中确有 49 个 =m,并含有 CONFIG_MODULES=y,所以视频搜索到 49 项并不是界面误报。

=y 并不是“生成 .c 文件”。源文件本来就存在;它通常被编译为 .o,再归入各目录的内建目标,最后链接进 vmlinux。同样,=m 也不是单靠 .config 自动变成 .ko,还需要对应 Makefile/Kbuild 规则、源文件、模块链接与 MODPOST 阶段。

官方 Kconfig 语言文档说明了 bool、tristate、依赖和 y/m/n 逻辑。.config 是生成文件,通常应通过 defconfig、menuconfig 等配置工具修改,而不是随意手工编辑。

1.4.3 编译内核

视频工作副本中的命令是:make uImage LOADADDR=0x10008000 -j16。

  • uImage 是本次 Make 的目标。
  • LOADADDR=0x10008000 最终传给 mkimage,成为 legacy uImage 头中的加载地址和入口地址。
  • -j16 表示最多 16 个并行任务槽,不等同于“强制使用 16 个 CPU 核”。

Linux 源码树有顶层 Makefile 和大量目录级 Kbuild/Makefile,但执行 make uImage 并不是“执行所有 Makefile、编译所有源码”。顶层规则根据目标和 .config 选择需要下降的目录;只有相关子目录的构建描述被读取,满足配置与依赖的对象才会构建。比如 drivers/net/usb/ 是否进入构建、其中哪些目标属于内建或模块,都由上层目录关系和本目录规则共同决定。

对本课程脚本而言,make uImage 负责生成内核启动镜像所需的内建部分,后面的 make modules 再构建模块部分。uImage 是内核镜像,不是根文件系统;设备树由后续各个 make *.dtb 目标生成,也不是由 make modules 产生。

1.4.4 make modules

make modules 构建当前配置中所有能到达的 obj-m 模块目标。课程包已经包含 drivers/net/usb/rtl8150.ko,其大小为 19,074 B,正好验证了本节的 RTL8150 示例。

必须区分三个动作:

  1. 构建:make modules 在内核构建树中生成 .ko。
  2. 安装到根文件系统:通常还需要 make modules_install INSTALL_MOD_PATH=<根文件系统暂存目录>,把模块放入 /lib/modules/<kernel-release>/... 并建立相关元数据。
  3. 目标机加载:系统运行后由 modprobe 按依赖加载,或用 insmod 直接加载指定文件。

课程脚本只有第一步,没有 modules_install,也没有把 .ko 自动装进目标板根文件系统,更不会在开发板上自动加载它。模块还必须和目标内核的版本、配置、体系结构及符号版本相容;随便复制另一个内核编出的 .ko,常会出现 invalid module format 或未解析符号。

Linux 官方 Kbuild 文档把顶层主要构建产品概括为内建内核与模块;外部模块官方文档进一步说明了构建准备、modules_install、安装目录和版本符号检查。

1.4.5 驱动的两种编译方式
1.4.5.1 编译进内核

驱动被设为 y 后,其代码作为 built-in 参与内核链接。它不需要目标系统从根文件系统另找同名 .ko,适合启动早期必须可用的功能,例如读取根文件系统所依赖的存储控制器、总线或文件系统支持;否则内核还没挂载根文件系统,就没有机会从其中加载模块。

但“设备上电即可使用”并不能单独证明驱动是内建的。现代系统可能从 initramfs 提前加载模块,也可能由 udev 在发现硬件后自动执行 modprobe。屏幕、鼠标、键盘能自动工作,既可能来自内建驱动,也可能来自自动加载的模块。

视频借 Windows 的 CH340 驱动安装帮助理解“后装驱动”,只能当类比:Windows 驱动包不是 Linux .ko。Linux 中的 CH341/CH340 支持同样可以根据内核配置选择内建、模块或关闭,具体状态要查目标内核的 .config 和模块目录。

1.4.5.2 编译成ko文件

.ko 是 Linux kernel object 模块文件,包含内核可装载的代码、数据、重定位及模块元信息,不是普通用户态动态库。模块方式常用于可选硬件或不必在早期启动阶段工作的功能。

对比项内建 y模块 m
文件位置代码进入内核镜像生成独立 .ko,通常安装到根文件系统
可用时机内核启动时即存在根文件系统或 initramfs 可访问并加载后才存在
运行时卸载通常不能单独卸载在无占用且模块允许时可卸载
镜像大小增大内核镜像减小内核镜像,但模块仍占存储空间,加载后仍占内存
典型用途启动必需、长期固定的硬件可选外设、调试功能、按需支持

模块化可能减小常驻内核镜像并提高部署灵活性,但不保证系统一定启动更快或“硬件要求更低”。加载模块本身也需要时间和存储,并引入依赖、版本匹配与根文件系统可用性问题。应根据启动链、硬件是否必需、安全策略和维护方式决定 y/m/n。

课程预告的 MPU6050、OLED 驱动会采用独立 .ko 演示;本节只建立概念,没有展示这些驱动的业务源码,因此不能提前补写后续代码。

1.4.5.3 应用案例

课程实际验证的 RTL8150 链路如下:

CONFIG_USB_RTL8150=m → obj-$(CONFIG_USB_RTL8150) += rtl8150.o → obj-m += rtl8150.o → 编译 rtl8150.c → 模块链接/MODPOST → rtl8150.ko

其中:

  • 配置行位于顶层 .config。
  • Kbuild 规则位于 drivers/net/usb/Makefile 第 8 行附近。
  • 源文件是 drivers/net/usb/rtl8150.c。
  • 本课程压缩包中的最终模块是 drivers/net/usb/rtl8150.ko。

单源文件模块可以由一个 .c 对应一个模块目标;多源文件模块则先把若干 .o 组合成一个模块对象,再生成一个 .ko。这不是“多个 .ko 合成一个 .ko”,而是多个源文件/中间目标共同组成一个模块。具体 Kbuild 语法应以目标内核版本的 Makefile 为准。

1.4.5.4 makefile的小开关

.config 可以形象地理解成 Makefile 的“开关输入”,但真正链路包含 Kconfig 依赖和 Kbuild 展开,不能认为 Makefile 逐行直接读取原始 .config。构建系统会生成并包含 include/config/auto.conf 等中间配置文件,同时生成供 C 代码使用的配置头。

视频在 Source Insight 中的验证步骤是:

  1. 从 .config 复制 CONFIG_USB_RTL8150。
  2. 打开全文搜索,在 Search In 中选择 All Makefiles,避免只查当前 .c 文件。
  3. 搜索结果定位到 drivers/net/usb/Makefile。
  4. 打开真实规则 obj-$(CONFIG_USB_RTL8150) += rtl8150.o。
  5. 把 .config 中的值 m 代入,理解为 obj-m += rtl8150.o。

这是一种“人工展开”便于理解;源码文件中的变量写法并没有被构建系统永久改成 obj-m。视频随后在行尾临时敲入的 obj... 只是讲解标记,窗口标题也出现未保存的 *,不属于原始 Makefile,不能抄回源码。

同理,如果配置值为 y,规则展开到 obj-y;若为 n/未设置,变量展开为空,目标不会进入 obj-y 或 obj-m。但是否可选、能否设为 m、是否被其他符号强制选择,还要回到对应 Kconfig 条目和依赖判断。

1.5 内容总结

本节可压缩为四句话:

  1. create.sh 用 Bash 串起架构、工具链、配置、内核、模块和设备树构建命令。
  2. imx_v7_defconfig 经 Kconfig 生成完整 .config,不是简单复制。
  3. Kbuild 根据配置把目标归入 obj-y、obj-m 或不构建;y 进入内建内核,m 生成 .ko。
  4. 新驱动要进入配置和构建体系,至少要同时处理驱动源码、Kconfig 条目和对应 Makefile/Kbuild 规则,不能只改其中一个文件。

原转录中的 configure、k fig、q 文件 分别应校正为 .config、Kconfig、.ko。make 的本质也不是“执行 Makefile 文件”,而是读取其中的目标、依赖和 recipe,按当前目标与时间戳选择并执行必要命令。

1.6 make menuconfig

make menuconfig 是基于 Kconfig 的终端配置界面。课程画面实际进入:

Device Drivers → I2C support → I2C eeprom slave driver

转录中的 “iPhone 6 support” 是 OCR 错误。课程源码 drivers/i2c/Kconfig 把 I2C_SLAVE_EEPROM 定义为 tristate "I2C eeprom slave driver",所以在依赖满足时可用空格循环选择三种状态:

菜单标记保存后的典型状态含义
<*>CONFIG_I2C_SLAVE_EEPROM=y编译进内核
<M>CONFIG_I2C_SLAVE_EEPROM=m编译为模块
< ># CONFIG_I2C_SLAVE_EEPROM is not set不构建

[*]/[ ] 通常是只能开关的布尔选项;<M> 只会出现在可模块化且依赖允许的三态选项。画面中某些项目显示 -*- 或无法切换,通常表示其状态被其他选择或依赖固定,不能把所有菜单项都机械理解为按空格必然循环三态。

本视频必须纠正的架构风险: menuconfig 顶栏清楚显示 Linux/x86 4.1.15 Kernel Configuration,而不是 ARM。原因是先前 ./create.sh 中的 export ARCH=arm 只在子脚本进程有效;回到父 Shell 后直接运行 make menuconfig,Make 默认按主机 x86 架构打开了配置界面。这个画面可以用来认识 y/m/n,但不能保存为开发板配置,否则会用 x86 Kconfig 改写 ARM .config。

为课程开发板修改配置时,应在内核源码根目录使用:

make ARCH=arm menuconfig

也可以先在当前终端执行 export ARCH=arm,并为后续编译同时设置正确的 CROSS_COMPILE,再运行 make menuconfig。进入界面后应先检查顶栏确实显示 ARM。若已经误把 x86 配置保存到 .config,应从备份恢复,或重新执行 make ARCH=arm imx_v7_defconfig 后再按需求配置;后者会回到默认配置,之前尚未另存的定制项需要重新选择。

保存 menuconfig 后,选择会写入顶层 .config。若要让一个新驱动真正可构建,还必须:

  1. 在合适的 Kconfig 中定义符号、类型、提示和依赖。
  2. 在相应 Makefile/Kbuild 中把该符号映射到目标对象。
  3. 提供能在该内核版本中正确编译的驱动源码。
  4. 重新构建内核或模块,并把新的镜像/DTB/模块按实际变化部署到目标板。

因此,Kconfig 负责“选项如何出现、能否选择”,.config 保存“本次选择结果”,Makefile/Kbuild 负责“选择如何映射到构建目标”;三者缺一不可。