clock_page_replacement/
lib.rs

1//! A simple (improved) clock page replacement algorithm.
2//!
3//! This algorithm utilizes the 'A' (Accessed) and 'D' (Dirty) bits in the PTE (Page Table Entry) to determine which page to replace. We maintain a circular pointer to the page in the memory and evict current page if it is neither accessed nor dirty. Otherwise, we clear the first one of 'A' and 'D' bit and move to the next page.
4
5#![no_std]
6
7extern crate alloc;
8
9use alloc::collections::VecDeque;
10
11/// A page with 'A' and 'D' bits.
12pub trait Page {
13    /// Check if the page is valid.
14    fn is_valid(&self) -> bool;
15
16    /// Check if the page is accessed.
17    fn is_accessed(&self) -> bool;
18
19    /// Check if the page is dirty.
20    fn is_dirty(&self) -> bool;
21
22    /// Set the 'A' bit.
23    fn set_accessed(&mut self);
24
25    /// Set the 'D' bit.
26    fn set_dirty(&mut self);
27
28    /// Clear the 'A' bit.
29    fn clear_accessed(&mut self);
30
31    /// Clear the 'D' bit.
32    fn clear_dirty(&mut self);
33}
34
35/// A page replacer based on the clock page replacement algorithm.
36///
37/// # Usage
38///
39/// Every time you allocate a physical memory page, you should call
40/// [`register`] to register it to the replacer.
41///
42/// You don't need to unregister a page when it's evicted from the
43/// physical memory, because the replacer will automatically remove it
44/// when it becomes invalid.
45///
46/// When you need to replace a page, call the `replace` method to get the
47/// page to replace. The page is automatically removed from the replacer.
48///
49/// [`register`]: ClockPageReplacer::register
50#[derive(Default)]
51pub struct ClockPageReplacer<T: Page> {
52    pages: VecDeque<T>,
53}
54
55impl<T: Page> ClockPageReplacer<T> {
56    /// Create a new [`ClockPageReplacer`].
57    pub fn new() -> Self {
58        Self {
59            pages: VecDeque::new(),
60        }
61    }
62
63    /// Register a page to the replacer.
64    pub fn register(&mut self, page: T) {
65        self.pages.push_back(page);
66    }
67
68    /// Find a page to replace.
69    ///
70    /// # Returns
71    ///
72    /// The algorithm always finds a page to replace, so it returns `Some(Page)`
73    /// unless the page list is empty.
74    pub fn replace(&mut self) -> Option<T> {
75        loop {
76            let mut page = self.pages.pop_front()?;
77
78            // Page is invalid, which means it's may have been unmapped.
79            if !page.is_valid() {
80                continue;
81            }
82
83            if !page.is_accessed() && !page.is_dirty() {
84                return Some(page);
85            } else if page.is_accessed() {
86                page.clear_accessed();
87            } else if page.is_dirty() {
88                page.clear_dirty();
89            } else {
90                unreachable!("Logically unreachable");
91            }
92
93            // The page is still in use, push it back to the queue.
94            self.pages.push_back(page);
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    extern crate std;
102    use std::{cell::RefCell, collections::BTreeSet, println, rc::Rc, vec::Vec};
103
104    use super::*;
105
106    /// Test page implementation.
107    #[derive(Clone, Debug)]
108    struct TestPage {
109        id: usize,
110        valid: bool,
111        accessed: bool,
112        dirty: bool,
113    }
114
115    impl TestPage {
116        fn new(id: usize, valid: bool) -> Self {
117            Self {
118                id,
119                valid,
120                accessed: false,
121                dirty: false,
122            }
123        }
124    }
125
126    impl Drop for TestPage {
127        fn drop(&mut self) {
128            println!("Page {} dropped", self.id);
129        }
130    }
131
132    impl Page for Rc<RefCell<TestPage>> {
133        fn is_valid(&self) -> bool {
134            self.borrow().valid
135        }
136
137        fn is_accessed(&self) -> bool {
138            self.borrow().accessed
139        }
140
141        fn is_dirty(&self) -> bool {
142            self.borrow().dirty
143        }
144
145        fn set_accessed(&mut self) {
146            let mut page = self.borrow_mut();
147            page.accessed = true;
148            println!(
149                "Page {} accessed, current AD bits: {} {}",
150                page.id, page.accessed, page.dirty
151            );
152        }
153
154        fn set_dirty(&mut self) {
155            let mut page = self.borrow_mut();
156            page.dirty = true;
157            println!(
158                "Page {} dirty, current AD bits: {} {}",
159                page.id, page.accessed, page.dirty
160            );
161        }
162
163        fn clear_accessed(&mut self) {
164            let mut page = self.borrow_mut();
165            page.accessed = false;
166            println!(
167                "Page {} accessed cleared, current AD bits: {} {}",
168                page.id, page.accessed, page.dirty
169            );
170        }
171
172        fn clear_dirty(&mut self) {
173            let mut page = self.borrow_mut();
174            page.dirty = false;
175            println!(
176                "Page {} dirty cleared, current AD bits: {} {}",
177                page.id, page.accessed, page.dirty
178            );
179        }
180    }
181
182    /// Simulate a memory access.
183    struct MemoryAccess {
184        page_id: usize,
185        write: bool,
186    }
187
188    impl From<(usize, bool)> for MemoryAccess {
189        fn from((page_id, write): (usize, bool)) -> Self {
190            Self { page_id, write }
191        }
192    }
193
194    /// Test basic replacement.
195    #[test]
196    fn test_basic() {
197        let mut replacer = ClockPageReplacer::new();
198
199        for i in 0..10 {
200            let page = TestPage {
201                id: i,
202                valid: i % 5 != 0,
203                accessed: i % 2 == 0,
204                dirty: i % 3 == 0,
205            };
206            println!("Registered page {page:?}");
207            replacer.register(Rc::new(RefCell::new(page)));
208        }
209
210        for _ in 0..10 {
211            if let Some(page) = replacer.replace() {
212                let page = page.borrow();
213                println!("Replaced page {page:?}");
214                assert_eq!(page.accessed, false);
215                assert_eq!(page.dirty, false);
216            } else {
217                println!("No page to replace");
218            }
219        }
220    }
221
222    /// Test using the example from [THU OS Lecture].
223    ///
224    /// [THU OS Lecture]: https://learningos.cn/os-lectures/lec6/p2-localpagereplace.html#66
225    #[test]
226    fn test_example() {
227        const MEM_SIZE: usize = 4;
228        let mem = (0..5)
229            .map(|i| Rc::new(RefCell::new(TestPage::new(i, i != 4))))
230            .collect::<Vec<_>>();
231        let mem_accesses = [
232            (2, false),
233            (0, true),
234            (3, false),
235            (1, true),
236            (4, false),
237            (1, false),
238            (0, true),
239            (1, false),
240            (2, false),
241            (3, false),
242        ]
243        .into_iter()
244        .map(Into::into)
245        .collect::<Vec<MemoryAccess>>();
246
247        let mut working_set: BTreeSet<_> = (0..4).collect();
248        let mut replacer = ClockPageReplacer::new();
249
250        for i in 0..4 {
251            replacer.register(mem[i].clone());
252        }
253
254        // Simulate memory access
255        for access in mem_accesses {
256            println!("Accessing page {}, write: {}", access.page_id, access.write);
257
258            if !working_set.contains(&access.page_id) {
259                println!(
260                    "Page {} is not in the working set, loading it",
261                    access.page_id
262                );
263                if working_set.len() == MEM_SIZE {
264                    println!("Working set is full, replacing a page");
265                    let replaced_page = replacer.replace().unwrap();
266                    replacer.register(mem[access.page_id].clone());
267
268                    println!("Replacing page {:?}", replaced_page);
269                    let replaced_id = replaced_page.borrow().id;
270                    working_set.remove(&replaced_id);
271                    replaced_page.borrow_mut().valid = false;
272
273                    working_set.insert(access.page_id);
274                    mem[access.page_id].borrow_mut().valid = true;
275                }
276            }
277            assert!(working_set.contains(&access.page_id));
278
279            let mut page = mem[access.page_id].clone();
280            assert!(page.is_valid());
281
282            if access.write {
283                page.set_dirty();
284            }
285            page.set_accessed();
286        }
287    }
288}