mmap_io/anonymous.rs
1//! Anonymous (file-less) memory mappings.
2//!
3//! [`AnonymousMmap`] is a process-local, RW memory region with no
4//! backing file. Useful for scratch buffers shared between threads,
5//! large temporary allocations that should bypass the heap, or as the
6//! kernel-side substrate for fd-passing IPC patterns (where the
7//! anonymous mapping's fd is shared with a child process; not exposed
8//! here at 1.0 because the fd-passing surface is platform-specific).
9//!
10//! For shared memory between cooperating processes on the same host,
11//! the cross-platform pattern is a file-backed mapping where both
12//! processes map the same path. See `examples/10_ipc_shared_state.rs`
13//! and the `T6` cross-process integration test.
14//!
15//! ## Differences from [`MemoryMappedFile`]
16//!
17//! - No file descriptor / handle; no `AsFd`/`AsRawFd`/`AsHandle` impls.
18//! - No `resize` (`memmap2` does not support resizing anonymous maps).
19//! - No `flush` (volatile memory; nothing to persist).
20//! - No `path` (there is no path).
21//!
22//! Everything else (read, write, slice access, atomic views if the
23//! `atomic` feature is enabled) works identically.
24//!
25//! [`MemoryMappedFile`]: crate::mmap::MemoryMappedFile
26
27use memmap2::MmapMut;
28use parking_lot::RwLock;
29
30use crate::errors::{MmapIoError, Result};
31use crate::mmap::{MappedSlice, MappedSliceMut};
32use crate::utils::slice_range;
33
34// Mirrors the same constants used in `mmap.rs`. Kept module-local so a
35// future refactor of one does not silently drift the other.
36const ERR_ZERO_SIZE: &str = "Size must be greater than zero";
37
38#[cfg(target_pointer_width = "64")]
39const MAX_MMAP_SIZE: u64 = 128 * (1 << 40); // 128 TB
40
41#[cfg(target_pointer_width = "32")]
42const MAX_MMAP_SIZE: u64 = 2 * (1 << 30); // 2 GB
43
44/// Process-local anonymous memory mapping (no backing file).
45///
46/// Created via [`AnonymousMmap::new`]. The mapping is RW; pages are
47/// zero-initialized by the kernel on first touch. Memory is released
48/// when the value is dropped.
49///
50/// # Examples
51///
52/// ```
53/// use mmap_io::AnonymousMmap;
54///
55/// let mmap = AnonymousMmap::new(4096)?;
56/// mmap.update_region(0, b"hello")?;
57/// let mut buf = [0u8; 5];
58/// mmap.read_into(0, &mut buf)?;
59/// assert_eq!(&buf, b"hello");
60/// # Ok::<(), mmap_io::MmapIoError>(())
61/// ```
62pub struct AnonymousMmap {
63 map: RwLock<MmapMut>,
64 len: u64,
65}
66
67impl AnonymousMmap {
68 /// Allocate an anonymous RW mapping of `size` bytes.
69 ///
70 /// Pages are zero-initialized on first touch (kernel guarantee on
71 /// every supported platform: Linux, macOS, Windows).
72 ///
73 /// # Errors
74 ///
75 /// - [`MmapIoError::ResizeFailed`] if `size` is zero or exceeds the
76 /// platform maximum (128 TB on 64-bit, 2 GB on 32-bit).
77 /// - [`MmapIoError::Io`] if the kernel rejects the allocation
78 /// (out of address space, out of memory, etc.).
79 pub fn new(size: u64) -> Result<Self> {
80 if size == 0 {
81 return Err(MmapIoError::ResizeFailed(ERR_ZERO_SIZE.into()));
82 }
83 if size > MAX_MMAP_SIZE {
84 return Err(MmapIoError::ResizeFailed(format!(
85 "Size {size} exceeds maximum safe limit of {MAX_MMAP_SIZE} bytes"
86 )));
87 }
88 let len_usize = usize::try_from(size)
89 .map_err(|_| MmapIoError::ResizeFailed(format!("Size {size} does not fit in usize")))?;
90 let mmap = MmapMut::map_anon(len_usize)?;
91 Ok(Self {
92 map: RwLock::new(mmap),
93 len: size,
94 })
95 }
96
97 /// Length of the mapping in bytes.
98 #[must_use]
99 pub fn len(&self) -> u64 {
100 self.len
101 }
102
103 /// Whether the mapping has zero length. Always `false` for a
104 /// successfully-constructed `AnonymousMmap` (the constructor
105 /// rejects zero size); provided for API symmetry with
106 /// `MemoryMappedFile::is_empty`.
107 #[must_use]
108 pub fn is_empty(&self) -> bool {
109 self.len == 0
110 }
111
112 /// Copy `buf.len()` bytes from the mapping starting at `offset`
113 /// into `buf`.
114 ///
115 /// # Errors
116 ///
117 /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
118 /// mapping length.
119 pub fn read_into(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
120 let (start, end) = slice_range(offset, buf.len() as u64, self.len)?;
121 let guard = self.map.read();
122 buf.copy_from_slice(&guard[start..end]);
123 Ok(())
124 }
125
126 /// Write `data.len()` bytes into the mapping starting at `offset`.
127 ///
128 /// # Errors
129 ///
130 /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
131 /// mapping length.
132 pub fn update_region(&self, offset: u64, data: &[u8]) -> Result<()> {
133 let (start, end) = slice_range(offset, data.len() as u64, self.len)?;
134 let mut guard = self.map.write();
135 guard[start..end].copy_from_slice(data);
136 Ok(())
137 }
138
139 /// Borrow a read-only slice of the mapping.
140 ///
141 /// The returned [`MappedSlice`] holds a read lock for its lifetime;
142 /// concurrent reads are fine, but any concurrent `as_mut_slice` or
143 /// `update_region` blocks until the slice is dropped.
144 ///
145 /// # Errors
146 ///
147 /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
148 /// mapping length.
149 pub fn as_slice(&self, offset: u64, len: u64) -> Result<MappedSlice<'_>> {
150 let (start, end) = slice_range(offset, len, self.len)?;
151 let guard = self.map.read();
152 Ok(MappedSlice::guarded(guard, start..end))
153 }
154
155 /// Borrow a mutable slice of the mapping.
156 ///
157 /// The returned [`MappedSliceMut`] holds an exclusive write lock for
158 /// its lifetime; any concurrent reader or writer blocks until the
159 /// slice is dropped.
160 ///
161 /// # Errors
162 ///
163 /// Returns [`MmapIoError::OutOfBounds`] if the range exceeds the
164 /// mapping length.
165 pub fn as_mut_slice(&self, offset: u64, len: u64) -> Result<MappedSliceMut<'_>> {
166 let (start, end) = slice_range(offset, len, self.len)?;
167 let guard = self.map.write();
168 Ok(MappedSliceMut::guarded(guard, start..end))
169 }
170
171 /// Raw pointer to the start of the mapping.
172 ///
173 /// # Safety
174 ///
175 /// The caller must not retain the pointer beyond the lifetime of
176 /// this `AnonymousMmap`. Reads through the pointer require no
177 /// active mutable borrow elsewhere; writes through the pointer
178 /// require no other active borrow at all. Use this only when
179 /// bridging to FFI or unsafe code that needs a raw byte pointer.
180 #[must_use]
181 pub unsafe fn as_ptr(&self) -> *const u8 {
182 // SAFETY of the function (not of this line): documented above.
183 // This expression itself only takes a read lock and reads the
184 // mapping's base address; the read guard is dropped on return,
185 // which is the entire point of marking the function unsafe.
186 let guard = self.map.read();
187 guard.as_ptr()
188 }
189
190 /// Raw mutable pointer to the start of the mapping.
191 ///
192 /// # Safety
193 ///
194 /// Same constraints as [`as_ptr`](Self::as_ptr), plus: the caller
195 /// is responsible for ensuring no aliasing mutable references
196 /// exist for any byte they write to via this pointer.
197 #[must_use]
198 pub unsafe fn as_mut_ptr(&self) -> *mut u8 {
199 // SAFETY: see function docs. Lock guard is released on return.
200 let mut guard = self.map.write();
201 guard.as_mut_ptr()
202 }
203}
204
205impl std::fmt::Debug for AnonymousMmap {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 f.debug_struct("AnonymousMmap")
208 .field("len", &self.len)
209 .finish()
210 }
211}