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}