I'm just an ordinary guy, and while being a child, my mother bought me a book, principles of operating systems.
The story, how I go to it, is very long. It's boggings from 60s, written by Betterbury Hanson.
So instead of political priests, I've been reading about ancient principles of operating systems.
Then, in 1999, I spotted an article called Q&AX Operating System on a single floppy, which was my first contact with micro kernels.
But unfortunately, back then, I wasn't a muse by the fact that it's a microchernel.
I've been a muse by the fact that it runs from a single floppy.
Then, I started my software developer career, and they're all from an industry to an industry.
And ended up in automotive where I got into contact with HardenBedit.
And what I've seen there wasn't very a muse, wasn't very amusing because the general status of HardenBedit of all power embedded is very poor regarding the cybersecurity.
A bulk of popular operating systems are running entirely unprotected.
And while some of them offer some solution to get memory protection running, even their own documentation states it's complicated.
Like, if you want it to separate two user space processes in the fire, it's like 150 lines of color or something like that.
So, and in general developers avoid it.
So, my question was, is it possible to come up with a way to bring memory protection to microcontrollers in a way which will be simple to use and still be usable for real world use cases?
And for this to happen, I came up with one concept.
If you want memory protection, you have some, you have to have some way to isolate processes, things apart.
So, I retrofit it, the concept of process.
It's nothing new for general microcorner or operating system community, but it's almost nonexistent in terms in a realms of, let's say, the fire.
And the way I did it was that I statically mapped parts of process onto memory protection unit regions.
The reason why I did it statically is that any other way of mapping, which would be dynamic, which compromise real time properties.
Because the very last thing you want to happen when you are serving, let's say, motor control routine is to have a page fault.
And another factor is that this is trivial to do an in a fully automatic way.
For example, here one process is considered everything which goes into one statically library.
So, as a developer doesn't have to do anything.
You simply think of it one library equals one process. Simple, well defined, it cannot communicate with each other.
You could say, okay, let's use kernel for that purpose.
Here's the problem.
Microcontrollers only have memory protection units.
And these make sharing memory, which you need for implementing Cisco's, and incredibly hard task.
You almost can share memory. You can't do copy and write or anything like that.
So, this also kind of prevents message passing and message passing based micro kernels.
So, I had to come up with some other primitive to allow any communication at all.
And this primitive is remote procedure calling.
Why? The way how CMRX implements remote procedure calling is that when client thread performs remote procedure call,
the thread itself is migrated into server process, with its access to the client process memory.
This avoids problem of sharing memory because the thread still has access to its original memory.
So, you can execute server routines on top of client memory.
Another characteristic here is that this RPC mechanism is object oriented.
So, you are not calling functions directly.
Rather, you are referring to method between interface, which can be supplied by third party.
And server implements these methods.
It turns out that all these can be checked at the compile time by the compiler.
No need for interface definition language. Everything is defined entirely in C.
You can check if the RPC call is calling a valid method within valid interface.
You can type check arguments.
You can type check if the implementation is implementing the interface, it states, it implements.
And you can also type check arguments there.
So, there is zero runtime overhead here.
The only thing you need to check is if this object reference is valid.
And client process didn't made it up to try to forge some malware or something like that.
And this still has one problem.
If client takes all his memory to the server, how this RPC will, for example, access its own memory.
For this RPC was extended to in a way that client doesn't take all its memory.
It only takes whatever is in the shared book.
Therefore, client has an option to opt what he wants to share.
And the remaining memory accessible during the remote procedure call comes from the server.
This way, you can copy data between server and client.
And the only overhead is the copy operation itself.
And you only have the overhead when you actually use it.
It has quite good, not best but quite good granularity.
Because the shared data are available only to the forced by the hardware to implement is lower lower lower
Interabrequest lines management because this cannot be done in the user space.
Everything else is run as a user space server.
Not one you may have as many user space servers as you want.
And for example, we don't have a bunch of calls in RTOS or features like Qs, NewTaxis.
We don't have anything of this in the kernel because everything can be implemented as a server.
So for example, we have a Q server which provides Qs.
It's completely a user space.
We don't even have newTaxis because this is provided by the hardware itself.
And this is pretty much all we have right now because it turns out we don't need anything else for the task we use this operating system for.
And this makes this kernel extremely portable.
Because for ARM you only need like maybe 10 objects out of CMC's headers.
And this is provided by pretty much any hell how out there.
So as long as you have these headers you are able to port CMRX there.
Currently it runs on pretty much anything from Cortex M0 plus to Cortex M33.
And I mentioned that it is my kernel.
So drivers also have to run in the user space and it turns it is possible.
You can give a process another region or segment and define addresses where it has the access to.
And if this memory region appears to be a memory region of some peripheral what you have a driver.
As of now with most of the hardware all the code of the driver can reside inside the process.
The only exception are interrupt service handlers.
And the reason for that is that if we dispatched them it would compromise latency.
You have this mechanism in for example in ZFIRE but it still has an option to do short path because sometimes this is fetching increases latency into an acceptable level.
And as for the API of the driver it is just another RPC service.
So we tested this and we were able to port tiny USB to run almost entirely in the user space.
And we only needed like five lines of modification inside the data and it was be code based aside from the operating system abstraction layer.
So I consider this to be a huge success.
And you already met a driver for CMRX.
This one line turns an ordinary process into the driver.
This line happens the addresses in this line happen to be a GPIO peripheral in RP2040 microcontroller.
So and so the Blinky needs this access to actually be able to bring with a lot of dialed.
So Blinky is actually a driver.
A very poor design of the driver but it's a driver.
And for use cases use cases on the right side are those which are already in production.
So naturally if you have a mixed criticality system where you have two parts.
One part may be communicating with the outside world and another part is drive something critical.
You may want to isolate them.
This works quite well in CMRX.
Components are easy to implement also due to the properties of the RPC mechanism.
And new use cases which emerged as the system was developed and are not entirely written are is the use of CMRX as a hypervisor.
Because the kernel itself is so small under 8 kilobytes that you could actually use it as a hypervisor.
And you may say that a process is actually a virtual machine.
And stuff and other operating system into the virtual machine.
You have two modes. One mode would be a par virtualization because you will have the same problem as with drivers.
That interrupt service handleers of the virtual machine would run with privileges of the hypervisor.
And that's not a nice thing but trust zone can help here.
I have, I want to replicate what QNX does.
But there is more allowable work to be done yet.
So this is in the pipeline.
Question?
It's not exactly exactly to me how remapping things work with RPCs.
If you can go back to that slide.
So how remapping works with the RPC?
You have a slide?
One slide?
One more. You have shared in the stack.
Yes.
Are all RPCs synchronous and you must carry the stack of the client to the server and the server can spot for the client stack?
Yes.
So when you do this RPC call, this thread essentially runs with the rights of the server process.
And for this MMU regions stack is carried over because you still have to be able to return.
And there's one another region here called shared and this is also carried over.
And the data and BSS are replaced by the server process.
So to answer your question, it is synchronous.
And yes, server process has full access to the client stack.
It's one of the downsides of not having an MMU.
Yeah, it's a constraint.
It's a result of constraints of the hardware.
So then if this is, you don't have your client and your server having separate stacks.
It's a single stack that you would be pushing about to when you call it wrong.