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
12 use std::fs;
13 use std::str;
14
15 use anyhow::Context;
16 use base::pagesize;
17 use base::warn;
18 use once_cell::sync::Lazy;
19
20 const TRANSPARENT_HUGEPAGE_SIZE_PATH: &str = "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size";
21
22 static PAGESIZE_SHIFT: Lazy<u8> = Lazy::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.
36 pub static THP_SIZE: Lazy<usize> = Lazy::new(|| {
37 match load_transparent_hugepage_size() {
38 Ok(transparent_hugepage_size) => transparent_hugepage_size,
39 Err(e) => {
40 warn!(
41 "failed to load huge page size: {:?}. fallback to 2MB as hugepage size.",
42 e
43 );
44 2 * 1024 * 1024 // = 2MB
45 }
46 }
47 });
48
load_transparent_hugepage_size() -> anyhow::Result<usize>49 fn load_transparent_hugepage_size() -> anyhow::Result<usize> {
50 let buf = fs::read(TRANSPARENT_HUGEPAGE_SIZE_PATH).context("read thp size file")?;
51 let text = str::from_utf8(&buf).context("utf8")?;
52 let hugepage_size = text.trim().parse::<usize>().context("parse usize")?;
53 Ok(hugepage_size)
54 }
55
56 /// Helper methods to calculate values derived from page size.
57 ///
58 /// This has performance benefits from:
59 ///
60 /// * Avoiding calling `sysconf(_SC_PAGESIZE)` multiple times by caching the shift bit.
61 /// * Using the (faster) shift instruction instead of (slower) multiply/divide instruction.
62 #[derive(Clone, Copy, Debug)]
63 pub struct PagesizeShift(u8);
64
65 /// The page index of the page which contains the "addr".
66 #[inline]
addr_to_page_idx(addr: usize) -> usize67 pub fn addr_to_page_idx(addr: usize) -> usize {
68 addr >> *PAGESIZE_SHIFT
69 }
70
71 /// The head address of the page.
72 #[inline]
page_idx_to_addr(page_idx: usize) -> usize73 pub fn page_idx_to_addr(page_idx: usize) -> usize {
74 page_idx << *PAGESIZE_SHIFT
75 }
76
77 /// The head address of the page which contains the "addr".
78 #[inline]
page_base_addr(addr: usize) -> usize79 pub fn page_base_addr(addr: usize) -> usize {
80 let pagesize_shift = *PAGESIZE_SHIFT;
81 (addr >> pagesize_shift) << pagesize_shift
82 }
83
84 /// Returns whether the address/size is aligned with page.
85 #[inline]
is_page_aligned(v: usize) -> bool86 pub fn is_page_aligned(v: usize) -> bool {
87 let mask = (1 << *PAGESIZE_SHIFT) - 1;
88 v & mask == 0
89 }
90
91 /// Converts the bytes to number of pages.
92 ///
93 /// This rounds down if the `size_in_bytes` is not multiple of page size.
94 #[inline]
bytes_to_pages(size_in_bytes: usize) -> usize95 pub fn bytes_to_pages(size_in_bytes: usize) -> usize {
96 size_in_bytes >> *PAGESIZE_SHIFT
97 }
98
99 /// Converts number of pages to byte size.
100 #[inline]
pages_to_bytes(num_of_pages: usize) -> usize101 pub fn pages_to_bytes(num_of_pages: usize) -> usize {
102 num_of_pages << *PAGESIZE_SHIFT
103 }
104
105 /// Returns whether the address/size is aligned with hugepage.
106 #[inline]
is_hugepage_aligned(v: usize) -> bool107 pub fn is_hugepage_aligned(v: usize) -> bool {
108 v & (*THP_SIZE - 1) == 0
109 }
110
111 /// Rounds up the address/size with the hugepage size.
112 #[inline]
round_up_hugepage_size(v: usize) -> usize113 pub fn round_up_hugepage_size(v: usize) -> usize {
114 let hugepage_size = *THP_SIZE;
115 (v + hugepage_size - 1) & !(hugepage_size - 1)
116 }
117
118 #[cfg(test)]
119 mod tests {
120
121 use super::*;
122
123 #[test]
test_addr_to_page_idx()124 fn test_addr_to_page_idx() {
125 let addr = 10 * pagesize();
126 assert_eq!(addr_to_page_idx(addr - 1), 9);
127 assert_eq!(addr_to_page_idx(addr), 10);
128 assert_eq!(addr_to_page_idx(addr + 1), 10);
129 }
130
131 #[test]
test_page_idx_to_addr()132 fn test_page_idx_to_addr() {
133 assert_eq!(page_idx_to_addr(10), 10 * pagesize());
134 }
135
136 #[test]
test_page_base_addr()137 fn test_page_base_addr() {
138 let addr = 10 * pagesize();
139 assert_eq!(page_base_addr(addr - 1), addr - pagesize());
140 assert_eq!(page_base_addr(addr), addr);
141 assert_eq!(page_base_addr(addr + 1), addr);
142 }
143
144 #[test]
test_is_page_aligned()145 fn test_is_page_aligned() {
146 let addr = 10 * pagesize();
147 assert!(!is_page_aligned(addr - 1));
148 assert!(is_page_aligned(addr));
149 assert!(!is_page_aligned(addr + 1));
150 }
151
152 #[test]
test_bytes_to_pages()153 fn test_bytes_to_pages() {
154 assert_eq!(bytes_to_pages(10 * pagesize()), 10);
155 assert_eq!(bytes_to_pages(10 * pagesize() + 1), 10);
156 }
157
158 #[test]
test_pages_to_bytes()159 fn test_pages_to_bytes() {
160 assert_eq!(pages_to_bytes(1), pagesize());
161 assert_eq!(pages_to_bytes(10), 10 * pagesize());
162 }
163
164 #[test]
test_is_hugepage_aligned()165 fn test_is_hugepage_aligned() {
166 let addr = 10 * *THP_SIZE;
167 assert!(!is_hugepage_aligned(addr - 1));
168 assert!(is_hugepage_aligned(addr));
169 assert!(!is_hugepage_aligned(addr - 1));
170 assert!(!is_hugepage_aligned(pagesize()));
171 }
172
173 #[test]
test_round_up_hugepage_size()174 fn test_round_up_hugepage_size() {
175 let addr = 10 * *THP_SIZE;
176
177 assert_eq!(round_up_hugepage_size(0), 0);
178 assert_eq!(round_up_hugepage_size(addr - 1), addr);
179 assert_eq!(round_up_hugepage_size(addr), addr);
180 assert_eq!(round_up_hugepage_size(addr + 1), addr + *THP_SIZE);
181 assert_eq!(round_up_hugepage_size(pagesize()), *THP_SIZE);
182 }
183 }
184