|
Last modified on Sat Jul 22 11:30:02 2023 UTC. | Improve this page |
µOS++ is a POSIX-like, portable, open source, royalty-free, multi-tasking real-time operating system intended for 32/64-bits embedded applications.
µOS++ is written in modern C++ 11, with C++ applications in mind, but also provides equally functional C APIs.
µOS++ is the third edition of µOS++, offering all of the services expected from a modern real-time system including resource management, synchronization, inter-thread communication, and more. µOS++ also offers many features not found in many other real-time systems, such as both C++ and C APIs, POSIX-like threads, POSIX synchronisation objects, use of standard C++ memory allocators, and more.
Here is a list of features provided by µOS++.
µOS++ is an open source project, provided under the very permissive terms of the MIT license.
The source code availability not only helps debugging, but also greatly improves the general understanding of the entire run environment, resulting in better and more robust applications.
µOS++ provides services via multiple APIs, covering both C++ and C applications.
The supported APIs are:
The µOS++ RTOS C API allows to write plain C applications, even if the RTOS core is written in C++.
The ARM CMSIS RTOS API allows to run legacy applications, written for the ARM RTOS API.
The ISO C++ Threads API provides a convenient way to run ISO standard code with almost no changes.
The design of the core APIs was heavily influenced by the POSIX threads specs. From this point of view, µOS++ can also be seen as POSIX++, a C++ version of POSIX.
POSIX compatibility is a very important feature, since POSIX is a mature and well established portable standard, and the experience gained while using POSIX on larger systems can improve productivity of embedded application developers too.
µOS++ also implements the standard ISO C++ thread API, including the chrono
functionality.
The ISO C++ 14882:2011 introduced a standard API for threads, mutexes, condition variables, locks, time related functions, etc.
This API is relatively easy to use, the overhead is low, and, if sharing code with some server environments is required, this may be a very interesting solution.
The APIs used by µOS++ are highly consistent. Once familiar with the coding conventions used, it is simple to predict the functions to call for the services required, and even predict which arguments are needed. For example, in the C API a pointer to an object is always the first argument, most functions return a POSIX error code, etc.
This translates into increased productivity and improved code readability over time.
µOS++ implements a priority based, preemptive, multi-tasking scheduler and therefore, µOS++ always runs the most important ready-to-run thread.
For real-time systems, this improves the reaction speed and helps meeting the required deadlines.
µOS++ allows multiple threads to run at the same priority level. When multiple threads at the same priority are ready to run, they are scheduled in a round-robin way.
This scheduling algorithm provides a fair distribution of the CPU time to common threads that do not have special needs, and can all share a common priority.
µOS++ has a number of internal data structures and variables that it needs to access atomically. To ensure this, µOS++ disables interrupts for very short periods, to ensure minimal interrupt latency.
Keeping the critical sections short gives a chance to fast interrupts to occur and system responsiveness is increased.
To avoid subtle bugs, the µOS++ critical sections are designed to be easily nested, saving the initial status during enter and restoring it while exiting.
This save implementation allows to invoke µOS++ system calls from any environment, including from callbacks coming from code using poorly implemented critical sections (for example those using counters to tell when to re-enable interrupts).
To be noted that the reverse is not true, calling library functions that implement critical sections using counters from environments where the interrupts are disabled possibly introduces a synchronisation problem, since the those functions will erroneously enable the interrupts.
The footprint (both code and data) can be adjusted based on the requirements of the application. This is performed at compile time through many available #defines
(see os-app-config.h
). µOS++ also performs a number of run-time checks on arguments passed to its services, like not passing NULL pointers, not calling thread level services from ISRs, that arguments are within allowable range, and options specified are valid, etc. These checks can be disabled (at compile time) to further reduce the code footprint and improve performance.
By a careful design, only those functions that are required are linked into an application, keeping the ROM size small.
By design µOS++ is highly portable; it was developed mostly on macOS and its tests are constantly run on both 32 and 64-bits platforms.
µOS++ was designed especially for embedded systems and can be ROMed along with the application code.
This is important for modern microcontrollers, which include large amounts of flash internally, and can execute code from there, without having to copy it to RAM.
µOS++ itself is fully statically allocated, it does not require dynamic memory, which makes it a perfect fit for special applications that cannot tolerate the risks associated with fragmentation.
For objects that require additional memory, the user has full control on using either statically or dynamically allocated memory.
µOS++ can be used to build special applications that are not allowed to use dynamic allocations.
In the µOS++ C++ API, all objects requiring additional memory can be configured to use a custom memory allocator, so, at the limit, each object can be allocated using a separate allocator.
µOS++ supports an unlimited number of threads. From a practical standpoint, however, the number of threads is actually limited by the amount of memory (both code and data space) that the processor has access to. Each thread requires its own stack; µOS++ provides features to allow stack growth to be monitored at run-time.
The default stack size and the minimum stack size can be configured by the user.
µOS++ allows for any number of threads, semaphores, mutexes, event flags, message queues, timers, memory pools, etc. The user at run-time allocates all system objects, either as global static objects, stack objects, or dynamically allocated objects.
Not having to statically define at compile time the maximum number of objects is very convenient.
Mutexes with POSIX functionality are provided for resource management. Both normal and recursive mutexes are available. Mutexes can be configured to have built-in priority inheritance, which eliminate unbounded priority inversions.
Timers are countdown counters that perform a user-definable action upon counting down to 0. Each timer can have its own action and, if a timer is periodic, the timer is automatically reloaded and the action is executed every time the countdown reaches zero.
µOS++ allows an ISR or thread to directly signal a thread. This avoids having to create an intermediate system object such as a semaphore or event flag just to signal a thread, and results in better performance.
Threads can include a user-definable user data structure, where the application can store any custom data.
The definition of this data structure is a plain C struct
, and the content is fully under user control.
µOS++ verifies that NULL pointers are not passed, that the user is not calling thread-level services from ISRs, that arguments are within allowable range, that options specified are valid, that a handler is passed to the proper object as part of the arguments to services that manipulate the desired object, and more.
Generally these additional validations are enabled in the Debug configurations, and disabled in Release, to save significant space and some run-time.
µOS++ has built-in features to measure the execution time of each thread, stack usage, the number of times a thread executes, CPU usage, and more.
This can be used monitor the system behaviour at run-time, and detect possible busy wait inefficient implementations.
All of the µOS++ blocking calls have versions that include timeouts, which help avoid deadlocks.
Each µOS++ system object can have a name associated with it. This makes it easy to recognize what the object is assigned to. Assign a name to a thread, a semaphore, a mutex, an event flag group, a message queue, a memory pool, and a timer. The object name can have any length, but must be NULL terminated.
These names can be inspected during debug sessions, to identify objects, and can also be displayed by advanced instrumentation tools.
This feature allows thread aware debuggers to examine and display µOS++ variables and data structures in a user-friendly way. (Work in progress).
This feature allows integration with instrumentation tools like SEGGER SystemView. (Work in progress).
The classic solution for handling time in most embedded systems is to use the scheduler timer. By default in µOS++ the resolution for this clock is 1 ms, acceptable for most applications.
µOS++ also defines a standard API to access the real-time clock, which has a resolution of 1 sec, and can be used to configure timeouts even when the CPU is in deep sleep.
For accurate time measurements, down to the CPU cycle level, a high resolution timer derived from the scheduler timer, is also available.
µOS++ provides a common API for all clocks, and all synchronisation objects that can be configured to use any of the available clocks.