1#![cfg(target_os = "linux")]
13
14use std::io;
15use std::os::unix::io::AsRawFd;
16use std::path::{Path, PathBuf};
17use std::ptr;
18
19pub const HUGEPAGE_2MB: usize = 2 * 1024 * 1024;
21
22pub const HUGEPAGE_1GB: usize = 1024 * 1024 * 1024;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum HugepageSize {
28 Mb2,
30 Gb1,
33}
34
35impl HugepageSize {
36 fn bytes(self) -> usize {
37 match self {
38 Self::Mb2 => HUGEPAGE_2MB,
39 Self::Gb1 => HUGEPAGE_1GB,
40 }
41 }
42
43 fn mmap_flag(self) -> libc::c_int {
44 match self {
45 Self::Mb2 => libc::MAP_HUGETLB | (21 << 26),
49 Self::Gb1 => libc::MAP_HUGETLB | (30 << 26),
50 }
51 }
52}
53
54pub struct HugepageRegion {
56 ptr: *mut u8,
57 len: usize,
58}
59
60unsafe impl Send for HugepageRegion {}
61unsafe impl Sync for HugepageRegion {}
62
63impl HugepageRegion {
64 pub fn allocate(pages: usize, size: HugepageSize) -> io::Result<Self> {
66 assert!(pages > 0);
67 let len = pages * size.bytes();
68 let prot = libc::PROT_READ | libc::PROT_WRITE;
69 let flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | size.mmap_flag();
70 let raw = unsafe { libc::mmap(ptr::null_mut(), len, prot, flags, -1, 0) };
71 if raw == libc::MAP_FAILED {
72 return Err(io::Error::last_os_error());
73 }
74 Ok(Self { ptr: raw as *mut u8, len })
75 }
76
77 pub fn as_mut_slice(&mut self) -> &mut [u8] {
78 unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
79 }
80
81 pub fn as_slice(&self) -> &[u8] {
82 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
83 }
84
85 pub fn len(&self) -> usize { self.len }
86 pub fn is_empty(&self) -> bool { self.len == 0 }
87}
88
89impl Drop for HugepageRegion {
90 fn drop(&mut self) {
91 unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
92 }
93}
94
95impl crate::spsc_ring::RegionOwner for HugepageRegion {
96 fn region_ptr(&mut self) -> *mut u8 {
97 self.as_mut_slice().as_mut_ptr()
98 }
99 fn region_len(&self) -> usize {
100 self.len()
101 }
102}
103
104pub struct SharedHugepageRegion {
116 _file: std::fs::File,
117 ptr: *mut u8,
118 len: usize,
119 path: PathBuf,
120 owner: bool,
121}
122
123unsafe impl Send for SharedHugepageRegion {}
124unsafe impl Sync for SharedHugepageRegion {}
125
126impl SharedHugepageRegion {
127 fn map(file: &std::fs::File, len: usize) -> io::Result<*mut u8> {
128 let raw = unsafe {
129 libc::mmap(
130 ptr::null_mut(),
131 len,
132 libc::PROT_READ | libc::PROT_WRITE,
133 libc::MAP_SHARED,
134 file.as_raw_fd(),
135 0,
136 )
137 };
138 if raw == libc::MAP_FAILED {
139 Err(io::Error::last_os_error())
140 } else {
141 Ok(raw as *mut u8)
142 }
143 }
144
145 pub fn create(
153 path: impl AsRef<Path>,
154 pages: usize,
155 size: HugepageSize,
156 ) -> io::Result<Self> {
157 assert!(pages > 0);
158 let path = path.as_ref().to_path_buf();
159 let len = pages * size.bytes();
160 match std::fs::OpenOptions::new()
161 .read(true)
162 .write(true)
163 .create_new(true)
164 .open(&path)
165 {
166 Ok(file) => {
167 file.set_len(len as u64)?;
170 let ptr = Self::map(&file, len)?;
171 Ok(Self { _file: file, ptr, len, path, owner: true })
172 }
173 Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
174 let file = std::fs::OpenOptions::new()
175 .read(true)
176 .write(true)
177 .open(&path)?;
178 let deadline = std::time::Instant::now() + crate::mmf_attach::INIT_WAIT;
181 loop {
182 let actual = file.metadata()?.len();
183 if actual == len as u64 {
184 break;
185 }
186 if actual > len as u64 {
187 return Err(io::Error::new(
188 io::ErrorKind::InvalidData,
189 "hugetlbfs region size does not match the requested pages",
190 ));
191 }
192 if std::time::Instant::now() >= deadline {
193 return Err(io::Error::new(
194 io::ErrorKind::TimedOut,
195 "the region's creator did not finish initializing it",
196 ));
197 }
198 std::thread::yield_now();
199 }
200 let ptr = Self::map(&file, len)?;
201 Ok(Self { _file: file, ptr, len, path, owner: false })
202 }
203 Err(e) => Err(e),
204 }
205 }
206
207 pub fn reset(
212 path: impl AsRef<Path>,
213 pages: usize,
214 size: HugepageSize,
215 ) -> io::Result<Self> {
216 assert!(pages > 0);
217 let path = path.as_ref().to_path_buf();
218 let len = pages * size.bytes();
219 let file = std::fs::OpenOptions::new()
220 .read(true)
221 .write(true)
222 .create(true)
223 .truncate(true)
224 .open(&path)?;
225 file.set_len(len as u64)?;
226 let ptr = Self::map(&file, len)?;
227 Ok(Self { _file: file, ptr, len, path, owner: true })
228 }
229
230 pub fn open(
233 path: impl AsRef<Path>,
234 pages: usize,
235 size: HugepageSize,
236 ) -> io::Result<Self> {
237 let path = path.as_ref().to_path_buf();
238 let len = pages * size.bytes();
239 let file = std::fs::OpenOptions::new()
240 .read(true)
241 .write(true)
242 .open(&path)?;
243 if file.metadata()?.len() != len as u64 {
244 return Err(io::Error::new(
245 io::ErrorKind::InvalidData,
246 "hugetlbfs region size does not match the requested pages",
247 ));
248 }
249 let ptr = Self::map(&file, len)?;
250 Ok(Self { _file: file, ptr, len, path, owner: false })
251 }
252
253 pub fn as_mut_slice(&mut self) -> &mut [u8] {
254 unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
255 }
256
257 pub fn as_slice(&self) -> &[u8] {
258 unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
259 }
260
261 pub fn len(&self) -> usize { self.len }
262 pub fn is_empty(&self) -> bool { self.len == 0 }
263}
264
265impl Drop for SharedHugepageRegion {
266 fn drop(&mut self) {
267 unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
268 if self.owner {
270 std::fs::remove_file(&self.path).ok();
271 }
272 }
273}
274
275impl crate::spsc_ring::RegionOwner for SharedHugepageRegion {
276 fn region_ptr(&mut self) -> *mut u8 {
277 self.as_mut_slice().as_mut_ptr()
278 }
279 fn region_len(&self) -> usize {
280 self.len()
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 fn hugetlbfs_dir() -> Option<PathBuf> {
295 let candidate = Path::new("/dev/hugepages");
296 let probe = candidate.join(format!("subetha_probe_{}", std::process::id()));
298 match std::fs::File::create(&probe) {
299 Ok(_) => {
300 std::fs::remove_file(&probe).ok();
301 Some(candidate.to_path_buf())
302 }
303 Err(_) => None,
304 }
305 }
306
307 #[test]
308 fn shared_hugepage_region_two_maps_share_memory() {
309 let Some(dir) = hugetlbfs_dir() else {
310 eprintln!("skipping: no writable hugetlbfs mount at /dev/hugepages");
311 return;
312 };
313 let path = dir.join(format!("subetha_shr_{}", std::process::id()));
314
315 let mut a = match SharedHugepageRegion::create(&path, 1, HugepageSize::Mb2) {
317 Ok(r) => r,
318 Err(e) => {
319 eprintln!("skipping: hugetlbfs create failed ({e}); reserve pages");
320 return;
321 }
322 };
323 assert_eq!(a.len(), HUGEPAGE_2MB);
324
325 let mut b = SharedHugepageRegion::open(&path, 1, HugepageSize::Mb2)
327 .expect("open second map of the same hugetlbfs file");
328
329 a.as_mut_slice()[0] = 0xAB;
331 a.as_mut_slice()[HUGEPAGE_2MB - 1] = 0xCD;
332 assert_eq!(b.as_slice()[0], 0xAB, "B must see A's write at offset 0");
333 assert_eq!(
334 b.as_slice()[HUGEPAGE_2MB - 1], 0xCD,
335 "B must see A's write at the last byte",
336 );
337
338 b.as_mut_slice()[42] = 0x7E;
340 assert_eq!(a.as_slice()[42], 0x7E, "A must see B's write");
341 }
342
343 #[test]
347 fn second_create_attaches_and_keeps_contents() {
348 let Some(dir) = hugetlbfs_dir() else {
349 eprintln!("skipping: no writable hugetlbfs mount at /dev/hugepages");
350 return;
351 };
352 let path = dir.join(format!("subetha_attach_{}", std::process::id()));
353
354 let mut a = match SharedHugepageRegion::create(&path, 2, HugepageSize::Mb2) {
355 Ok(r) => r,
356 Err(e) => {
357 eprintln!("skipping: hugetlbfs create failed ({e}); reserve pages");
358 return;
359 }
360 };
361 a.as_mut_slice()[7] = 0x5A;
362
363 let b = SharedHugepageRegion::create(&path, 2, HugepageSize::Mb2)
364 .expect("second create attaches");
365 assert_eq!(b.as_slice()[7], 0x5A, "attach lost region contents");
366 assert!(
367 SharedHugepageRegion::create(&path, 1, HugepageSize::Mb2).is_err(),
368 "a different page count must be refused",
369 );
370
371 drop(b);
373 assert!(path.exists(), "attacher drop unlinked the creator's path");
374
375 let fresh = SharedHugepageRegion::reset(&path, 2, HugepageSize::Mb2)
376 .expect("reset");
377 assert_eq!(fresh.as_slice()[7], 0, "reset kept region contents");
378 drop(fresh);
379 drop(a);
380 }
381}