Processes

CMRX recognizes two different modes of execution. Privileged and non-privileged (a.k.a usermode). Privileged mode is used exclusively by CMRX kernel and interrupt service handlers and is extempt from memory protection. Everything else runs in usermode. Most of usermode is composed of processes.

A process is a container serving as protection domain. All entities living inside process can access each other. Everything outside this protection domain has no access to entities inside the process with two exceptions:

  • CMRX allows shared libraries - it is possible to call a function from outside of a process directly as long as it is not touching hard-coded memory areas. This enables seamless use of most vendor HALs and standard libraries as long as they don’t mantain state in internally-allocated objects.
  • Access via RPC - RPC calls grant access to parts of calling process which author explicitly allowed to be shared with callees. This share is active only during RPC call.

That said, CMRX process is more like an isolation delimiter rather than storage for execution contexts and as such, it is allocated statically at compile-time. Thus, (for now) processes cannot be created nor destroyed in runtime. While processes can’t be destroyed, there is no need for a process to remain in existence to have a thread. It is perfectly fine for a process to define no threads at all and even if process defines a thread, its termination is not a reason for process destruction. Model where a process has no threads at all is useful for processes that host RPC services, for example worker-less drivers.

Determining process boundaries

One of major problems with handling memory protection on microcontrollers is management of memory protection unit. It has to be defined manually which is a tedious and error-prone task. To avoid this, CMRX introduces simple model to determine what belongs to process and what doesn’t:

Process resides inside a static library. It takes ownership of any data residing in this library and grants access to this data to all threads that are created in this library. From developer’s perspective the answer to the question: Does this particular piece of code have access to this particular piece of data? is simple: If code is executed in thread owned by same library as the data lives in, then yes. In any other case, no.

Threads

Processes are just protection delimiters themselves providing no execution contexts. Execution contexts are provided by threads. CMRX provides a multi-threaded scheduler. Each process is free to create any amount of threads it needs up to the compile-time limit defined by firmware developer. Threads are owned by a process. Each thread is scheduled independently from other threads. Each thread is given its own stack which is inaccessible to any other thread (even from the same process).

All threads share access to all memory belonging to process they are owned by. This in turn means they share access to all memory-resident objects residing in process’ memory, such as mutexes.

Priorities

As threads are scheduled independently from each other and CMRX implements priority-driven scheduler, scheduling priorities are properties of threads, not processes. One process can have multiple threads of vastly different priorities handling tasks of different importance.

Shared memory

Code often needs to share memory between various execution contexts. CMRX doesn’t support the concept of unconditionally shared memory. Instead it supports concept of sharing memory with RPC server. Also in this case, memory protection is configured fully automatically with no intervention from developer. This method of sharing memory enabled unlimited amount of shared memory blocks to exist in system without exhausting available memory protection hardware.

Shared libraries

It is possible to link a plain library to CMRX firmware and use the code of this library from any process without tripping memory protection. The precondition for this mode of operation is that library is not allocating its own memory. This applies both to HAL libraries and to purely functional libraries. Anything that allocates memory needs to be wrapped into its own process and talked to via RPC.