elfenpiff
Joined 2 years ago
Joined 2 years ago
Just in time for Christmas, we are excited to announce the v0.5 release of iceoryx2 – a ultra-fast and reliable inter-process communication (ipc) library written in Rust, with language bindings for C, C++ and soon Python!
But what is iceoryx2, and why should you care? If you’re looking for a solution to:
- Communicate between processes in a service-oriented manner, - Transfer gigabytes of data between processes efficiently, - payload-independent, consistently low latency - Wake up processes, send notifications, and handle events seamlessly, - Build a decentralized, robust system with minimal IPC overhead, - Use a communication library that doesn’t spawn threads, - Communicate without serialization overhead, - Ensure your system remains operational even when some processes crash, - Work with C, C++, and Rust processes in a single project (with Python and Go support coming next year!),
...then iceoryx2 is the library you’ve been waiting for!
Happy Hacking,
Elfenpiff
- GitHub iceoryx2: https://github.com/eclipse-iceoryx/iceoryx2 - GitHub ROS 2 iceoryx2_rmw binding: https://github.com/ekxide/rmw_iceoryx2 - Release Announcement Article: https://ekxide.io/blog/iceoryx2-0-5-release/ - crates.io: https://crates.io/crates/iceoryx2 - docs.rs: https://docs.rs/iceoryx2/latest/iceoryx2/
Yes, we are using shared memory, and I agree that shared memory is a challenge but there are some mechanisms that can make it secure.
The main problem with shared memory is, that one process can corrupt the data structure while another process is consuming it. Even verifying the contents of the data structure is insufficient since it can always be corrupted afterwards. We have named the problem "modify-after-delivery problem" - a sender modifies the data after it has been delivered to a receiver.
This can be handled with:
1. memfd: The sender acquires it, writes its payload, seals it so that it is read-only and then transfers the file descriptor to all receivers. The receiver can verify the read-only seal with fcntl. Since linux guarantees us that it cannot be reverted the receiver can now safely consume the data. This allows it to be used even in a zero-trust environment. [1] provides a good introduction (see the File-Sealing IPC subsection). 2. Memory protection keys [2]: I do not have too much experience with them, but as far as I understand, they solve the problem with mprotect, meaning, the sender can call mprotect and make the segment read only for it self, but the receiver has no way of verifying it or to prevent the sender from calling mprotect again and granting it read/write access again to corrupt the data.
So, the approach is that a sender acquires shared memory, writes its payload into it, makes it read-only, and then transfers it to the receivers.
> Shared memory works as a transport if you either assume that all parties are trusted (in which case why do IPC in the first place?
Robustness is another use case. In mission-critical systems you trust each process but a crash caused by a bug in one sub-system shall not bring down the whole system. So you split up the monolith in many processes and the overall system survives if one process goes down or deadlocks, assuming you have a shared memory library that itself is safe. If you detect a process crash, you can restart it and continue operations.
[1] https://dvdhrm.wordpress.com/2014/06/10/memfd_create2/ [2] https://www.kernel.org/doc/html/latest/core-api/protection-k...
* All IPC resources are represented in the file system and have a global naming scheme. So if you would like to perform a service discovery, you take a look at the `/tmp/iceoryx2/services` list all service toml files that you are allowed to access and handle them.
* Connecting to a service means, under the hood, opening a specific shared memory identified via a naming scheme, adding yourself to the participant list, and receiving/sending data.
* Crashing/resource cleanup is done decentrally by every process that has the permissions to perform them.
* In a central/broker architecture you would have the central broker that checks this in a loop.
* In a decentralized architecture, we defined certain sync points where this is checked. These points are placed so that you check the misbehavior before it would affect you. For instance, when a sender shall send you a message every second but you do not receive it, you would actively check if it is still alive. Other sync points are, when an iceoryx2 node is created or you connect or disconnect to a service.
The main point is that the API is decentralized but you can always use it in a central daemon if you like - but you don't have to. It is optional.
We aim to support Windows so iceoryx2 can be used safely and securely in a desktop environment.
The unix-domain socket has the advantage that you can combine it with external non-iceoryx events, but you pay with a small performance hit. The semaphore event is usually faster. As a user, you can configure your IPC service architecture and use the mechanism that suits you best (but I have to admit that we have not yet documented this in detail). For the zero_copy service variants, it is done here: https://github.com/eclipse-iceoryx/iceoryx2/blob/main/iceory....
One of the key operations in these algorithms is a memory copy using core::ptr::copy. However, this results in undefined behavior if one process reads the data while another process writes to it concurrently. Even if our lock-free algorithm reliably detects such a race, iceoryx2 cannot depend on undefined behavior in a safety-critical system. This blog post introduces our solution: a byte-wise atomic wrapper that enables well-defined concurrent copy operations. It also shows how it can be used to implement a simple sequence lock.