Browse Source
A detailed overview of Zephyr's build system. This is a thorough view of the low level build process starting from CMake and using Make as the build system tool. Things missing here that will be further documented: - west - external modules/libraries Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>pull/20714/head
8 changed files with 172 additions and 0 deletions
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 61 KiB |
@ -0,0 +1,155 @@
@@ -0,0 +1,155 @@
|
||||
.. _build_overview: |
||||
|
||||
Build Overview |
||||
############## |
||||
|
||||
The Zephyr build process can be divided into two main phases: a |
||||
configuration phase (driven by *CMake*) and a build phase (driven by |
||||
*Make* or *Ninja*). We will descibe the build phase using *Make* as |
||||
example. |
||||
|
||||
|
||||
Configuration Phase |
||||
******************* |
||||
|
||||
The configuration phase begins when the user invokes *CMake*, |
||||
specifying a source application directory and a board target. |
||||
|
||||
.. figure:: build-config-phase.svg |
||||
:align: center |
||||
:alt: Zephyr's build configuration phase |
||||
:figclass: align-center |
||||
:width: 80% |
||||
|
||||
*CMake* begins by processing the *CMakeLists.txt* file in the application |
||||
directory, which refers to the *CMakeLists.txt* file in the Zephyr |
||||
top-level directory, which in turn refers to *CMakeLists.txt* files |
||||
throughout the build tree (directly and indirectly). Its primary |
||||
output is a set of Makefiles to drive the build process, but *CMake* |
||||
scripts do some build processing of their own: |
||||
|
||||
Device tree |
||||
Using *cpp*, device-tree specifications (*.dts/.dtsi* files) are |
||||
collected from the target’s architecture, SoC, board, and |
||||
application directories and compiled with *dtc*. Then the build |
||||
tool (scripts/dts) convert this into *.h* files for later |
||||
consumption. |
||||
|
||||
Device tree fixup |
||||
Files named *dts_fixup.h* from the target’s architecture, SoC, |
||||
board, and application directories are concatenated into a single |
||||
*dts_fixup.h*. Its purpose is to normalize constants output in the |
||||
previous step so they have the names expected by the source files |
||||
in the build phase. |
||||
|
||||
Kconfig |
||||
The build tool reads the *Kconfig* files for the target |
||||
architecture, the target SoC, the target board, the target |
||||
application, as well as *Kconfig* files associated with subsystems |
||||
throughout the source tree. It incorporates the device tree outputs |
||||
to allow configurations to make use of that data. It ensures the |
||||
desired configuration is consistent, outputs *autoconf.h* for the |
||||
build phase. |
||||
|
||||
Build Phase |
||||
*********** |
||||
|
||||
The build phase begins when the user invokes *make*. Its ultimate |
||||
output is a complete Zephyr application in a format suitable for |
||||
loading/flashing on the desired target board (*zephyr.elf*, |
||||
*zephyr.hex*, etc.) The build phase can be broken down, conceptually, |
||||
into four stages: the pre-build, first-pass binary, final binary, and |
||||
post-processing. |
||||
|
||||
Pre-build occurs before any source files are compiled, because during |
||||
this phase header files used by the source files are generated. |
||||
|
||||
Pre-build |
||||
========= |
||||
|
||||
Offset generation |
||||
Access to high-level data structures and members is sometimes |
||||
required when the definitions of those structures is not |
||||
immediately accessible (e.g., assembly language). The generation of |
||||
*offsets.h* (by *gen_offset_header.py*) facilitates this. |
||||
|
||||
System call boilerplate |
||||
The *gen_syscall_header.py*, *parse_syscalls.py* and |
||||
*gen_syscall_header.py* scripts work together to bind potential |
||||
system call functions with their implementations. |
||||
|
||||
.. figure:: build-build-phase-1.svg |
||||
:align: center |
||||
:alt: Zephyr's build stage I |
||||
:figclass: align-center |
||||
:width: 80% |
||||
|
||||
First-pass binary |
||||
================= |
||||
|
||||
Compilation proper begins with the first-pass binary. Source files (C |
||||
and assembly) are collected from various subsystems (which ones is |
||||
decided during the configuration phase), and compiled into archives |
||||
(with reference to header files in the tree, as well as those |
||||
generated during the configuration phase and the pre-build stage). |
||||
|
||||
If memory protection is enabled, then: |
||||
|
||||
Partition grouping |
||||
The gen_app_partitions.py script scans all the |
||||
generated archives and outputs linker scripts to ensure that |
||||
application partitions are properly grouped and aligned for the |
||||
target’s memory protection hardware. |
||||
|
||||
Then *cpp* is used to combine linker script fragments from the target’s |
||||
architecture/SoC, the kernel tree, optionally the partition output if |
||||
memory protection is enabled, and any other fragments selected during |
||||
the configuration process, into a *linker.cmd* file. The compiled |
||||
archives are then linked with *ld* as specified in the |
||||
*linker.cmd*. |
||||
|
||||
In some configurations, this is the final binary, and the next stage |
||||
is skipped. |
||||
|
||||
.. figure:: build-build-phase-2.svg |
||||
:align: center |
||||
:alt: Zephyr's build stage II |
||||
:figclass: align-center |
||||
:width: 80% |
||||
|
||||
Final binary |
||||
============ |
||||
|
||||
In some configurations, the binary from the previous stage is |
||||
incomplete, with empty and/or placeholder sections that must be filled |
||||
in by, essentially, reflection. When :ref:`usermode` is enabled: |
||||
|
||||
Kernel object hashing |
||||
The *gen_kobject_list.py* scans the *ELF DWARF* |
||||
debug data to find the address of the all kernel objects. This |
||||
list is passed to *gperf*, which generates a perfect hash function and |
||||
table of those addresses, then that output is optimized by |
||||
*process_gperf.py*, using known properties of our special case. |
||||
|
||||
Then, the link from the previous stage is repeated, this time with the |
||||
missing pieces populated. |
||||
|
||||
.. figure:: build-build-phase-3.svg |
||||
:align: center |
||||
:alt: Zephyr's build stage III |
||||
:figclass: align-center |
||||
:width: 80% |
||||
|
||||
|
||||
Post processing |
||||
=============== |
||||
|
||||
Finally, if necessary, the completed kernel is converted from *ELF* to |
||||
the format expected by the loader and/or flash tool required by the |
||||
target. This is accomplished in a straightforward manner with *objdump*. |
||||
|
||||
.. figure:: build-build-phase-4.svg |
||||
:align: center |
||||
:alt: Zephyr's build final stage |
||||
:figclass: align-center |
||||
:width: 80% |
Loading…
Reference in new issue