The Zephyr Project is an open-source project—an operating system that targets the low end of the Internet of Things (IoT). It can be run on small microcontrollers and higher-end ones alike. It also makes sense in a virtual machine or container for providing IoT functionality in some applications. The project is associated with the Linux Foundation and is covered by the more-permissive Apache 2.0 license.
The 1.11 version of the Zephyr OS is now out; it lets developers takes advantage of MPU and MMU support. Intel’s Andrew Boie gave a great introduction to this feature at the Embedded Linux Conference and OpenIoT Summit in Portland, Oreg. The conference sponsors include the Zephyr Project and other IoT OS platforms—like Apache’s myNewt with commercial support from Runtime, which incorporates myNewt into its cloud-based IoT platform. These platforms join commercial products like Express Logic’s ThreadX and Micrium’s µC/OS family of compact operating systems for IoT applications, along with open-source operating systems like Amazon’s FreeRTOS.
Zephyr 1.11 also introduces support for the Thread Group’s Thread protocol and using the OpenThread implementation. This takes advantage of the existing 802.15.4 drivers. The new OS also includes the start of IEEE POSIX support for the PS352 (Realtime Controller Product Standard) profile.
Zephyr does extensive parameter checking to make sure the data is being correctly referenced.
In addition, it includes over-the-air device firmware upgrade (OTA DFU) support using Bluetooth Low Energy (LE). This integration includes support for MCUmgr, along with the usual MCUboot bootloader that allows Zephyr images to be sent and programmed over a BLE connection. A management layer also supports remote access to the filesystem and to retrieve kernel statistics.
But back to memory protection.
The original Zephyr OS did not support memory protection. Applications were linked to the OS and all code in this static package had access to all memory and I/O. Bugs and nefarious code could easily lead to crashes, given that most code would be written in C and the system provided no additional protection.
Some microcontrollers still lack memory protection support. Zephyr will likely work well with these, but most popular microcontroller platforms and all microprocessor platforms have memory protection, if not a more advanced virtual memory support. Zephyr 1.11 can take advantage of these and provide advanced error detection for errors like stack overflow, as well as memory isolation.
Boie notes that the trick with Zephyr’s implementation was the need to retrofit memory protection into APIs that did not originally support it. The problem is compounded by the fact that resources are referenced by pointers, not handles. The lack of indirection means the system needs additional validity checks to prevent accidental operations on garbage data.
The memory protection support is designed to handle memory protection units (MPUs) like those found on Arm’s Cortex-M family. An MPU normally has fixed regions that can limit access or read/write operations. Memory management units (MMU) found with Cortex-A and x86 platforms provide virtual memory support and are more powerful than an MPU. The Zephyr support can be used with either, but will not take advantage of the more advanced features than an MMU can provide. At this point, the 1.11 version supports ARM, ARC, and x86 architectures.
Its approach allows the environment to be divided into the system supervisor and user applications. The latter can either be isolated or share memory with other user applications. The entire system is designed for static configuration that would be common in an embedded system. It also minimizes start-up time because the MPU/MMU configuration has already been created—it’s just a matter of copying it into the hardware at start-up. This reduces the likelihood that a third party can compromise the system after it is running.
Zephyr takes a layered approach to memory protection. Layer 1 is the boot-time MMU/MPU hardware set-up. It can protect code, address nonsense addresses, and catch null pointer references. Layer 2 provides supervisor-mode stack overflow detection. This uses a page to trap stack overflow references. Layer 3 addresses user-mode threads. User threads run in unprivileged mode that requires system calls. This is where some of the magic lies. Layer 4 is for the future, and will address virtual memory support and more dynamic memory management.
This approach assumes that all supervisor mode code, including the kernel and device drivers, have full access to the system. Toolchain headers and source code that are part of the kernel are assumed to be trusted. The system does not prevent denial of service (DoS) problems due to thread CPU starvation, since Zephyr does not have features like thread priority aging.
Zephyr’s magic occurs with the system API call processing (see figure). System calls run through a number of checks before the implementation code is run. It marshals data going to and from the system call unless the system is already running in supervisor mode, in which case this is all bypassed. Part of the magic is handled by C macros used with system calls and structures.
This build support is augmented by scripts used to build a system, allowing the system configuration to be created so it can be copied to the hardware memory management system on boot. The approach allows new system calls to be easily added.
Overall, the memory management enhancement is elegant and efficient. It adds a much-needed feature and one that is found in other operating systems that were designed from scratch to support MPUs. This is still a work in progress. Applications that demand dynamic memory and resource creation and management will need to wait for future enhancements.