在之前的博文 Linux 之八 完整嵌入式 Linux 环境、(交叉)编译工具链、CPU 体系架构、嵌入式系统构建工具 中说了要一步步搭建整个嵌入式 Linux 运行环境,今天就开始编译 Linux Kernel。
源码
文中涉及的源代码均放到了我个人的 Github 上:https://github.com/ZCShou/BOARD-STM32F769I-EVAL
。这个仓库中包含了要搭建的完整嵌入式 Linux 环境的所有源代码,后续博文均以该仓库中的源码为基础来学习!
开发环境
我这里使用的基本开发环境依旧是在之前博文中多次说明的 Ubuntu 22.04.1 LTS + Arm GNU Toolchain 11.3.Rel1,对应的 J-link 也依旧是 J-Link_Linux_V764e_x86_64.deb。该环境下需要注意的问题说明如下:
- 由于 Ubuntu 22.04 LTS 默认是标配 OpenSSL 3.x,而旧版 U-Boot 使用的是 OpenSSL 1.x,所以,该环境编译旧版 U-Boot(从 commit e927e21c 开始添加了相关处理) 将出现一堆警告,因此,后续使用 u-boot-v2022.10 这个版本为主:
- Arm GNU Toolchain 10.2-2022.02 存在 BUG,导致编译 U-Boot 报错,不要使用这个版本!
- 新版(Arm GNU Toolchain 10.3 之后的版本)的 Arm GNU Toolchain 在 Linux 上 GDB 需要 Python3.8 支持。然而,Ubuntu 22.04 默认的 Python 是 3.10。直接运行
arm-none-eabi-gdb
报错如下:
解决方法就是直接手动安装 Python3.8 即可。 旧版的 Arm GNU Toolchain 10.3 -2021.10 不需要 Python 支持sudo add-apt-repository ppa:deadsnakes/ppa -y sudo apt install python3.8
还有一点,由于在之前在编译 U-Boot 时,我已经安装了大部分构建过程中需要的工具,因此,这里可能没有太多需要重新安装的工具了。如果出现错误,具体见博文 U-Boot 之一 零基础编译 U-Boot 过程详解、Image 镜像介绍及使用说明、DTB 文件使用说明 应该有解决方法。
运行环境
我使用的嵌入式环境是 STM32F769I-EVAL 板子。STM32F769I-EVAL 板子使用的 STM32F769NI 这个 MCU,STM32F769NI 这款 MCU 采用的是 ARM Cortex-M7 的核心,指令集架构是 ARMv7m。此外,还需要注意,这个板子上的的串口的 RX 默认是断开,需要用短路帽连接起来。
Kernel 本身没有提供对于 STM32F769I-EVAL 板子的支持,但是它支持的 STM32F769-Disco 板子。STM32F769-Disco 开发板与 STM32F769I-EVAL 评估板的 MCU 都使用的是 STM32F769NI,因此,我们直接编译后下载是可以运行的。
不过两者的板载资源不同(例如,DRAM 大小),因此,Kernel 识别的某些外设信息是不对的。这里仅仅关注如何进行零基础编译,在博文 Linux 之十 移植过程详解、 STM32F769I-EVAL 开发板适配 中我详细介绍了如何将 Kernel 移植到 STM32F769I-EVAL 板子。
构建过程
Linux Kernel 的构建过程与 U-Boot 构建过程是一模一样的。因为 U-Boot 的构建就是采用的 Linux Kernel 的 Kconfig/Kbuild 构建系统。具体见博文 U-Boot 之一 零基础编译 U-Boot 过程详解、Image 镜像介绍及使用说明、DTB 文件使用说明 和 U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解。
在开始编译之前,我们先介绍两个命令:make clean
用于清空编译中间文件 和 make distclean
用于清除所有编译产生的文件。如果我们想要重新编译,可以使用以上两个命令清理环境。这两个命令均定义在 Kernel 源码根目录的 Makefile 中。
-
第一步肯定是获取 Kernel 的源代码,我这里采用的是最新的 LTS 版:5.15.52:
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.52.tar.xz
。下载后直接解压:tar xvJf linux-5.15.52.tar.xz
。
- 可能需要相关工具才可以直接从 Kernel 官网下载
- 最好不要选用 Mainline 分支的版本
- 解压过程时间较长,耐心等待
成功下载并解压源代码之后,我们需要进入解压的 Kernel 源码目录下,使用命令:
cd linux-5.15.52
。此后就在 Kernel 源码目录下进行各种操作。 -
第二步就是生成配置,直接使用命令:
ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 stm32_defconfig
。其中,参数O=xx
用于指定编译的输出路径,这样,所有编译输出都放到指定目录下,方便查看。
-
第三步修改配置(裁剪)。直接使用命令:
ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 menuconfig
。其中,参数O=xx
用于指定编译的输出路径,这样,所有编译输出都放到指定目录下,方便查看。
需要特殊注意的是,每一步命令都必须带有ARCH=arm CROSS_COMPILE=arm-none-eabi-
这俩参数,否则不能正常按照我们指定的架构来显示配置。正常就会进入下面左侧的配置界面了。
这里我们需要更改一些参数: “System Type” > “DRAM Base” = 0xc0000000 and “DRAM Size” = 0x02000000,以及将 “Boot options” -> “Kernel Execute-In_Place From ROM” 前面的型号去掉。最后保存退出即可。
-
第四步就是真正的编译了,直接使用命令:
ARCH=arm CROSS_COMPILE=arm-none-eabi- make O=build_stm32 -j$(nproc)
,然后就是漫长的等待… 其中,参数O=xx
用于指定编译的输出路径,这样,所有编译输出都放到指定目录下,方便查看。
-
正常编译完成之后,就会在 Kernel 源码的对应架构目录下生成我们真正需要的
arch/arm/boot/zImage
和arch/arm/boot/dts/stm32f769-disco.dtb
这两个文件了。
Image 说明
在构建 Kernel 镜像时,我们可以为内核镜像选择多种格式。具体就看在 make menuconfig
这一步的配置。某些格式的镜像可能是针对特定架构的,下面来介绍一下常见的镜像格式。
-
vmlinux:
vm
是 Virtual Memory 的缩写,它是由用户对内核源码编译出来的最原始的内核文件,是一个 ELF( Executable and Linkable Format)格式的文件。当我们编译完 Kernel 后,在源码根目录下就会生成该文件。vmlinux 是最原始的镜像文件,其他镜像文件都有它来生成。所有其他未压缩的镜像都直接或者间接由它生成。
vmlinux 这个名字来自于 Unix。在上世纪 60 年代,Unix 开发者简单地称他们的内核为 “unix”,所以,Linux 在上世纪 90 年代诞生后,Linus 开始称他们的内核为 “Linux”。当虚拟内存被开发为更容易的多任务处理能力时,“vm”被放在文件的前面,以表明内核支持虚拟内存。有一段时间,Linux 内核被称为 vmlinux。
在 Ubuntu 中,内核镜像文件就是 vmlinux 这个格式,存储在/boot
文件夹中,名字的格式为 vmlinuz-A.B.C.D。A.B.C 是 Stable Kernel 的版本号,D 表示 PATCH 版本号。
注意,在./arch/arm/boot/compressed
下,还有个经过压缩之后的 vmlinux 。所有其他与压缩有关的镜像都直接或者间接由它生成。
-
System.map: 记录 Kernel 中各个符号在内核中位置,但是这个文件是使用了 nm 和 grep工具来手动生成的(不是链接器输出的 map 文件),具体处理是在
scripts\mksysmap
文件中!
-
vmlinuz:随着内核变得越来越大,无法容纳到可用的引导内存中,因此压缩了内核镜像,命名上则将最后 x 改为 z,以表示它是用
zlib
压缩压缩的。这种压缩并不总是被使用,而是经常被 LZMA 或 BZIP2 取代。 -
Image:它是基于根目录下未压缩的 vmlinux 生成的,编译完 Kernel 后,在对应架构的
boot
(例如,arch\arm\boot
) 目下就会生成该文件。
它经过 objcopy 处理的只包含二进制数据的内核代码,就是将 vmlinux 中的 ELF 头和尾部的符号表等去掉之后剩余的内容。
-
zImage:它是基于压缩过的 vmlinux 生成的,编译完 Kernel 后,在对应架构的
boot
(例如,arch\arm\boot
) 目下就会生成该文件。
生成方式与上面的 Image 是一模一样的(掐头去尾)。这里就不多上图了。- zImage 是 ARM linux 常用的一种压缩镜像文件
- 这种格式的 Linux 镜像文件多存放在 NAND 上
-
bzImage:bz 表示 big zImage,其格式与 zImage 类似,但采用了不同的压缩算法,bzImage 的压缩率更高。
-
uImage:这是 uboot 专用的镜像文件,它是在 zImage 之前加上一个长度为 0x40 的头信息(tag),在头信息内说明了该镜像文件的类型、加载位置、生成时间、大小等信息。换句话说,若直接从 uImage 的 0x40 位置开始执行,则 zImage 和 uImage 没有任何区别。
- 编译完 Kernel 后,在对应架构的
boot
(例如,arch\arm\boot
) 目下就会生成该文件 - 这种格式的 Linux 镜像文件多存放在 NAND 上
- 编译完 Kernel 后,在对应架构的
-
xipImage:它是基于根目录下未压缩的 vmlinux 生成的,编译完 Kernel 后,在对应架构的
boot
(例如,arch\arm\boot
) 目下就会生成该文件。
生成方式与上面的 Image 是一模一样的(掐头去尾)。这里就不多上图了。xip
是 Execute In Place 的缩写,这种格式的 Linux 镜像文件多存放在 NorFlash 上,运行时不需要拷贝到内存 SDRAM 中,可以直接在 NorFlash 中运行。
Image 使用
Kernel 镜像的使用与我们在 make menuconfig
中的配置是有关系的。当然也与 U-Boot 相关。在上面的编译中,我们实际需要的文件是 linux-5.15.52/build_stm32/arch/arm/boot/zImage
和 linux-5.15.52/build_stm32/arch/arm/dts/stm32f769-disco.dtb
这两个文件。
- zImage:这个通常需要使用 U-Boot 的相关命令来将它下载开发板的对应存储器中。例如,我这里就需要使用命令
fatload mmc 0 0xc0000000 zImage
将 zImage 下载到 MMC 中 - stm32f769-disco.dtb:这个需要直接烧录到 MCU 的内部 FLASH 中,地址是 0x81c0000。这个其实在 U-Boot 中有对应的配置项。
参考
- https://www.howtogeek.com/howto/31632/what-is-the-linux-kernel-and-what-does-it-do/
- https://www.cnblogs.com/Oude/articles/12039025.html
- https://james-hui.com/2021/07/02/building-a-small-uboot-linux-and-rootfs-for-arm-cortex-m7/
- https://blog.csdn.net/m0_47799526/article/details/106203517
- https://blog.csdn.net/lpwsw/article/details/122098847