概述
ncs 相关文章,部分为原始文档翻译,水平有限,如果有错误,欢迎指出。
本文参考链接:
https://docs.zephyrproject.org/latest/guides/build/index.html
编译过程总览
nRF Connect SDK的编译过程和zephyr的编译过程是一致的,主要分为两个阶段:
- 配置阶段由CMake驱动
- 编译阶段一般由Make或者Ninja ,在ncs中使用Ninja
配置阶段
当我们调用CMake时,配置阶段开始,指定源应用程序目录和板目标开始配置,也就 是我们输入如下指令的时候
cmake -GNinja -DBOARD=nrf9160_pcal0090ns..
当然,在目前的ncs环境中,我们可以通过使用west工具输入下面指令进行配置和构建,它实际上调用了上面的指令,并且额外调用了ninja指令
west build -b nrf9160_pcal0090ns
配置的过程如下图所示:

CMake首先处理用户应用程序目录中的CMakeLists.txt文件,我们以helloworld为例,打开hello_world\CMakeLists.txt:
1 cmake_minimum_required(VERSION 3.13.1)
2 find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
3 project(hello_world)
4 target_sources(app PRIVATE src/main.c)
这里1,3,4行都是一些基础配置,比如设置工程名字,添加源代码等,先忽略掉,这里主要看第二行代码:
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
这里的ZEPHYR_BASE指向的是zephyr的根目录,我们在安装ncs环境的时候,已经配置过该宏定义指向,也就是ncs\zephyr这个目录,然后find_package的作用是在ZEPHYR_BASE目录下找到Zephyr的模块,在cmake中,模块的命名模板为xxxConfig.cmake或Findxxx.cmake,这里我们找到的是ncs\zephyr\share\zephyr-package\cmake\ZephyrConfig.cmake它通过调用ncs\zephyr\cmake\app\boilerplate.cmake来处理devicetree及kconfig等配置文件,输出一组makefile或Ninja文件来驱动构建过程。
Devicetree处理
*. dts (devicetree源文件)和*. dtsi(devicetree源文件包含的文件)从配置的目标架构,SOC ,板子和用户目录中获取
*dts文件通过Cpp也就是C预处理器包含*dtsi文件,C预处理器也可以用于在任何 devicetree的*. overlay文件中进行合并,或者在*. dts,*.dtsi或*. overlay文件中展开相应的宏定义
经过预处理的devicetree源文件(存储在*. dts. pre. tmp中)由gen_defines. py进行解 析,生成一个带有预处理宏的devicetree_unfixed. h文件。
同样的,出于调试的目的,gen_defines. py同样会把最终的devicetree写入 zephyr.dts文件中,该文件仅作为参考,没有任何地方会用到。
dtc devivetree编译工具会获取到编译过程中的错误和警告,如果没有出现错误和警 告,则不会用到。
Devicetree fixups 来自目标体系结构、SoC、board和应程序目录的dts_fixup.h的文件被链接到一个 devicetree_fixups. h文件中.dts_fixup. h文件用于将生成的宏重命名为源代码所期望的名称.
源代码通过包含了devicetree_unfixed.h和devicetree_fixups.h文件生成的devicetree.h访问从devicetree生成的预处理器宏
Kconfig
Kconfig文件定义了目标体系结构、SoC、board和应用程序的可用配置选项,以及选项之间的依赖关系。
Kconfig配置存储在配置文件中.最初的配置是通过合并来自板子和应用程序的配置(如pri.conf)来生成的。
Kconfig的输出是一个autoconf.h头文件和一个.config文件,它同时作为已保存的配 置和配置的输出文件(由CMake使用),
Kconfig可以通过在kconfigfunctions.py中定义的函数获得来自devicetree的信息。
构建阶段
构建阶段从用户调用make或ninja开始。它的最终输出是完整的Zephyr应用程序,其格式适合烧录到到所需的目标板,如zephyr.elf或zephyr.hex等等,构建阶段 可以被分解为四个阶段:
- pre-build
- first-pass binary
- final binary
- post-processing
Pre-build 阶段

创建offset
当不能立即访问结构和成员的定义时(例如,汇编语言),有时需要通过访问这些结构和成员,通过生成offset .h(通过gen_offset_header.py)来实现这一点。
引用System call
gen_syscall.py和parse_syscalls.py脚本工作,将潜在的系统调用函数与其实现绑定在一起。
First-pass binary阶段

正常的编译从第一遍二进制文件开始,源文件(C和汇编)从各种子系统收集(在配置阶段决定),并编译成归档文件(参考树中的头文件,以及在配置阶段和预构建阶段生成的头文件)。
如果启用了memory protection,那么gen_appjpartitions.py脚本扫描所有生成的归档文件并输出链接脚本,以确保应用程序分区被正确的分区并且和目础板子的内存保护硬件对齐。
然后使用cpp将来自目标体系结构/SoC的链接器脚本片段、内核树,如果启用了内存保护,还会包含分区输出,以及在配置过程中选择的任何其他片段组合到linker. cmd文件中。然后按照linker .cmd中指定的方式通过Id工具将上面产生的 归档文件链接起来。
在某些配置中,这就是最后的二制文件,下一阶段(Final binary)将被跳过。
Final binary阶段

在某些配置中,来自上一阶段的二进制数据是不完整的,其中有空的和/或占位符部分,本质上必须由反射来填充。
当启用User Mode时,内核对象的哈希:
gen_kobject_list.py扫描ELF DWARF调试数据,以找到所有内核对象的地址。这个列表被传递给gperf, gperf生成一个合适的散列函数和这些地址的表,然后由process_gperf.py使用我们已知的特殊情况的属性来优化输出。
然后,重复前一阶段的链接过程,这次填充缺失的部分。
Post processing阶段

最后,如果有必要,将完成的内核从ELF转换为加载程序或目标程序所需要的flash工具所期望的格式。这可以通过使用objdump以一种简单的方式完成。
用到的脚本文件
构建过程中,用到的所有脚本文件如下:
scripts/gen_syscalls.py生成系统调用syscall的脚本
scripts/gen_kobject_list.py 生成内核对象元数据的gpef表的脚本
scripts/gen_offset_header.py 主要用于汇编代码及结构体成员访问部分。 scripts/parse_syscalls.py 扫描Zephyr的包含目录和发出系统调用和子系统元数据的脚本
scripts/gen_relocate_app. py 该脚本将从所需文件中重新定位.text、.rodata、.data 和.bss部分,并将其放到所需的内存区域。
scripts/process_gperf. py文件后处理器脚本,用于将elf格式转换为其他格式
这里只对以上脚本功能做简单说明。
有兴趣可以参考以下链接做具体了解:
https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/build/index.html
2309

被折叠的 条评论
为什么被折叠?



