mapped_file/
hugetlb.rs

1//! Huge-page interface for `MappedFile<T>` and `MemoryFile`.
2use super::*;
3use std::{
4    mem,
5    hash,
6    num::NonZeroUsize,
7    fs,
8    path::{Path, PathBuf},
9    fmt, error,
10};
11use libc::{
12    c_int,
13    MAP_HUGE_SHIFT,
14};
15
16/// Location in which the kernel exposes available huge-page sizes.
17pub const HUGEPAGE_LOCATION: &'static str = "/sys/kernel/mm/hugepages/";
18
19/// Represents a statically defined `MAP_HUGE_*` flag.
20#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
21#[repr(transparent)]
22pub struct MapHugeFlag(c_int);
23
24/// Error for when `HugePage::compute_huge()` fails.
25#[derive(Debug)]
26pub struct HugePageCalcErr(());
27
28impl TryFrom<HugePage> for MapHugeFlag
29{
30    type Error = HugePageCalcErr;
31
32    #[inline] 
33    fn try_from(from: HugePage) -> Result<Self, Self::Error>
34    {
35	from.compute_huge().ok_or(HugePageCalcErr(()))
36    }
37}
38
39
40impl error::Error for HugePageCalcErr{}
41impl fmt::Display for HugePageCalcErr
42{
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
44    {
45	f.write_str("Invalid huge-page specification")
46    }
47}
48
49
50impl Default for MapHugeFlag
51{
52    #[inline] 
53    fn default() -> Self {
54	Self(MAP_HUGE_SHIFT)
55    }
56}
57#[inline(always)]
58const fn log2(n: usize) -> usize
59{
60    usize::BITS as usize -  n.leading_zeros() as usize - 1
61}
62
63impl MapHugeFlag
64{
65    /// Create from a raw `MAP_HUGE_*` flag.
66    ///
67    /// # Safety
68    /// The passed `flag` **must** be a valid bitmask representing a `MAP_HUGE_*` value **only**.
69    #[inline] 
70    pub const unsafe fn from_mask_unchecked(flag: c_int) -> Self
71    {
72	Self(flag)
73    }
74
75    /// The kernel's default huge-page size.
76    pub const HUGE_DEFAULT: Self = Self(MAP_HUGE_SHIFT);
77    /// Predefined `MAP_HUGE_2MB` mask,
78    pub const HUGE_2MB: Self = Self(libc::MAP_HUGE_2MB);
79    /// Predefined `MAP_HUGE_1GB` mask,
80    pub const HUGE_1GB: Self = Self(libc::MAP_HUGE_1GB);
81    
82    /// Calculate a `MAP_HUGE_*` flag from a size (in kB).
83    #[inline(always)] 
84    pub const fn calculate(kilobytes: NonZeroUsize) -> Self
85    {
86	Self((log2(kilobytes.get()) << (MAP_HUGE_SHIFT as usize)) as c_int)
87    }
88
89    /// Attempt to calculate `MAP_HUGE_*` flag from a size (in kB).
90    #[inline]
91    pub const fn try_calculate(kilobytes: usize) -> Option<Self>
92    {
93	match kilobytes {
94	    0 => None,
95	    kilobytes => {
96		if let Some(shift) = log2(kilobytes).checked_shl(MAP_HUGE_SHIFT as u32) {
97		    if shift <= c_int::MAX as usize {
98			return Some(Self(shift as c_int));
99		    }
100		}
101		None
102	    }
103	}
104    }
105
106    /// Attempt to calculate `MAP_HUGE_*`, or use `HUGE_DEFAULT` on failure.
107    ///
108    /// # Note
109    /// If `kilobytes` is `0`, or there is a calculation overflow, then `HUGE_DEFAULT` is returned.
110    #[inline] 
111    pub const fn calculate_or_default(kilobytes: usize) -> Self
112    {
113	match Self::try_calculate(kilobytes) {
114	    None => Self::HUGE_DEFAULT,
115	    Some(x) => x,
116	}
117    }
118
119    /// Check if this is the smallest huge-page size the kernel supports.
120    #[inline] 
121    pub const fn is_default(&self) -> bool
122    {
123	self.0 == Self::HUGE_DEFAULT.0
124    }
125
126    /// Get the `MAP_HUGE_*` mask.
127    #[inline(always)] 
128    pub const fn get_mask(self) -> c_int
129    {
130	self.0
131    }
132}
133
134impl From<MapHugeFlag> for c_int
135{
136    #[inline] 
137    fn from(from: MapHugeFlag) -> Self
138    {
139	from.0
140    }
141}
142
143/// Provides an arbitrary huge-page size and mapping flag for that size.
144///
145/// Can store or create a `MAP_HUGE_*` flag for use with `mmap()`, (`MappedFile`) or `memfd_create()` (`file::MemoryFile::with_hugetlb()`)
146///
147/// # Usage
148/// Main usage is for generating a `MapHugeFlag` via `compute_huge()`. This function may fail (rarely), so a `TryInto` impl exists for `MapHugeFlag` as well.
149#[derive(Default, Clone, Copy)]
150pub enum HugePage {
151    /// A staticly presented `MAP_HUGE_*` flag. See `MapHugeFlag` for details.
152    Static(MapHugeFlag),
153    /// A dynamically calculated `MAP_HUGE_*` flag from an arbitrary size *in kB*.
154    ///
155    /// # Safety
156    /// The kernel must actually support huge-pages of this size.
157    ///
158    /// If `kilobytes` is 0, or an overflow in calculation happens, then this is identical to `Smallest`.
159    Dynamic{ kilobytes: usize },
160    /// The smallest huge-page size on the system
161    #[default]
162    Smallest,
163    /// The largest huge-page size on the system 
164    Largest,
165    /// Use a callback function to select the huge-page size (*in kB*) from an *ordered* (lowest to highest) enumeration of all available on the system.
166    Selected(for<'r> fn (&'r [usize]) -> Option<&'r usize>),
167}
168
169impl hash::Hash for HugePage {
170    #[inline] 
171    fn hash<H: hash::Hasher>(&self, state: &mut H) {
172	mem::discriminant(self).hash(state);
173	match self {
174	    Self::Static(hpf) => hpf.hash(state),
175	    Self::Dynamic { kilobytes } => kilobytes.hash(state),
176	    Self::Selected(func) => ptr::hash(func as *const _, state),
177	    _ => (),
178	};
179    }
180}
181
182impl fmt::Debug for HugePage
183{
184    #[inline] 
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
186    {
187	f.debug_tuple("HugePage")
188	    .field({
189		let v: &dyn fmt::Debug = match &self {
190		    Self::Static(ref huge) => huge,
191		    Self::Dynamic { ref kilobytes } => kilobytes,
192		    Self::Smallest => &"<smallest>",
193		    Self::Largest => &"<largest>",
194		    Self::Selected(_) => &"<selector>",
195		};
196		v
197	    })
198	    .finish()
199    }
200}
201
202
203impl Eq for HugePage {}
204impl PartialEq for HugePage
205{
206    #[inline] 
207    fn eq(&self, other: &Self) -> bool
208    {
209	match (self, other) {
210	    (Self::Static(hpf), Self::Static(hpf2)) => hpf == hpf2,
211	    (Self::Dynamic { kilobytes }, Self::Dynamic { kilobytes: kilobytes2 }) => kilobytes == kilobytes2,
212	    (Self::Selected(func), Self::Selected(func2)) => ptr::eq(func, func2),
213	    _ => mem::discriminant(self) == mem::discriminant(other),
214	}
215    }
216}
217
218impl HugePage
219{
220    /// Compute the `MapHugeFlag` from this huge-page specification.
221    ///
222    /// # Returns
223    /// * `None` - If there was an error in computing the correct flag.
224    /// * `Some` - If the computation was successful.
225    /// 
226    /// # Panics
227    /// In debug builds, if scanning the system for huge-pages fails after `SYSTEM_HUGEPAGES` has already failed.
228    #[inline]  // This call is recursive, but also can be large for variant `Selected`, which we have factored out into a non-inline local function. All other variants are small enough for this to be okay.
229    pub fn compute_huge(self) -> Option<MapHugeFlag>
230    {
231	use HugePage::*;
232	match self {
233	    Dynamic { kilobytes: 0 } |
234	    Smallest |
235	    Static(MapHugeFlag::HUGE_DEFAULT) => Some(MapHugeFlag::HUGE_DEFAULT),
236	    Static(mask) => Some(mask),
237	    Dynamic { kilobytes } => {
238		MapHugeFlag::try_calculate(kilobytes) //XXX: Should we use `calculate_or_default()` here?
239	    },
240	    Largest => Self::Selected(|sizes| sizes.iter().max()).compute_huge(),
241	    Selected(func) => {
242		// Factored out into a non-`inline` function since it's the only one doing actual work, and allows the parent function to be `inline` without bloating to much
243		fn compute_selected(func: for<'r> fn (&'r [usize]) -> Option<&'r usize>) -> Option<MapHugeFlag>
244		{
245		    use std::borrow::Cow;
246		    let mask = match SYSTEM_HUGEPAGE_SIZES.as_ref() {
247			Ok(avail) => Cow::Borrowed(&avail[..]),
248			Err(_) => {
249			    // Attempt to re-scan the system. Fail if scan fails.
250			    #[cold]
251			    fn rescan() -> io::Result<Vec<usize>>
252			    {
253				scan_hugepages().and_then(|x| x.into_iter().collect())
254			    }
255			    let v = rescan();
256			    let mut v = if cfg!(debug_assertions) {
257				v.expect("Failed to compute available hugetlb sizes")
258			    } else {
259				v.ok()?
260			    };
261			    v.sort_unstable();
262			    Cow::Owned(v)
263			},
264		    };
265
266		    match func(mask.as_ref()) {
267			Some(mask) => Dynamic { kilobytes: *mask }.compute_huge(),
268			None => Some(MapHugeFlag::HUGE_DEFAULT),
269		    }
270		}
271		compute_selected(func)
272	    },
273	}
274    }
275}
276
277lazy_static! {
278    /// A persistent invocation of `scan_hugepages()`.
279    pub(crate) static ref SYSTEM_HUGEPAGE_SIZES: io::Result<Vec<usize>> = {
280	let mut val: io::Result<Vec<usize>> = scan_hugepages().and_then(|x| x.into_iter().collect());
281	if let Ok(ref mut arr) = val.as_mut() {
282	    arr.sort_unstable();
283	};
284	val
285    };
286
287    /// A list of all availble huge-page flags if enumeration of them is possible.
288    ///
289    /// This is created from a persistent invocation of `scan_hugepages()`.
290    pub static ref SYSTEM_HUGEPAGES: io::Result<Vec<MapHugeFlag>> =
291	SYSTEM_HUGEPAGE_SIZES.as_ref()
292	.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, format!("SYSTEM_HUGEPAGES failed with error {err}")))
293	.map(|vec| vec.iter().map(|&size| MapHugeFlag::calculate_or_default(size)).collect());
294}
295
296/// Scan the system for available huge-page sizes (in kB).
297///
298/// # Returns
299/// If reading the directory `HUGEPAGE_LOCATION` fails, then the error is returned.
300/// Otherwise, an iterator over each item in this location, parsed for its size, is returned.
301/// If reading an entry fails, an error is returned.
302///
303/// If an entry is not parsed correctly, then it is skipped.
304pub fn scan_hugepages() -> io::Result<impl IntoIterator<Item=io::Result<usize>> + Send + Sync + 'static>
305{
306    let path = Path::new(HUGEPAGE_LOCATION);
307    let dir = fs::read_dir(path)?;
308
309    #[derive(Debug)]
310    struct FilteredIterator(fs::ReadDir);
311    
312    impl Iterator for FilteredIterator
313    {
314	type Item = io::Result<usize>;
315	fn next(&mut self) -> Option<Self::Item> {
316	    loop {
317		break if let Some(next) = self.0.next() {
318		    let path = match next {
319			Ok(next) => next.file_name(),
320			Err(err) => return Some(Err(err)),
321		    };
322		    let kbs = if let Some(dash) = memchr::memchr(b'-', path.as_bytes()) {
323			let name = &path.as_bytes()[(dash+1)..];
324			if let Some(k_loc) = memchr::memrchr(b'k', &name) {
325			    &name[..k_loc]
326			} else {
327			    continue
328			}
329		    } else {
330			continue
331		    };
332		    let kb = if let Ok(kbs) = std::str::from_utf8(kbs) {
333			kbs.parse::<usize>().ok()
334		    } else {
335			continue
336		    };
337		    match kb {
338			None => continue,
339			valid => valid.map(Ok)
340		    }
341		} else {
342		    None
343		}
344	    }
345	}
346    }
347    
348    Ok(FilteredIterator(dir))
349}