Real-time operating system Zephyr The open-source alternative for small systems

From Andreas Klinger* 7 min Reading Time

Related Vendors

Zephyr is a young open-source RTOS with many parallels to Linux. However, it's not just a downsized Linux, but a standalone operating system. Why another open-source OS?

Zephyr was originally the name of a Greek wind deity. This probably inspired the creators of the new RTOS to use a kite in the logo.(Image: Vogel Communications Group)
Zephyr was originally the name of a Greek wind deity. This probably inspired the creators of the new RTOS to use a kite in the logo.
(Image: Vogel Communications Group)

Linux has its limits where small microcontrollers are to be used and memory and flash resources are extremely scarce. The lower limits for Linux on systems without an MMU are considered to be 4 MB of RAM and 3 MB of flash. With an MMU, it's 8 MB of RAM. This is an extremely reduced Linux system, which no longer includes most of the great features that define Linux. The overhead caused by the operating system is significantly larger than with specialized operating systems for small microcontrollers.

A typical example is the Cortex processors. While Linux plays a major role in the Cortex-A series, the Cortex-M processors are no longer its domain. This is where smaller operating systems come into play.

Zephyr: Architecture and components

Zephyr is a real-time operating system that can be used from small microcontrollers to large systems. Through a configuration menu, the components to be created can be configured. The configuration includes not only the features of the operating system but also the selection of drivers, libraries, and user applications.

Since there is only one executable, these applications run as threads just like the operating system's housekeeping. This makes it possible to offer the user a shell with a considerable selection of commands while simultaneously recording data and responding to interrupts.

The following sections provide more detailed information on the architecture of the operating system.

Priority-Based Preemptive Scheduling

The scheduler recognizes priorities, where a smaller numerical value means higher priority. Higher priority means that tasks with lower priority are interrupted, thus a priority-based preemptive scheduling, as we know from many other real-time operating systems.

Image 1: In the priority range supported by the scheduler, there are configurable areas for different thread types.
(Image:IT Klinger)

A special feature is the priority ranges where different thread types are located, as can be seen in Image 1:

  • Hardware interrupts are above the scheduler and interrupt it

  • Optional high negative priorities (in Figure 1: -21 ... -25): With the configuration variable NUM_PREEMPT_PRIORITIES, high-priority preemptor threads can be defined and used as meta-interrupt service routines. These are used to remove code from the hardware interrupt service routines and to execute it before the rest of the scheduling. These are essentially prioritized as high as an ISR but are nonetheless scheduled. Hence the term "Meta-IRQ".

  • Negative priorities (in Image 1: Prio -1 ... -20): Cooperative threads (SCHED_FIFO) do not interrupt each other on the respective priority level and are scheduled in the order they became ready to compute.

  • Positive priorities (in Image 1: Prio 0 ... 14): Preemptive threads (SCHED_RR) which are scheduled on the respective priority level in a round-robin manner.

  • Idle Thread (in Image 1: Prio 15): Exactly one thread at the lowest priority level.

In addition to the described priority divisions, a deadline scheduling can also be optionally used (CONFIG_SCHED_DEADLINE). With this method, tasks within a priority level are scheduled according to the Earliest-Deadline-First procedure. This means that the task with the most urgent deadline is addressed first. This method is applied to each priority level, so that tasks of lower priority but with shorter deadlines would not be addressed.

This is how memory management works

In a single Zephyr executable, there are many threads. A number of them are created by the operating system depending on the configuration (e.g., network, shell, logging, tracing, ...) and additional ones can be created by the application. These threads work on the same memory and use the same address pool. However, the MPU (Memory Protection Unit) is utilized to make only the designated memory accessible to each thread. This prevents threads from overwriting each other's memory.

Image 2: Example of a memory layout with two threads, kernel and user space heap. For clarity, the remaining threads have been omitted compared to the screen output (Image 4).
(Image:IT Klinger)

In Image 2, one can see the basic division of the available memory. For clarity, only the code, data, and stack areas for two threads are shown. In a real system, there are usually significantly more threads with their memory areas. The memory usage is configured and determined at build time:

  • Memory pool for allocations in the kernel using k_malloc()

  • Malloc arena for the user space, requested with the function malloc()

  • Stack of the subsystems

  • Interrupt stack

  • Stack of the main thread

Image 3: Example from a build process
(Image:IT Klinger)

The size of the executable results from the linking process. From these individual memory areas, the build process can calculate whether the available memory is sufficient at all, and if not, the build is terminated with an error. In the successful case, a summary of memory usage is displayed. Image 3 shows an example from a build process.

Subscribe to the newsletter now

Don't Miss out on Our Best Content

By clicking on „Subscribe to Newsletter“ I agree to the processing and use of my data according to the consent form (please expand for details) and accept the Terms of Use. For more information, please see our Privacy Policy. The consent declaration relates, among other things, to the sending of editorial newsletters by email and to data matching for marketing purposes with selected advertising partners (e.g., LinkedIn, Google, Meta)

Unfold for details of your consent
Image 4: Overview of the existing threads and their maximum stack usage so far
(Image:IT Klinger)

If you have configured the target system with a shell and thread monitoring, you get an overview of the existing threads and their maximum stack usage so far with the command "kernel stacks", see Image 4.

Once you have thoroughly tested the system, you can potentially optimize the stack usage based on these values to save memory space.

Timer events and sleep functions

In Zephyr, the time base is defined with the configuration variable CONFIG_SYS_CLOCK_TICKS_PER_SEC. Timer events or sleep functions can be used with this granularity. If a periodic system tick is used (CONFIG_TICKLESS_KERNEL is not set), then periodic interrupts are generated at this frequency.

Since each interrupt is associated with a certain overhead, the frequency cannot be increased indefinitely. A realistic value, which is still usable with our microcontrollers, would be for example 10 kHz. With this value, the temporal resolution is limited to 100 µs, which is too coarse-grained for many applications.

On the other hand, if you configure a tickless kernel (CONFIG_TICKLESS_KERNEL is set), timer events are processed entirely on an event basis and by eliminating the periodic tick and its base load, significantly more granular time handling can be achieved. An increase in time granularity (CONFIG_SYS_CLOCK_TICKS_PER_SEC) to 1 µs then becomes realistic even for small microcontrollers.

In summary, it can be emphasized that the variable CONFIG_SYS_CLOCK_TICKS_PER_SEC defines the frequency of timer tick interrupts in a tick-based kernel and sets the minimum time resolution in a tickless kernel. Therefore, in a tick-based kernel, this frequency is cautiously increased to avoid overwhelming the system with a flood of timer interrupts, while in a tickless system, it is significantly increased to benefit from a high time resolution.

Configuration and build process

A meta-tool called "west" serves as an interface to the configuration, build, and debug process. From this tool, all operations can be initiated and executed.

Zephyr – the gentle west wind

Zephyr, translated as "the one coming from the mountain“, is a wind deity from Greek mythology, embodying the (gentle) west wind. In antiquity, Zephyr was revered as a harbinger of spring and "ripening of the crops". This comes full circle to author Andreas Klinger, who is not only a software expert but also an ecological grain farmer: www.it-klinger.de.

Regarding Device Tree and configuration, there are two levels – that of the board and of the project. While a Device Tree describes the used board and is part of the Zephyr repository, it is complemented by the extensions and modifications required for the project. Both levels are located in different directories, ensuring high reusability.

The configuration is menu-driven and similar to that of the Linux kernel. In the project directory, there is a subdirectory called "board/". There, you can find the device tree fragment for each board that can be used with the project.

The Zephyr repository contains a deep directory hierarchy. Important subdirectories for developers are:

  • arch/$ARCH/: ($ARCH stands for the architecture, e.g., "arm") Architecture-dependent code (interrupt vectors, fault handling, timer, ...)

  • dts/$ARCH/$SOCVENDOR/: ($SOCVENDOR stands for the SOC manufacturer, e.g., "st") Device tree includes of the SOCs with basic, reusable components

  • soc/$ARCH/${SOCVENDOR}_$SUBARCH/: ($SUBARCH stands for the sub-architecture, e.g., "stm32") Adjustments for the SOC (System-On-Chip)

  • board/$ARCH/$BOARD/: ($BOARD stands for the board, e.g., "olimex_stm32_e407") Adjustments to a specific board (configuration, device tree, documentation, ...)

  • kernel/: Zephyr kernel with its central, architecture-independent code (timer, scheduler, IPC, driver model, ...)

  • subsys/: Subsystems as part of Zephyr (logging, shell, tracing, bus systems, ...)

  • drivers/: Driver implementations

  • lib/: Libraries (C-Library, Posix, OS, ...)

  • include/: Include files

  • modules/: Optional components

  • samples/: Example projects for the use of Zephyr features, drivers, sensors, networking and much more

  • doc/: Documentation

Summary

Zephyr is a young open-source RTOS with many parallels to Linux. However, it is not simply a downsized Linux, but a standalone operating system. For some features, you have to look closely, as mechanisms that are identically named compared to Linux are used slightly differently. The scheduling mechanisms are examples of this. (jw)

Zephyr – the gentle west wind

Zephyr, translated as "the one coming from the mountain", is a wind deity from Greek mythology, embodying the (gentle) west wind. In antiquity, Zephyr was revered as a harbinger of spring and "ripening of the crops". This comes full circle to author Andreas Klinger, who is not only a software expert but also an ecological grain farmer: www.it-klinger.de.