I need to share memory between C and Rust app. Rust - producer, C - consumer.
In C I would create a shared memory region and pass it to Rust for writing.
On C side I use something like this:
fd = shm_open(STORAGE_ID, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
res = ftruncate(fd, STORAGE_SIZE);
addr = mmap(NULL, STORAGE_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
// read from that memory after it is written by Rust app
char data[STORAGE_SIZE];
memcpy(data, addr, STORAGE_SIZE);
what do I do on Rust side to open memory with STORAGE_ID and write to it? I guess it is along the lines using this, but could not find solid example:
https://docs.rs/libc/0.2.77/libc/fn.shm_open.html
https://docs.rs/memmap/0.6.1/memmap/struct.Mmap.html
https://doc.rust-lang.org/nightly/std/primitive.slice.html#method.clone_from_slice
Basically, I need to do something like this, but set.c written in Rust: POSIX shared memory IPC example (shm_open, mmap), working on Linux and macOS
Using Rust's libc
crate we have access to the same functions that the C process has access to, so the code in Rust would look very similar to the code you'd write if writing the producer in C.
The code below demonstrates Rust code as both producer and consumer. Producer and consumer are combined this way only for the sake of having a simple example that can run from the command line to verify the Rust producer code works.
In the code below, the block at the top of the main()
function is the same code as in the question post. Both the consumer and producer execute this to get the shared region of memory.
The first branch of the if
block sets up the original process as a consumer, which launches a child process with the same executable. The child's path of execution will follow the else
branch and write to the shared region of memory.
The parent blocks until the child is done, then reads the shared region of memory and prints it.
use libc::{close, ftruncate, memcpy, mmap, shm_open, strncpy};
use libc::{MAP_SHARED, O_RDWR, O_CREAT, PROT_WRITE, S_IRUSR, S_IWUSR};
use libc::{c_char, c_void, off_t, size_t};
use std::{env, ptr, str};
use std::process::Command;
use std::error::Error;
const STORAGE_ID : *const c_char = b"MY_MEM_ID\0".as_ptr() as *const c_char;
const STORAGE_SIZE : size_t = 128;
fn main() -> Result<(), Box<dyn Error>>
{
let args = env::args().collect::<Vec<_>>();
let (fd, addr) = unsafe {
let null = ptr::null_mut();
let fd = shm_open(STORAGE_ID, O_RDWR | O_CREAT, (S_IRUSR | S_IWUSR) as size_t);
let _res = ftruncate(fd, STORAGE_SIZE as off_t);
let addr = mmap(null, STORAGE_SIZE, PROT_WRITE, MAP_SHARED, fd, 0);
(fd, addr)
};
if args.len() == 1 {
// Consumer...
let exec_path = &args[0];
// Start producer process. Block until done.
let output = Command::new(exec_path).arg("Producer...").output()?;
println!("Producer stdout : {}", str::from_utf8(&output.stdout)?);
let mut data = [0_u8; STORAGE_SIZE];
let pdata = data.as_mut_ptr() as *mut c_char;
unsafe {
strncpy(pdata, addr as *const c_char, STORAGE_SIZE);
close(fd);
}
println!("Producer message : {}", str::from_utf8(&data)?);
} else {
// Producer...
let data = b"Hello, World!\0";
let pdata = data.as_ptr() as *const c_void;
unsafe {
memcpy(addr, pdata, data.len());
}
print!("Done.");
}
Ok(())
}
Output:
Producer stdout : Done.
Producer message : Hello, World!