xdp_socket/mmap.rs
1//! # Memory Mapping for UMEM
2//!
3//! ## Purpose
4//!
5//! This module provides safe abstractions for creating and managing memory-mapped (`mmap`)
6//! regions, specifically for the AF_XDP UMEM (Userspace Memory). The UMEM is a critical
7//! component for achieving zero-copy performance.
8//!
9//! ## How it works
10//!
11//! It defines an `OwnedMmap` struct that encapsulates a raw pointer to a memory-mapped
12//! region and its size. This struct's implementation handles the low-level `libc::mmap`
13//! call for allocation and `libc::munmap` in its `Drop` implementation to ensure the
14//! memory is safely released. It also includes logic to check for and optionally use
15//! huge pages to back the UMEM, which can improve performance by reducing TLB misses.
16//!
17//! ## Main components
18//!
19//! - `OwnedMmap`: A struct that acts as a safe owner of a memory-mapped region.
20//! - `get_hugepage_info()`: A helper function that parses `/proc/meminfo` to determine if
21//! huge pages are available for use.
22
23use std::fs::File;
24use std::io::{BufRead as _, BufReader};
25use std::{io, ptr};
26
27/// A safe wrapper for a memory-mapped region.
28///
29/// This struct owns the memory-mapped pointer and ensures that `munmap` is called
30/// when it goes out of scope, preventing memory leaks.
31pub struct OwnedMmap(
32 /// A raw pointer to the beginning of the memory-mapped area.
33 pub *mut libc::c_void,
34 /// The total size of the memory-mapped area in bytes.
35 pub usize,
36);
37
38impl OwnedMmap {
39 /// Constructs a new `OwnedMmap` from a raw pointer and size.
40 ///
41 /// This is a low-level constructor. Prefer `mmap` for new allocations.
42 pub fn new(ptr: *mut libc::c_void, size: usize) -> Self {
43 OwnedMmap(ptr, size)
44 }
45
46 /// Creates a new memory-mapped region.
47 ///
48 /// This function allocates a new anonymous, private memory-mapped region suitable
49 /// for use as a UMEM. It can optionally be backed by huge pages.
50 ///
51 /// # How it works
52 ///
53 /// It first determines whether to use huge pages. If `huge_page` is `None`, it
54 /// checks `/proc/meminfo` for available huge pages. It then calculates the
55 /// required size aligned to the page size (standard or huge) and calls `libc::mmap`
56 /// with the appropriate flags (`MAP_HUGETLB` if using huge pages).
57 /// On success, it returns an `OwnedMmap` that manages the allocated memory.
58 pub fn mmap(size: usize, huge_page: Option<bool>) -> Result<Self, io::Error> {
59 // if not specified use huge pages, check if they are available
60 let huge_tlb = if let Some(yes) = huge_page {
61 yes
62 } else {
63 let info = get_hugepage_info()?;
64 if let (Some(x), Some(2048)) = (info.free, info.size_kb) {
65 x > 0
66 } else {
67 false
68 }
69 };
70 let page_size = {
71 if huge_tlb {
72 2 * 1024 * 1024 // 2MB huge page size
73 } else {
74 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
75 }
76 };
77 let aligned_size = (size + page_size - 1) & !(page_size - 1);
78 let ptr = unsafe {
79 libc::mmap(
80 ptr::null_mut(),
81 aligned_size,
82 libc::PROT_READ | libc::PROT_WRITE,
83 libc::MAP_PRIVATE
84 | libc::MAP_ANONYMOUS
85 | if huge_tlb {
86 libc::MAP_HUGETLB | libc::MAP_HUGE_2MB
87 } else {
88 0
89 },
90 -1,
91 0,
92 )
93 };
94 if ptr == libc::MAP_FAILED {
95 return Err(io::Error::last_os_error());
96 }
97 Ok(OwnedMmap(ptr, aligned_size))
98 }
99
100 /// Returns the raw pointer to the memory-mapped region.
101 pub fn as_void_ptr(&self) -> *mut libc::c_void {
102 self.0
103 }
104
105 /// Returns a mutable raw pointer to the memory-mapped region as a byte slice.
106 pub fn as_u8_ptr(&mut self) -> *mut u8 {
107 self.0 as *mut u8
108 }
109
110 /// Returns the size of the memory-mapped region in bytes.
111 pub fn len(&self) -> usize {
112 self.1
113 }
114
115 /// Returns `true` if the memory-mapped region has a size of zero.
116 pub fn is_empty(&self) -> bool {
117 self.1 == 0
118 }
119}
120
121impl Drop for OwnedMmap {
122 fn drop(&mut self) {
123 unsafe {
124 if self.0 != libc::MAP_FAILED && !self.0.is_null() {
125 let res = libc::munmap(self.0, self.1);
126 if res < 0 {
127 log::error!("Failed to unmap memory: {}", io::Error::last_os_error());
128 }
129 }
130 }
131 }
132}
133
134/// Contains information about the system's huge page configuration.
135#[derive(Debug, Default)]
136pub struct HugePageInfo {
137 /// The size of a huge page in kilobytes.
138 pub size_kb: Option<u64>,
139 /// The total number of huge pages configured in the system.
140 pub total: Option<u64>,
141 /// The number of free (available) huge pages.
142 pub free: Option<u64>,
143}
144
145/// Parses `/proc/meminfo` to get information about huge pages.
146///
147/// # How it works
148///
149/// It reads the `/proc/meminfo` pseudo-file line by line, looking for keys
150/// `Hugepagesize`, `HugePages_Total`, and `HugePages_Free`. It parses their
151/// corresponding values and returns them in a `HugePageInfo` struct.
152pub fn get_hugepage_info() -> io::Result<HugePageInfo> {
153 let file = File::open("/proc/meminfo")?;
154 let reader = BufReader::new(file);
155 let mut info = HugePageInfo::default();
156 for line in reader.lines() {
157 let line = line?;
158 let parts: Vec<&str> = line.split(':').collect();
159
160 if parts.len() == 2 {
161 let key = parts[0].trim();
162 let value_str = parts[1].trim().trim_end_matches(" kB");
163 match key {
164 "Hugepagesize" => info.size_kb = Some(value_str.parse().map_err(io::Error::other)?),
165 "HugePages_Total" => {
166 info.total = Some(value_str.parse().map_err(io::Error::other)?)
167 }
168 "HugePages_Free" => info.free = Some(value_str.parse().map_err(io::Error::other)?),
169 _ => {} // Ignore other lines
170 }
171 }
172 }
173 Ok(info)
174}