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}