This blog is written by Roy Jamil, Embedded Systems Training Engineer at Ac6.
User mode overview
User mode plays a significant role in keeping applications safe and reliable by enforcing memory access permissions and restricting the execution of privileged instructions. Zephyr brings convenience and simplicity to handling user threads. This series of three blog posts will explore user mode in Zephyr using practical examples.
User mode refers to the execution context in which applications run with limited privileges. In contrast to kernel mode, where the code has unrestricted access to all hardware and software resources, user mode restricts application access to essential resources and requires explicit permissions to interact with hardware or memory outside its allocated range.
Key features of running in user mode include:
- Limited Access: Threads operate with restricted access to the system’s memory and hardware to prevent unintended or harmful changes to system settings.
- Isolation: User mode threads are isolated from others, which helps prevent a faulty or compromised thread from affecting others or the entire system.
- Security: Threads must ask for permission from kernel threads if they need to perform higher-privilege operations, like using certain hardware components.
User mode in Zephyr is based on either an MPU (Memory Protection Unit) or an MMU (Memory Management Unit). The key features we’ll focus on are memory domains and syscalls.
User mode application structure
In Zephyr, the term “app” refers to your project that contains all the code you’re working on, specified in the main CMakeLists.txt file. This app organizes and configures how your project is built.
In user mode within Zephyr, enabling CONFIG_USERSPACE=y allows for the creation of multiple “logical apps”. These are basically collections of user space threads grouped under a single memory domain. This setup facilitates structured and secure management of various functionalities within the application.
Threads in each logical app are isolated from those in another logical app, preventing them from accessing variables defined in different memory domains (associated with other logical apps). Meanwhile, kernel threads can access all memory addresses without needing explicit permission.
Memory domains and partitions
Memory domains in Zephyr are designed to control memory access from user threads. Each domain consists of one or more partitions. A partition is a contiguous memory region where global variables are defined and placed. The same partition can be specified in multiple memory domains, allowing it to be shared among them.
Practical example
This code was tested on NXP FRDM-MCXN947. Check this github repository if you want to clone the project.
Conclusion
User mode in Zephyr offers significant benefits for managing the security and stability of applications. Using memory domains and partitions, Zephyr effectively isolates user space threads, limiting their interaction with system-wide resources and other threads. The practical examples provided, such as the setup on the NXP FRDM-MCXN947, illustrate how these concepts can be easily applied in your application.
In the practical example, we had to manually specify the placement of each variable within specific partitions. In the following article, we will rewrite the code into separate modules and automatically allocate all variables of the entire logical app to their respective partitions. This approach simplifies scaling in larger projects.
If you want to learn more about Zephyr, check out our comprehensive Zephyr training course, covering everything from fundamentals to advanced topics. Click here for more information.