Skip to main content

refaults_vmo/
lib.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod atomic_vec;
6pub use atomic_vec::{AtomicBitVec, Growable, NoOverflow, Overflow};
7
8use memory_mapped_vmo::{ReadOnlyMemoryMappedVmo, ReadWriteMemoryMappedVmo};
9use std::sync::atomic::{AtomicU64, Ordering};
10use zx::Rights;
11
12/// Stores monotonic counters of page refaults and total page faults in a shared VMO.
13///
14/// Both counters are measured in **pages supplied** by the userspace pager (including readahead),
15/// rather than the number of pager requests:
16/// - `refault_count_ptr` (offset 0): pages supplied that had already been supplied before and were
17///   subsequently evicted.
18/// - `fault_count_ptr` (offset 8): total pages supplied by the userspace pager.
19pub struct PageFaultCounter {
20    vmo: zx::Vmo,
21    _storage: ReadOnlyMemoryMappedVmo,
22    refault_count_ptr: *const AtomicU64,
23    fault_count_ptr: *const AtomicU64,
24}
25
26// SAFETY: both `vmo` and `_storage` are Send, and they are not modified once created (thus, they
27// are Sync). `refault_count_ptr` and `fault_count_ptr` are pointers pointing to the memory mapped
28// region managed by `ReadOnlyMemoryMappedVmo`. They are valid as long as `_storage` is valid, and
29// are not invalidated by moving `PageFaultCounter` as the memory mapped region stays in the same
30// place.
31unsafe impl Send for PageFaultCounter {}
32unsafe impl Sync for PageFaultCounter {}
33
34const VMO_SIZE: usize = 2 * size_of::<AtomicU64>();
35const REFAULT_OFFSET: usize = 0;
36const FAULT_OFFSET: usize = size_of::<AtomicU64>();
37
38impl PageFaultCounter {
39    /// Creates a new read-write PageFaultCounter.
40    pub fn new() -> Result<Self, zx::Status> {
41        let vmo = zx::Vmo::create(VMO_SIZE.try_into().unwrap())?;
42        vmo.set_name(&zx::Name::new_lossy("page_fault_counter"))?;
43
44        // SAFETY: all accesses to [storage] are synchronized (through Atomics).
45        let mut storage = unsafe { ReadWriteMemoryMappedVmo::new(&vmo)? };
46        let refault_count_ptr: *const AtomicU64 = storage
47            .get_object_mut::<AtomicU64>(REFAULT_OFFSET)
48            .map_err(|_| zx::Status::INVALID_ARGS)?;
49        let fault_count_ptr: *const AtomicU64 = storage
50            .get_object_mut::<AtomicU64>(FAULT_OFFSET)
51            .map_err(|_| zx::Status::INVALID_ARGS)?;
52        Ok(PageFaultCounter {
53            vmo,
54            _storage: storage.into_readonly(),
55            refault_count_ptr,
56            fault_count_ptr,
57        })
58    }
59
60    /// Creates a new read-only PageFaultCounter from the provided VMO. The VMO must have the
61    /// READ, MAP, and GET_PROPERTY rights.
62    pub fn from_vmo_readonly(vmo: zx::Vmo) -> Result<Self, zx::Status> {
63        if vmo.get_size()? < VMO_SIZE.try_into().unwrap() {
64            return Err(zx::Status::INVALID_ARGS);
65        }
66        // SAFETY: all accesses to [storage] are synchronized (through Atomics).
67        let storage = unsafe { ReadOnlyMemoryMappedVmo::new(&vmo)? };
68        let refault_count_ptr: *const AtomicU64 = storage
69            .get_object::<AtomicU64>(REFAULT_OFFSET)
70            .map_err(|_| zx::Status::INVALID_ARGS)?;
71        let fault_count_ptr: *const AtomicU64 =
72            storage.get_object::<AtomicU64>(FAULT_OFFSET).map_err(|_| zx::Status::INVALID_ARGS)?;
73        Ok(PageFaultCounter { vmo, _storage: storage, refault_count_ptr, fault_count_ptr })
74    }
75
76    /// Increments the page refault counter by `count` pages (pages resupplied after eviction).
77    pub fn increment_refaults(&self, count: u64, order: Ordering) {
78        // SAFETY: `self.refault_count_ptr` is non-null per construction, and valid as long as
79        // `_storage` is valid.
80        unsafe { &*self.refault_count_ptr }.fetch_add(count, order);
81    }
82
83    /// Reads the page refault counter (in pages).
84    pub fn read_refaults(&self, order: Ordering) -> u64 {
85        // SAFETY: `self.refault_count_ptr` is non-null per construction, and valid as long as
86        // `_storage` is valid.
87        unsafe { &*self.refault_count_ptr }.load(order)
88    }
89
90    /// Increments the total page fault counter by `count` pages (total pages supplied by the
91    /// userspace pager, including readahead).
92    pub fn increment_faults(&self, count: u64, order: Ordering) {
93        // SAFETY: `self.fault_count_ptr` is non-null per construction, and valid as long as
94        // `_storage` is valid.
95        unsafe { &*self.fault_count_ptr }.fetch_add(count, order);
96    }
97
98    /// Reads the total page fault counter (in pages supplied).
99    pub fn read_faults(&self, order: Ordering) -> u64 {
100        // SAFETY: `self.fault_count_ptr` is non-null per construction, and valid as long as
101        // `_storage` is valid.
102        unsafe { &*self.fault_count_ptr }.load(order)
103    }
104
105    /// Returns a read-only handle for the backing VMO.
106    pub fn readonly_vmo(&self) -> Result<zx::Vmo, zx::Status> {
107        self.vmo.duplicate_handle(Rights::BASIC | Rights::READ | Rights::MAP | Rights::GET_PROPERTY)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_page_fault_counter() {
117        let counter = PageFaultCounter::new().unwrap();
118
119        let ro_vmo = counter.readonly_vmo().unwrap();
120        let ro_counter = PageFaultCounter::from_vmo_readonly(ro_vmo).unwrap();
121
122        counter.increment_refaults(100, Ordering::SeqCst);
123        counter.increment_faults(250, Ordering::SeqCst);
124        assert_eq!(ro_counter.read_refaults(Ordering::SeqCst), 100);
125        assert_eq!(ro_counter.read_faults(Ordering::SeqCst), 250);
126
127        counter.increment_refaults(100, Ordering::SeqCst);
128        counter.increment_faults(50, Ordering::SeqCst);
129        assert_eq!(ro_counter.read_refaults(Ordering::SeqCst), 200);
130        assert_eq!(ro_counter.read_faults(Ordering::SeqCst), 300);
131    }
132}