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;
7
8use memory_mapped_vmo::MemoryMappedVmo;
9use std::sync::atomic::{AtomicU64, Ordering};
10use zx::Rights;
11
12pub struct PageRefaultCounter {
13    vmo: zx::Vmo,
14    _storage: MemoryMappedVmo,
15    count_ptr: *const AtomicU64,
16}
17
18// SAFETY: both `vmo`` and `_storage`` are Send, and they are not modified once created (thus, they
19// are Sync). `count_ptr` is a pointer, pointing to the memory mapped region managed by
20// `MemoryMappedVmo`. It is valid as long as `_storage` is valid, and the pointer is not
21// invalidated by moving `PageRefaultCounter` as the memory mapped region stays in the same place.
22unsafe impl Send for PageRefaultCounter {}
23unsafe impl Sync for PageRefaultCounter {}
24
25impl PageRefaultCounter {
26    /// Creates a new read-write PageRefaultCounter.
27    pub fn new() -> Result<Self, zx::Status> {
28        let vmo = zx::Vmo::create(size_of::<AtomicU64>().try_into().unwrap())?;
29        vmo.set_name(&zx::Name::new_lossy("page_refault_counter"))?;
30
31        // SAFETY: all accesses to [storage] are synchronized (through an Atomic).
32        let mut storage: MemoryMappedVmo = unsafe { MemoryMappedVmo::new_readwrite(&vmo)? };
33        let count_ptr: *mut AtomicU64 =
34            storage.get_object_mut::<AtomicU64>(0).map_err(|_| zx::Status::INVALID_ARGS)?;
35        Ok(PageRefaultCounter { vmo: vmo, _storage: storage, count_ptr })
36    }
37
38    /// Creates a new read-only PageRefaultCounter from the provided VMO. The VMO must have the
39    /// READ, MAP, and GET_PROPERTY rights.
40    pub fn from_vmo_readonly(vmo: zx::Vmo) -> Result<Self, zx::Status> {
41        if vmo.get_size()? < size_of::<AtomicU64>().try_into().unwrap() {
42            return Err(zx::Status::INVALID_ARGS);
43        }
44        // SAFETY: all accesses to [storage] are synchronized (through an Atomic).
45        let storage: MemoryMappedVmo = unsafe { MemoryMappedVmo::new_readonly(&vmo)? };
46        let count_ptr: *const AtomicU64 =
47            storage.get_object::<AtomicU64>(0).map_err(|_| zx::Status::INVALID_ARGS)?;
48        Ok(PageRefaultCounter { vmo: vmo, _storage: storage, count_ptr })
49    }
50
51    pub fn increment(&self, count: u64, order: Ordering) {
52        // SAFETY: `self.count_ptr` is non-null per construction, and valid as long as `_storage`
53        // is valid.
54        unsafe { &*self.count_ptr }.fetch_add(count, order);
55    }
56
57    pub fn read(&self, order: Ordering) -> u64 {
58        // SAFETY: `self.count_ptr` is non-null per construction, and valid as long as `_storage`
59        // is valid.
60        unsafe { &*self.count_ptr }.load(order)
61    }
62
63    /// Returns a read-only handle for the backing VMO.
64    pub fn readonly_vmo(&self) -> Result<zx::Vmo, zx::Status> {
65        self.vmo.duplicate_handle(Rights::BASIC | Rights::READ | Rights::MAP | Rights::GET_PROPERTY)
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_page_refault_counter() {
75        let counter = PageRefaultCounter::new().unwrap();
76
77        let ro_vmo = counter.readonly_vmo().unwrap();
78        let ro_counter = PageRefaultCounter::from_vmo_readonly(ro_vmo).unwrap();
79
80        counter.increment(100, Ordering::SeqCst);
81        assert_eq!(ro_counter.read(Ordering::SeqCst), 100);
82
83        counter.increment(100, Ordering::SeqCst);
84        assert_eq!(ro_counter.read(Ordering::SeqCst), 200);
85    }
86}