swap/
pagesize.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Helpers to calculate values derived from page size.
6//!
7//! This has performance benefits from:
8//!
9//! * Avoiding calling `sysconf(_SC_PAGESIZE)` multiple times by caching the shift bit.
10//! * Using the (faster) shift instruction instead of (slower) multiply/divide instruction.
11
12use std::fs;
13use std::str;
14use std::sync::LazyLock;
15
16use anyhow::Context;
17use base::info;
18use base::pagesize;
19
20const TRANSPARENT_HUGEPAGE_SIZE_PATH: &str = "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size";
21
22static PAGESIZE_SHIFT: LazyLock<u8> = LazyLock::new(|| {
23    let pagesize_shift = pagesize().trailing_zeros();
24    // pagesize() should be power of 2 in almost all cases. vmm-swap feature does not support
25    // systems in which page size is not power of 2.
26    if 1 << pagesize_shift != pagesize() {
27        panic!("page size is not power of 2");
28    }
29    // pagesize_shift must be less than 64 since usize has at most 64 bits.
30    pagesize_shift as u8
31});
32
33/// The transparent hugepage size loaded from /sys/kernel/mm/transparent_hugepage/hpage_pmd_size.
34///
35/// If it fails to load the hugepage size, it fallbacks to use 2MB.
36pub static THP_SIZE: LazyLock<usize> = LazyLock::new(|| {
37    match load_transparent_hugepage_size() {
38        Ok(transparent_hugepage_size) => transparent_hugepage_size,
39        Err(e) => {
40            info!(
41                "failed to load huge page size: {:?}, maybe the THP is disabled. \
42                fallback to 2MB as hugepage size.",
43                e
44            );
45            2 * 1024 * 1024 // = 2MB
46        }
47    }
48});
49
50fn load_transparent_hugepage_size() -> anyhow::Result<usize> {
51    let buf = fs::read(TRANSPARENT_HUGEPAGE_SIZE_PATH).context("read thp size file")?;
52    let text = str::from_utf8(&buf).context("utf8")?;
53    let hugepage_size = text.trim().parse::<usize>().context("parse usize")?;
54    Ok(hugepage_size)
55}
56
57/// The page index of the page which contains the "addr".
58#[inline]
59pub fn addr_to_page_idx(addr: usize) -> usize {
60    addr >> *PAGESIZE_SHIFT
61}
62
63/// The head address of the page.
64#[inline]
65pub fn page_idx_to_addr(page_idx: usize) -> usize {
66    page_idx << *PAGESIZE_SHIFT
67}
68
69/// The head address of the page which contains the "addr".
70#[inline]
71pub fn page_base_addr(addr: usize) -> usize {
72    let pagesize_shift = *PAGESIZE_SHIFT;
73    (addr >> pagesize_shift) << pagesize_shift
74}
75
76/// Returns whether the address/size is aligned with page.
77#[inline]
78pub fn is_page_aligned(v: usize) -> bool {
79    let mask = (1 << *PAGESIZE_SHIFT) - 1;
80    v & mask == 0
81}
82
83/// Converts the bytes to number of pages.
84///
85/// This rounds down if the `size_in_bytes` is not multiple of page size.
86#[inline]
87pub fn bytes_to_pages(size_in_bytes: usize) -> usize {
88    size_in_bytes >> *PAGESIZE_SHIFT
89}
90
91/// Converts number of pages to byte size.
92#[inline]
93pub fn pages_to_bytes(num_of_pages: usize) -> usize {
94    num_of_pages << *PAGESIZE_SHIFT
95}
96
97/// Returns whether the address/size is aligned with hugepage.
98#[inline]
99pub fn is_hugepage_aligned(v: usize) -> bool {
100    v & (*THP_SIZE - 1) == 0
101}
102
103/// Rounds up the address/size with the hugepage size.
104#[inline]
105pub fn round_up_hugepage_size(v: usize) -> usize {
106    let hugepage_size = *THP_SIZE;
107    (v + hugepage_size - 1) & !(hugepage_size - 1)
108}
109
110#[cfg(test)]
111mod tests {
112
113    use super::*;
114
115    #[test]
116    fn test_addr_to_page_idx() {
117        let addr = 10 * pagesize();
118        assert_eq!(addr_to_page_idx(addr - 1), 9);
119        assert_eq!(addr_to_page_idx(addr), 10);
120        assert_eq!(addr_to_page_idx(addr + 1), 10);
121    }
122
123    #[test]
124    fn test_page_idx_to_addr() {
125        assert_eq!(page_idx_to_addr(10), 10 * pagesize());
126    }
127
128    #[test]
129    fn test_page_base_addr() {
130        let addr = 10 * pagesize();
131        assert_eq!(page_base_addr(addr - 1), addr - pagesize());
132        assert_eq!(page_base_addr(addr), addr);
133        assert_eq!(page_base_addr(addr + 1), addr);
134    }
135
136    #[test]
137    fn test_is_page_aligned() {
138        let addr = 10 * pagesize();
139        assert!(!is_page_aligned(addr - 1));
140        assert!(is_page_aligned(addr));
141        assert!(!is_page_aligned(addr + 1));
142    }
143
144    #[test]
145    fn test_bytes_to_pages() {
146        assert_eq!(bytes_to_pages(10 * pagesize()), 10);
147        assert_eq!(bytes_to_pages(10 * pagesize() + 1), 10);
148    }
149
150    #[test]
151    fn test_pages_to_bytes() {
152        assert_eq!(pages_to_bytes(1), pagesize());
153        assert_eq!(pages_to_bytes(10), 10 * pagesize());
154    }
155
156    #[test]
157    fn test_is_hugepage_aligned() {
158        let addr = 10 * *THP_SIZE;
159        assert!(!is_hugepage_aligned(addr - 1));
160        assert!(is_hugepage_aligned(addr));
161        assert!(!is_hugepage_aligned(addr - 1));
162        assert!(!is_hugepage_aligned(pagesize()));
163    }
164
165    #[test]
166    fn test_round_up_hugepage_size() {
167        let addr = 10 * *THP_SIZE;
168
169        assert_eq!(round_up_hugepage_size(0), 0);
170        assert_eq!(round_up_hugepage_size(addr - 1), addr);
171        assert_eq!(round_up_hugepage_size(addr), addr);
172        assert_eq!(round_up_hugepage_size(addr + 1), addr + *THP_SIZE);
173        assert_eq!(round_up_hugepage_size(pagesize()), *THP_SIZE);
174    }
175}