⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/ipc/
posix_shm.rs

1//! POSIX Shared Memory (shm_open / shm_unlink)
2//!
3//! Provides named shared memory objects accessible via `/dev/shm` semantics.
4//! Used by Wayland compositors and other IPC-heavy applications for zero-copy
5//! buffer sharing between processes.
6//!
7//! This module implements the kernel-side of the POSIX shared memory API:
8//! - `shm_open()`: Create or open a named shared memory object
9//! - `shm_unlink()`: Remove a named shared memory object
10//! - `ftruncate()`: Set the size of the shared memory object
11//! - `mmap(MAP_SHARED)`: Map the shared memory into a process address space
12//!
13//! Named objects are stored in a global registry keyed by name. Each object
14//! tracks its physical backing frames and per-process virtual mappings.
15
16use alloc::{
17    collections::BTreeMap,
18    string::{String, ToString},
19};
20use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
21
22use spin::Mutex;
23
24use crate::{
25    error::{KernelError, KernelResult},
26    process::ProcessId,
27};
28
29// ---------------------------------------------------------------------------
30// Constants
31// ---------------------------------------------------------------------------
32
33/// Maximum name length for a shared memory object.
34pub const SHM_NAME_MAX: usize = 255;
35
36/// Maximum number of concurrent shared memory objects.
37pub const SHM_MAX_OBJECTS: usize = 256;
38
39/// Maximum size of a single shared memory object (256 MB).
40pub const SHM_MAX_SIZE: usize = 256 * 1024 * 1024;
41
42// ---------------------------------------------------------------------------
43// Data Structures
44// ---------------------------------------------------------------------------
45
46/// Open flags for shm_open (mirror POSIX O_CREAT, O_EXCL, O_RDONLY, O_RDWR).
47#[derive(Debug, Clone, Copy)]
48pub struct ShmOpenFlags {
49    /// Create the object if it does not exist.
50    pub create: bool,
51    /// Fail if object already exists (used with create).
52    pub exclusive: bool,
53    /// Read-only access.
54    pub read_only: bool,
55}
56
57impl ShmOpenFlags {
58    /// O_RDWR | O_CREAT
59    pub const CREATE_RDWR: Self = Self {
60        create: true,
61        exclusive: false,
62        read_only: false,
63    };
64
65    /// O_RDONLY
66    pub const RDONLY: Self = Self {
67        create: false,
68        exclusive: false,
69        read_only: true,
70    };
71}
72
73/// A per-process mapping of a shared memory object.
74#[derive(Debug, Clone)]
75pub struct ShmMapping {
76    /// Virtual address in the process's address space.
77    pub virt_addr: u64,
78    /// Mapping size (may be less than the object size).
79    pub size: usize,
80    /// Read-only mapping.
81    pub read_only: bool,
82}
83
84/// A named shared memory object.
85pub struct ShmObject {
86    /// Object name (without leading slash).
87    pub name: String,
88    /// Unique object ID.
89    pub id: u64,
90    /// Size in bytes (set by ftruncate).
91    pub size: usize,
92    /// Physical frame number of the backing memory (contiguous).
93    pub phys_frame: usize,
94    /// Number of physical frames allocated.
95    pub num_frames: usize,
96    /// Reference count (number of open descriptors).
97    pub ref_count: AtomicU32,
98    /// Per-process virtual mappings.
99    pub mappings: Mutex<BTreeMap<u64, ShmMapping>>, // key = ProcessId.0
100    /// Creator process ID.
101    pub owner: ProcessId,
102    /// Whether the object has been unlinked (will be destroyed when ref_count
103    /// reaches 0).
104    pub unlinked: bool,
105}
106
107// ---------------------------------------------------------------------------
108// Global Registry
109// ---------------------------------------------------------------------------
110
111/// Global counter for unique object IDs.
112static NEXT_SHM_ID: AtomicU64 = AtomicU64::new(1);
113
114/// Global registry of named shared memory objects.
115static SHM_REGISTRY: Mutex<BTreeMap<String, ShmObject>> = Mutex::new(BTreeMap::new());
116
117// ---------------------------------------------------------------------------
118// API
119// ---------------------------------------------------------------------------
120
121/// Create or open a named shared memory object.
122///
123/// Returns the object ID on success. The object starts with size 0;
124/// use `shm_truncate()` to set its size before mapping.
125pub fn shm_open(name: &str, flags: ShmOpenFlags, owner: ProcessId) -> KernelResult<u64> {
126    if name.is_empty() || name.len() > SHM_NAME_MAX {
127        return Err(KernelError::InvalidArgument {
128            name: "name",
129            value: "empty or exceeds SHM_NAME_MAX",
130        });
131    }
132
133    let mut registry = SHM_REGISTRY.lock();
134
135    if let Some(existing) = registry.get(name) {
136        if flags.exclusive {
137            return Err(KernelError::AlreadyExists {
138                resource: "shm_object",
139                id: existing.id,
140            });
141        }
142        existing.ref_count.fetch_add(1, Ordering::Relaxed);
143        return Ok(existing.id);
144    }
145
146    // Object does not exist.
147    if !flags.create {
148        return Err(KernelError::NotFound {
149            resource: "shm_object",
150            id: 0,
151        });
152    }
153
154    if registry.len() >= SHM_MAX_OBJECTS {
155        return Err(KernelError::ResourceExhausted {
156            resource: "shm_objects",
157        });
158    }
159
160    let id = NEXT_SHM_ID.fetch_add(1, Ordering::Relaxed);
161    let obj = ShmObject {
162        name: name.to_string(),
163        id,
164        size: 0,
165        phys_frame: 0,
166        num_frames: 0,
167        ref_count: AtomicU32::new(1),
168        mappings: Mutex::new(BTreeMap::new()),
169        owner,
170        unlinked: false,
171    };
172
173    registry.insert(name.to_string(), obj);
174
175    println!("[SHM] Created shared memory object '{}' (id={})", name, id);
176    Ok(id)
177}
178
179/// Remove a named shared memory object.
180///
181/// The object's name is removed from the registry immediately, but the
182/// backing memory is not freed until all references are closed (ref_count
183/// reaches 0).
184pub fn shm_unlink(name: &str) -> KernelResult<()> {
185    let mut registry = SHM_REGISTRY.lock();
186
187    if let Some(obj) = registry.get_mut(name) {
188        obj.unlinked = true;
189        let refs = obj.ref_count.load(Ordering::Relaxed);
190        if refs == 0 {
191            // Free physical frames if allocated.
192            if obj.num_frames > 0 {
193                let frame = crate::mm::FrameNumber::new(obj.phys_frame as u64);
194                let _ = crate::mm::FRAME_ALLOCATOR
195                    .lock()
196                    .free_frames(frame, obj.num_frames);
197            }
198            registry.remove(name);
199            println!("[SHM] Unlinked and destroyed '{}'", name);
200        } else {
201            println!(
202                "[SHM] Unlinked '{}' (deferred destroy, {} refs remaining)",
203                name, refs
204            );
205        }
206        Ok(())
207    } else {
208        Err(KernelError::NotFound {
209            resource: "shm_object",
210            id: 0,
211        })
212    }
213}
214
215/// Set the size of a shared memory object (analogous to ftruncate).
216///
217/// Allocates (or reallocates) the physical backing memory. Existing
218/// mappings are NOT updated; callers must re-map after truncating.
219pub fn shm_truncate(name: &str, size: usize) -> KernelResult<()> {
220    if size > SHM_MAX_SIZE {
221        return Err(KernelError::InvalidArgument {
222            name: "size",
223            value: "exceeds SHM_MAX_SIZE",
224        });
225    }
226
227    let mut registry = SHM_REGISTRY.lock();
228    let obj = registry.get_mut(name).ok_or(KernelError::NotFound {
229        resource: "shm_object",
230        id: 0,
231    })?;
232
233    // Free old frames if any.
234    if obj.num_frames > 0 {
235        let frame = crate::mm::FrameNumber::new(obj.phys_frame as u64);
236        let _ = crate::mm::FRAME_ALLOCATOR
237            .lock()
238            .free_frames(frame, obj.num_frames);
239        obj.phys_frame = 0;
240        obj.num_frames = 0;
241    }
242
243    if size == 0 {
244        obj.size = 0;
245        return Ok(());
246    }
247
248    // Allocate new contiguous frames.
249    let num_frames = size.div_ceil(4096);
250    let frame = crate::mm::FRAME_ALLOCATOR
251        .lock()
252        .allocate_frames(num_frames, None)
253        .map_err(|_| KernelError::OutOfMemory {
254            requested: size,
255            available: 0,
256        })?;
257
258    // Zero the memory.
259    let phys_addr = frame.as_u64() * 4096;
260    let virt_addr = crate::mm::phys_to_virt_addr(phys_addr);
261    // SAFETY: virt_addr points to freshly allocated frames mapped by bootloader.
262    unsafe {
263        core::ptr::write_bytes(virt_addr as *mut u8, 0, num_frames * 4096);
264    }
265
266    obj.phys_frame = frame.as_u64() as usize;
267    obj.num_frames = num_frames;
268    obj.size = size;
269
270    println!(
271        "[SHM] Truncated '{}' to {} bytes ({} frames)",
272        name, size, num_frames
273    );
274    Ok(())
275}
276
277/// Close a reference to a shared memory object.
278///
279/// Decrements the reference count. If the object was unlinked and this
280/// was the last reference, the backing memory is freed.
281pub fn shm_close(name: &str) -> KernelResult<()> {
282    let mut registry = SHM_REGISTRY.lock();
283    let should_destroy = if let Some(obj) = registry.get(name) {
284        let prev = obj.ref_count.fetch_sub(1, Ordering::Release);
285        prev == 1 && obj.unlinked
286    } else {
287        return Ok(());
288    };
289
290    if should_destroy {
291        if let Some(obj) = registry.remove(name) {
292            if obj.num_frames > 0 {
293                let frame = crate::mm::FrameNumber::new(obj.phys_frame as u64);
294                let _ = crate::mm::FRAME_ALLOCATOR
295                    .lock()
296                    .free_frames(frame, obj.num_frames);
297            }
298            println!("[SHM] Destroyed '{}' (last reference closed)", name);
299        }
300    }
301    Ok(())
302}
303
304/// Get information about a shared memory object.
305pub fn shm_stat(name: &str) -> KernelResult<(u64, usize, u32)> {
306    let registry = SHM_REGISTRY.lock();
307    let obj = registry.get(name).ok_or(KernelError::NotFound {
308        resource: "shm_object",
309        id: 0,
310    })?;
311    Ok((obj.id, obj.size, obj.ref_count.load(Ordering::Relaxed)))
312}
313
314/// Get the number of active shared memory objects.
315pub fn shm_count() -> usize {
316    SHM_REGISTRY.lock().len()
317}