process_sync/
shared_memory.rs

1use libc::{c_void, mmap, munmap, MAP_ANONYMOUS, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE};
2use std::{mem::size_of, ptr::null_mut};
3
4/// An object that can be shared between processes.
5///
6/// After spawning child process (using `fork()`, `clone()`, etc.) updates to this object will be seen by both processes.
7/// This is achieved by allocating memory using mmap with `MAP_SHARED` flag.
8///
9/// For more details see [man page](https://man7.org/linux/man-pages/man2/mmap.2.html).
10///
11/// # Example
12/// ```rust
13/// # use std::error::Error;
14/// # use std::thread::sleep;
15/// # use std::time::Duration;
16/// #
17/// # use libc::fork;
18/// #
19/// # use process_sync::private::check_libc_err;
20/// # use process_sync::SharedMemoryObject;
21/// #
22/// # fn main() -> Result<(), Box<dyn Error>> {
23/// #
24/// let mut shared = SharedMemoryObject::new(123)?;
25///
26/// let pid = unsafe { fork() };
27/// assert!(pid >= 0);
28///
29/// if pid == 0 {
30///     assert_eq!(*shared.get(), 123);
31///     *shared.get_mut() = 456;
32///     sleep(Duration::from_millis(40));
33///     assert_eq!(*shared.get(), 789);
34/// } else {
35///     sleep(Duration::from_millis(20));
36///     assert_eq!(*shared.get(), 456);
37///     *shared.get_mut() = 789;
38/// }
39/// #
40/// #     Ok(())
41/// # }
42/// ```
43pub struct SharedMemoryObject<T> {
44    ptr: *mut T,
45}
46
47impl<T: Sync + Send> SharedMemoryObject<T> {
48    /// Allocates shared memory and moves `obj` there.
49    ///
50    /// # Errors
51    /// If allocation fails returns error from [`last_os_error`].
52    ///
53    /// [`last_os_error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.last_os_error
54    pub fn new(obj: T) -> std::io::Result<Self> {
55        let addr = allocate_shared_memory(size_of::<T>())?;
56
57        let addr = addr as *mut T;
58        unsafe { *addr = obj };
59
60        Ok(Self { ptr: addr })
61    }
62
63    /// Returns reference to underlying object.
64    ///
65    /// # Safety
66    /// See [`get_mut`](#method.get_mut).
67    pub fn get(&self) -> &T {
68        unsafe { &*self.ptr }
69    }
70
71    /// Returns mutable reference to underlying object.
72    ///
73    /// # Safety
74    /// This function (and [`get`](#method.get)) is always safe to call, but access to data under returned reference
75    /// must be somehow synchronized with another processes to avoid data race.
76    pub fn get_mut(&mut self) -> &mut T {
77        unsafe { &mut *self.ptr }
78    }
79}
80
81impl<T> Drop for SharedMemoryObject<T> {
82    fn drop(&mut self) {
83        // every process owning shared memory object must free it individually
84        free_shared_memory(self.ptr as *mut c_void, size_of::<T>())
85            .expect("cannot munmap() shared memory");
86    }
87}
88
89fn allocate_shared_memory(len: usize) -> std::io::Result<*mut c_void> {
90    let addr = unsafe {
91        mmap(
92            null_mut(),
93            len,
94            PROT_READ | PROT_WRITE,
95            MAP_SHARED | MAP_ANONYMOUS,
96            -1,
97            0,
98        )
99    };
100    if addr == MAP_FAILED {
101        return Err(std::io::Error::last_os_error());
102    }
103    Ok(addr)
104}
105
106fn free_shared_memory(addr: *mut c_void, len: usize) -> std::io::Result<()> {
107    let ret = unsafe { munmap(addr, len) };
108    if ret != 0 {
109        return Err(std::io::Error::last_os_error());
110    }
111    Ok(())
112}