Zephyr构建过程简述

Creative Commons
本作品采用知识共享署名

本文简述Zephyr的构建过程。

本文所有图片全部来自Zephyr官方文档,详情请见文末参考

本文说明Zephyr的构建过程,本文会提及构建过程用到的一些脚本,但不分析构建的脚本,可以将本文作为分析构建过程的目录。

概览

Zephyr的构建过程分为两个主要阶段:配置阶段和编译阶段。配置阶段生成必要的配置项,编译阶段根据配置项选择编译源代码并链接成最终的可执行文件。

配置阶段

配置阶段由cmake驱动完成,通过cmake命令和python脚本主要完成下面内容:dts处理,conf/Kconfig文件处理,cmake处理输出控制编译构建的makefile或者ninja文件,整个流程如下图(摘自zephyr官方文档,参考最后链接)
build-config-phase.png

dts

配置阶段会在cmake/dts.cmake中将所有的分散的dts/dtsi通过C预编译整合为一个dts文件,再通过script/dts/下的python脚本将整合后的dts结合dts/bindings/的yaml转化为头文件devicetree_unfixed.h和devicetree.conf。
cmake文件将各个目录下的dts_fixup.h转化为devicetree_fixups.h。
include/devicetree.h直接include devicetree_unfixed.h和devicetree_fixups.h 然后提供给zephyr各个module使用

Kconfig

Kconfig读取dts产生的devicetree.conf,结合Kconfig文件,prj.conf及其它配置文件被script/kconfig/下的python脚本整合为一个配置文件.config并生成一个统一的配置头文件autoconf.h为统一的配置头文件, 该文件会被zephyr各个module引用。

cmake

zephyr使用cmake控制构建,在配置阶段会通过cmake/下的文件去搭配各目录的CMakeList.txt生成makefile或者ninja文件。cmake分析的入口可以从cmake/app/boilerplate.cmake开始。

编译阶段

编译阶段会使用make或者ninja读取配置阶段产生的makefile或者ninja文件,控制进行编译,最后生成可执行文件。编译阶段分为4个子阶段:预编译,首次编译链接,二次编译链接,镜像生成。
如果你使用make来编译,那边这个阶段就是从你执行make命令开始,到make执行完成结束,你可以在make前执行下面命令,那么make时的所有执行的动作都会被显示出来,方便分析编译链接过程:

1
export VERBOSE=1

预编译

这个阶段主要是生成offset.h和系统调用的头文件,官方文档提供的流程图如下:
build-build-phase-1.png

1.生成offset.h

在底层汇编代码中实现内核上线文切换等情况时,通常会访问C中定义结构体内的成员,汇编就需要这些成员在数据结构中的偏移地址,offset.c中利用gcc的特性,可以在编译后按照一定的规则生成一组宏定义来保存结构体成员的偏移地址,在汇编中通过这些宏就可以取得结构中体成员的偏移值,从而可以访问C结构体变量的成员。因为和上线文切换有关,因此这是架构相关的,不同的架构有不同的offset.c,cortex-m7的就是zephyr/arch/arm/core/offsets/offsets_aarch32.c。
生成流程是offset.c 被gcc编译成obj文件, scripts/gen_offset_header.py解析obj文件,生成offsets.h,里面保存了偏移地址宏。

2.生成系统调用

统调用主要是由script/gen_syscall.py扫描头文件和c文件,生成系统调用的头文件,系统调用的细节可以参考Zephyr用户模式-系统调用一文

首次编译链接

首次编译链接会收集各个模块内的C和汇编文件,然后根据配置阶段的头文件选择响应的代码进行编译,编译产出库文件。
如果开启了userspace,且代码中使用了Domain/Partition,那么在该编译阶段还会使用gen_app_partitions.py解析app_smem,并生成app_smem的ld文件,将该文件整合入该阶段的链接文件link.cmd。内存保护的相关内容可以参看Zephyr用户模式-内存保护Zephyr-libc简介和malloc分析两遍文章。
通过预编译器将各个ld文件整合为一个链接文件link.cmd,最后根据该链接文件将前面生成的库文件链接为zephyr_prebuild.elf
官方文档提供的流程图如下:
build-build-phase-2.png

二次编译链接

进行二次编译链接时会重新放置中断向量表和生成内核对象的数据,最后和其它库重新编译链接生成最终的zephyr.elf

生成中断向量表

首次编译时中断向量被方法.intList中,这里会将其从zephyr_prebuild.elf内提取出来生成isr_table.c 然后重新编译,详细信息可以参考Zephyr中断系统-实现

内核对象数据生成

第一阶段编译链接后所有的内核对象地址已经被确定,这里会将这些内核对象的地址从zephyr_prebuild.elf中提取出来生成hash表,再编译链接进入zephyr.elf的kobject_data段,用于内核对象的管理,详细信息参考Zephyr用户模式-内核对象
官方文档提供的流程图如下(只有内核对象数据生成流程,省略了生成中断向量表的流程):
build-build-phase-3.png

镜像生成

一些情况下zephyr.elf并不能满足实际的需求,例如zephyr的镜像会被bootloader加载(需要bin),或者是一些flash tool进行烧写(hex或bin),因此zephyr的构建系统会在最后将elf转化为bin或者hex。
官方提供的流程图如下(实际应该是用objcopy):
build-build-phase-4.png

参考

https://docs.zephyrproject.org/latest/guides/build/index.html