Skip to main content

thread_create_vmars/
thread_create_vmars.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use starnix_uapi::errno;
6use starnix_uapi::errors::Errno;
7use std::sync::Arc;
8use {fuchsia_runtime, zx};
9
10// Allocate VMARs in 1GiB blocks. Even for the reduced size of the shared asapce 1GiB is not too
11// wasteful and should still sufficiently minimize the number of allocations done.
12const VMAR_SIZE: usize = 1 * 1024 * 1024 * 1024;
13// We do not know the exact size of the allocations that libc will perform in the VMARs. To
14// compensate we way over approximate the size of the probe. In the worst case this wastes 32MiB
15// of a 1GiB vmar, which is a minor percentage.
16const PROBE_SIZE: usize = 32 * 1024 * 1024;
17
18/// Small wrapper around a zx::vmar for creating and probing a compact vmar.
19///
20/// Use `CompactVmar::new()` to create a new instance.
21///
22/// # Safety
23///
24/// This implements `Drop` and will explicitly destroy the underlying VMAR. Clients must ensure that
25/// all mappings within this VMAR are cleared/unmapped before this object is dropped. Failure to do
26/// so will trigger a panic.
27#[derive(Debug)]
28struct CompactVmar {
29    vmar: Arc<zx::Vmar>,
30}
31
32impl CompactVmar {
33    fn new() -> Option<CompactVmar> {
34        fuchsia_runtime::vmar_root_self()
35            .allocate(
36                0,
37                VMAR_SIZE,
38                zx::VmarFlags::CAN_MAP_READ
39                    | zx::VmarFlags::CAN_MAP_WRITE
40                    | zx::VmarFlags::CAN_MAP_SPECIFIC
41                    | zx::VmarFlags::COMPACT,
42            )
43            .ok()
44            .map(|(vmar, _)| CompactVmar { vmar: Arc::new(vmar) })
45    }
46    /// See if this VMAR can support an allocation of the requested size.
47    fn probe(&self, size: usize) -> bool {
48        if let Ok((child, _addr)) = self.vmar.allocate(0, size, zx::VmarFlags::CAN_MAP_READ) {
49            // SAFETY: just allocated this
50            unsafe {
51                let _ = child.destroy();
52            }
53            true
54        } else {
55            false
56        }
57    }
58}
59
60impl Drop for CompactVmar {
61    fn drop(&mut self) {
62        // Check that all child VMARs and mappings have been removed before destroying the VMARs.
63        // We pass an empty buffer to `maps` to get the count of available maps in `avail`.
64        let mut buf = [];
65        match self.vmar.maps(&mut buf) {
66            Err(e) => panic!("Failed to retrieve vmar maps info: {e}"),
67            Ok((_, _, avail)) => {
68                if avail != 1 {
69                    panic!(
70                        "Vmar has unexpected allocations (found {avail}). Destroying is likely to \
71                        cause threads to crash"
72                    );
73                }
74            }
75        }
76
77        // SAFETY: Just validated that there are no children (i.e. mappings).
78        unsafe {
79            let _ = self.vmar.destroy();
80        }
81    }
82}
83
84/// Implements a list of VMARs that grows, but never shrinks, to support additional allocations.
85///
86/// This does not actually track allocations and frees, but rather allows for performing a probe
87/// just prior to allocation to find a VMAR that has space. Although probing is less efficient, this
88/// is useful if the exact amounts allocated, and when they are freed, cannot be easily tracked.
89#[derive(Debug, Default)]
90pub struct GrowableVmars {
91    vmars: std::collections::VecDeque<CompactVmar>,
92}
93
94impl GrowableVmars {
95    /// Returns a handle to a VMAR that has at least `PROBE_SIZE` bytes of free space.
96    ///
97    /// The returned handle is suitable for passing to `thrd_set_zx_create_handles` via the
98    /// `thrd_zx_create_handles_t` structure.
99    pub fn probe(&mut self) -> Result<Arc<zx::Vmar>, Errno> {
100        // Search out existing VMARs for one with free space. The full vmars get placed back on the
101        // end of the list to ensure future searches will try them last.
102        if let Some(index) = self.vmars.iter().position(|v| v.probe(PROBE_SIZE)) {
103            self.vmars.rotate_left(index);
104        } else {
105            // All the vmars were full. Allocate a new VMAR to return.
106            let vmar = CompactVmar::new().ok_or_else(|| errno!(ENOMEM))?;
107            self.vmars.push_front(vmar);
108        }
109        Ok(self.vmars.front().expect("populated above").vmar.clone())
110    }
111    /// Creates a new empty `GrowableVmars`.
112    pub fn new() -> GrowableVmars {
113        Default::default()
114    }
115}
116
117/// Holds VMARs suitable for creating new threads.
118///
119/// Provides three different VMAR allocators, one for each of the allocations performed on thread
120/// creation. See `thrd_zx_create_handles_t` in `zircon/threads.h` for details.
121#[derive(Debug, Default)]
122pub struct ThreadCreateVmars {
123    /// VMARs for the machine stack.
124    pub machine_stack: GrowableVmars,
125    /// VMARs for the security stack (unsafe stack on x86, shadow call stack on others).
126    pub security_stack: GrowableVmars,
127    /// VMARs for the thread control block and thread-local storage.
128    pub thread_block: GrowableVmars,
129}
130
131impl ThreadCreateVmars {
132    /// Creates a new `ThreadCreateVmars` with empty VMAR lists.
133    pub fn new() -> ThreadCreateVmars {
134        Default::default()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    // Wrapper around a zx::Vmar for testing that calls destroy on drop.
143    struct VmarWrap(zx::Vmar);
144
145    impl Drop for VmarWrap {
146        fn drop(&mut self) {
147            // SAFETY: No mappings were ever placed in this VMAR.
148            unsafe {
149                let _ = self.0.destroy();
150            }
151        }
152    }
153
154    fn fill_vmar(raw_handle: zx::sys::zx_handle_t) -> Vec<VmarWrap> {
155        // SAFETY: We are only creating an unowned handle to inspect/allocate children.
156        // The handle lifetime is managed by GrowableVmars.
157        let vmar = unsafe { zx::Unowned::<zx::Vmar>::from_raw_handle(raw_handle) };
158        let mut handles = Vec::new();
159        loop {
160            // Allocate a child VMAR to occupy space.
161            // We use the same parameters as probe() to ensure we effectively block it.
162            match vmar.allocate(0, PROBE_SIZE, zx::VmarFlags::CAN_MAP_READ) {
163                Ok((child, _)) => handles.push(VmarWrap(child)),
164                Err(_) => break,
165            }
166        }
167        handles
168    }
169
170    #[fuchsia::test]
171    fn test_thread_create_vmars_init() {
172        let mut tcv = ThreadCreateVmars::new();
173        // Just verify we can probe each.
174        assert!(tcv.machine_stack.probe().is_ok());
175        assert!(tcv.security_stack.probe().is_ok());
176        assert!(tcv.thread_block.probe().is_ok());
177    }
178
179    #[fuchsia::test]
180    fn test_probe_growth_and_rotation() {
181        let mut vmars = GrowableVmars::new();
182
183        // Probe for an initial VMAR.
184        let h1 = vmars.probe().expect("failed to probe");
185        let h1_raw = h1.raw_handle();
186
187        // Exhaust the provided VMAR
188        let allocs1 = fill_vmar(h1_raw);
189
190        // Probing should yield a new VMAR.
191        let h2 = vmars.probe().expect("failed to probe 2");
192        let h2_raw = h2.raw_handle();
193        assert_ne!(h1_raw, h2_raw);
194        assert_eq!(vmars.vmars.len(), 2);
195        // Current order should be [h2, h1] because h1 failed and h2 is new.
196        assert_eq!(vmars.vmars[0].vmar.raw_handle(), h2_raw);
197        assert_eq!(vmars.vmars[1].vmar.raw_handle(), h1_raw);
198
199        // Now exhaust this VMAR.
200        let _allocs2 = fill_vmar(h2_raw);
201
202        // Once again, probing should yield another VMAR.
203        let h3 = vmars.probe().expect("failed to probe 3");
204        let h3_raw = h3.raw_handle();
205        assert_ne!(h3_raw, h1_raw);
206        assert_ne!(h3_raw, h2_raw);
207        assert_eq!(vmars.vmars.len(), 3);
208        assert_eq!(vmars.vmars[0].vmar.raw_handle(), h3_raw);
209
210        // Exhaust this VMAR.
211        let _allocs3 = fill_vmar(h3_raw);
212
213        // Free our allocations from the original VMAR.
214        drop(allocs1);
215
216        // Probing should now find h1 again, and not allocate a new VMAR. A consequence of searching
217        // should therefore leave the vmars in the order [h1, h3, h2]
218        let h_rotated = vmars.probe().expect("failed to probe rotated");
219        assert_eq!(h_rotated.raw_handle(), h1_raw);
220        assert_eq!(vmars.vmars[0].vmar.raw_handle(), h1_raw);
221        assert_eq!(vmars.vmars[1].vmar.raw_handle(), h3_raw);
222        assert_eq!(vmars.vmars[2].vmar.raw_handle(), h2_raw);
223    }
224}