Skip to main content

kernel_manager/
pager.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
5//! This supports paging for ext4 files.  `zx_pager_supply_pages` requires is to transfer pages to
6//! the target, hence the need for a transfer VMO.  This also uses a static zeroed VMO to transfer
7//! pages that should be zeroed.
8
9use fidl_fuchsia_starnix_runner as fstarnixrunner;
10use futures::TryStreamExt;
11use refaults_vmo::{AtomicBitVec, PageRefaultCounter};
12use starnix_core::mm::PAGE_SIZE;
13use starnix_logging::{log_debug, log_error, log_warn, with_zx_name};
14use starnix_sync::{LockDepMutex, PagerFilesByInodeLock, PagerFilesystemsLock};
15use starnix_uapi::errors::Errno;
16use starnix_uapi::{errno, error};
17use std::collections::HashMap;
18use std::collections::hash_map::Entry;
19use std::ops::Range;
20use std::sync::Arc;
21use std::sync::atomic::{AtomicU32, Ordering};
22use zx::sys::zx_page_request_command_t::{ZX_PAGER_VMO_COMPLETE, ZX_PAGER_VMO_READ};
23
24// N.B. At time of writing, no particular science has gone into picking these numbers; tweaking
25// these numbers might or might not give us better performance.
26const PAGER_THREADS: usize = 1;
27const TRANSFER_VMO_SIZE: u64 = 1 * 1024 * 1024;
28const ZERO_VMO_SIZE: u64 = 1 * 1024 * 1024;
29const READAHEAD_ALIGNMENT: u64 = 128 * 1024;
30
31/// Tracing category used to trace the pager.
32const CATEGORY_STARNIX_PAGER: &'static str = "starnix:pager";
33
34pub async fn run_pager(
35    pager_request: fstarnixrunner::ManagerCreatePagerRequest,
36    pager: Arc<Pager>,
37) {
38    let fstarnixrunner::ManagerCreatePagerRequest {
39        backing_vmo: Some(backing_vmo),
40        block_size: Some(block_size),
41        pager: Some(pager_server),
42        ..
43    } = pager_request
44    else {
45        log_error!("Invalid create pager request");
46        return;
47    };
48
49    let filesystem = Arc::new(match Filesystem::new(pager.clone(), backing_vmo, block_size) {
50        Ok(filesystem) => filesystem,
51        Err(error) => {
52            log_error!("Unable to register filesystem {error}");
53            return;
54        }
55    });
56
57    pager.add_filesystem(filesystem.clone());
58
59    let mut stream = pager_server.into_stream();
60    'outer: while let Ok(Some(event)) = stream.try_next().await {
61        match event {
62            fstarnixrunner::PagerRequest::RegisterFile {
63                payload:
64                    fstarnixrunner::PagerRegisterFileRequest {
65                        name: Some(name),
66                        inode_num: Some(inode_num),
67                        size: Some(size),
68                        extents: Some(extents),
69                        ..
70                    },
71                responder,
72                ..
73            } => {
74                fuchsia_trace::instant!(
75                    CATEGORY_STARNIX_PAGER,
76                    "file_register",
77                    fuchsia_trace::Scope::Thread
78                );
79                match filesystem.register(
80                    &name,
81                    inode_num,
82                    size,
83                    extents
84                        .iter()
85                        .map(|e| PagerExtent {
86                            logical: e.logical_start..e.logical_end,
87                            physical_block: e.physical_block,
88                        })
89                        .collect(),
90                ) {
91                    Ok(vmo) => {
92                        match responder.send(Ok(fstarnixrunner::PagerRegisterFileResponse {
93                            vmo: Some(vmo),
94                            ..Default::default()
95                        })) {
96                            Ok(_) => {}
97                            Err(e) => {
98                                log_error!("Error sending pager response {:?}", e);
99                                break 'outer;
100                            }
101                        }
102                    }
103                    Err(e) => match responder.send(Err(e.into_raw())) {
104                        Ok(_) => {}
105                        Err(e) => {
106                            log_error!("Error sending pager error response {:?}", e);
107                            break 'outer;
108                        }
109                    },
110                };
111            }
112            fstarnixrunner::PagerRequest::RegisterFile { .. } => {
113                log_error!("Invalid RegisterFile request");
114                break 'outer;
115            }
116            _ => {}
117        }
118    }
119    pager.remove_filesystem(&*filesystem);
120}
121
122/// A simple pager implementation. One pager can serve multiple filesystems.
123pub struct Pager {
124    pager: zx::Pager,
125    port: zx::Port,
126    zero_vmo: zx::Vmo,
127    next_filesystem_id: AtomicU32,
128    filesystems: LockDepMutex<HashMap<u32, Arc<Filesystem>>, PagerFilesystemsLock>,
129    page_refault_counter: PageRefaultCounter,
130}
131
132impl Pager {
133    pub fn new() -> Result<Self, Errno> {
134        Ok(Self {
135            pager: zx::Pager::create(zx::PagerOptions::empty()).map_err(|error| {
136                log_error!(error:?; "Pager::create failed");
137                errno!(EINVAL)
138            })?,
139            port: zx::Port::create(),
140            zero_vmo: with_zx_name(
141                zx::Vmo::create(ZERO_VMO_SIZE).map_err(|_| errno!(EINVAL))?,
142                b"starnix:ext4",
143            ),
144            next_filesystem_id: AtomicU32::new(1),
145            filesystems: Default::default(),
146            page_refault_counter: PageRefaultCounter::new().map_err(|error| {
147                log_error!(error:?; "PageRefaultCounter::new failed");
148                errno!(EINVAL)
149            })?,
150        })
151    }
152
153    pub fn page_refault_counter(&self) -> &PageRefaultCounter {
154        &self.page_refault_counter
155    }
156
157    /// Starts the pager threads.
158    pub fn start_threads(self: &Arc<Self>) {
159        for i in 0..PAGER_THREADS {
160            let this = self.clone();
161            let _ = std::thread::Builder::new().name(format!("pager-{}", i)).spawn(move || {
162                this.run_pager_thread();
163            });
164        }
165    }
166
167    /// Dedicated thread responsible for listening on port and supplying pages as needed.
168    /// More than one pager thread can be running concurrently.
169    pub fn run_pager_thread(&self) {
170        let transfer_vmo = with_zx_name(
171            zx::Vmo::create(TRANSFER_VMO_SIZE).expect("unable to create transfer vmo"),
172            b"starnix:ext4",
173        );
174        let transfer_vmo_addr = fuchsia_runtime::vmar_root_self()
175            .map(
176                0,
177                &transfer_vmo,
178                0,
179                TRANSFER_VMO_SIZE as usize,
180                zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE | zx::VmarFlags::ALLOW_FAULTS,
181            )
182            .expect("unable to map transfer vmo");
183        scopeguard::defer!({
184            // SAFETY: We mapped the VMO above.
185            let _ = unsafe {
186                fuchsia_runtime::vmar_root_self()
187                    .unmap(transfer_vmo_addr, TRANSFER_VMO_SIZE as usize)
188            };
189        });
190        let split_key = |key: u64| -> (u32, u32) { ((key >> 32) as u32, key as u32) };
191        loop {
192            match self.port.wait(zx::MonotonicInstant::INFINITE) {
193                Ok(packet) => {
194                    match packet.contents() {
195                        zx::PacketContents::Pager(contents)
196                            if contents.command() == ZX_PAGER_VMO_READ =>
197                        {
198                            fuchsia_trace::duration!(CATEGORY_STARNIX_PAGER, "vmo_read");
199                            let (filesystem_num, inode_num) = split_key(packet.key());
200                            let filesystem = self
201                                .filesystems
202                                .lock()
203                                .get(&filesystem_num)
204                                .cloned()
205                                .expect("Unexpected packet key");
206                            filesystem.receive_pager_packet(
207                                inode_num,
208                                contents,
209                                &transfer_vmo,
210                                transfer_vmo_addr,
211                            );
212                        }
213                        zx::PacketContents::Pager(contents)
214                            if contents.command() == ZX_PAGER_VMO_COMPLETE =>
215                        {
216                            fuchsia_trace::duration!(CATEGORY_STARNIX_PAGER, "vmo_complete");
217                            // We don't care about this command, but we will receive them and we
218                            // don't want to log them as unexpected.
219                        }
220                        zx::PacketContents::SignalOne(signals)
221                            if signals.observed().contains(zx::Signals::VMO_ZERO_CHILDREN) =>
222                        {
223                            fuchsia_trace::duration!(
224                                CATEGORY_STARNIX_PAGER,
225                                "signal_zero_children"
226                            );
227                            let (filesystem_num, inode_num) = split_key(packet.key());
228                            // We may get VMO_ZERO_CHILDREN notifications for
229                            // files within a filesystem after it is unmounted,
230                            // ignore those.
231                            let filesystem = self.filesystems.lock().get(&filesystem_num).cloned();
232                            if let Some(filesystem) = filesystem {
233                                filesystem.on_zero_children(inode_num).expect("on_zero_children");
234                            }
235                        }
236                        zx::PacketContents::User(_) => break,
237                        _ => log_error!("Unexpected port packet: {:?}", packet.contents()),
238                    }
239                }
240                Err(error) => log_error!(error:?; "Port::wait failed"),
241            }
242        }
243        log_debug!("Pager thread terminating");
244    }
245
246    fn create_pager_vmo(&self, key: u64, size: u64) -> Result<zx::Vmo, zx::Status> {
247        self.pager.create_vmo(zx::VmoOptions::empty(), &self.port, key, size)
248    }
249
250    fn allocate_filesystem_id(&self) -> u32 {
251        self.next_filesystem_id.fetch_add(1, Ordering::Relaxed)
252    }
253
254    fn add_filesystem(&self, filesystem: Arc<Filesystem>) {
255        self.filesystems.lock().insert(filesystem.id(), filesystem);
256    }
257
258    fn remove_filesystem(&self, filesystem: &Filesystem) {
259        self.filesystems.lock().remove(&filesystem.id());
260    }
261
262    /// Terminates (asynchronously) the pager threads.
263    pub fn terminate(&self) {
264        let up = zx::UserPacket::from_u8_array([0; 32]);
265        let packet = zx::Packet::from_user_packet(0, 0, up);
266        for _ in 0..PAGER_THREADS {
267            self.port.queue(&packet).unwrap();
268        }
269    }
270}
271
272/// Filesystem registered with the pager.
273pub struct Filesystem {
274    pager: Arc<Pager>,
275    backing_vmo: zx::Vmo,
276    block_size: u64,
277    files_by_inode: LockDepMutex<HashMap<u32, Arc<PagedFile>>, PagerFilesByInodeLock>,
278    id: u32,
279}
280
281impl Filesystem {
282    /// Returns a new filesystem.  `block_size` shouldn't be too big (which might cause overflows) and it
283    /// should be a power of 2.
284    pub fn new(pager: Arc<Pager>, backing_vmo: zx::Vmo, block_size: u64) -> Result<Self, Errno> {
285        if block_size > 1024 * 1024 || !block_size.is_power_of_two() {
286            return error!(EINVAL, "Bad block size {block_size}");
287        }
288        let id = pager.allocate_filesystem_id();
289        Ok(Self { pager, backing_vmo, block_size, files_by_inode: Default::default(), id })
290    }
291
292    /// Registers the file with the pager.  Returns a child VMO.  `extents` should be sorted.
293    pub fn register(
294        &self,
295        name: &str,
296        inode_num: u32,
297        size: u64,
298        extents: Box<[PagerExtent]>,
299    ) -> Result<zx::Vmo, zx::Status> {
300        let (file, did_create) = {
301            match self.files_by_inode.lock().entry(inode_num) {
302                Entry::Occupied(o) => (o.get().clone(), false),
303                Entry::Vacant(v) => {
304                    let num_chunks = size.div_ceil(READAHEAD_ALIGNMENT);
305                    let chunks_supplied = AtomicBitVec::new(num_chunks);
306                    (
307                        v.insert(Arc::new(PagedFile {
308                            vmo: self
309                                .pager
310                                .create_pager_vmo(self.port_key_for_inode(inode_num), size)?,
311                            extents,
312                            chunks_supplied,
313                        }))
314                        .clone(),
315                        true,
316                    )
317                }
318            }
319        };
320        let child_vmo = file.vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0);
321        if did_create {
322            let set_up_vmo = |vmo| -> Result<(), zx::Status> {
323                self.watch_for_zero_children(vmo, inode_num)?;
324                vmo.set_name(&zx::Name::new_lossy(&format!("ext4!{}", name)))?;
325                Ok(())
326            };
327
328            if let Err(e) = set_up_vmo(&file.vmo) {
329                self.files_by_inode.lock().remove(&inode_num);
330                return Err(e);
331            }
332        }
333        child_vmo
334    }
335
336    fn receive_pager_packet(
337        &self,
338        inode_num: u32,
339        contents: zx::PagerPacket,
340        transfer_vmo: &zx::Vmo,
341        transfer_vmo_addr: usize,
342    ) {
343        let Some(file) = self.files_by_inode.lock().get(&inode_num).cloned() else {
344            return;
345        };
346
347        let requested_range = contents.range();
348
349        // Align the read to 128 KiB slots (readahead).
350        let readahead_start = (requested_range.start / READAHEAD_ALIGNMENT) * READAHEAD_ALIGNMENT;
351        let mut readahead_end = requested_range.end.next_multiple_of(READAHEAD_ALIGNMENT);
352
353        // Clamp to VMO size to avoid supplying pages out of bounds.
354        let vmo_size = match file.vmo.get_size() {
355            Ok(size) => size,
356            Err(status) => {
357                log_error!("Failed to get VMO size: {:?}", status);
358                let _ = self.pager.pager.op_range(
359                    zx::PagerOp::Fail(zx::Status::IO),
360                    &file.vmo,
361                    contents.range(),
362                );
363                return;
364            }
365        };
366        readahead_end = std::cmp::min(readahead_end, vmo_size);
367
368        if readahead_end <= readahead_start {
369            return;
370        }
371
372        let supplied_end = self.supply_range(
373            &file,
374            readahead_start..readahead_end,
375            transfer_vmo,
376            transfer_vmo_addr,
377        );
378
379        self.record_refaults(&file, readahead_start..supplied_end);
380    }
381
382    /// Supplies the pages in `range`, reading the data from the backing VMO.  Returns the offset
383    /// up to which pages were successfully supplied; if an error occurs, the remainder of `range`
384    /// is failed and the returned offset is where supplying stopped.
385    fn supply_range(
386        &self,
387        file: &PagedFile,
388        range: Range<u64>,
389        transfer_vmo: &zx::Vmo,
390        transfer_vmo_addr: usize,
391    ) -> u64 {
392        let start_block = (range.start / self.block_size) as u32;
393        let mut ix = file.extents.partition_point(|e| e.logical.end <= start_block);
394
395        // SAFETY: We know that `transfer_vmo` is mapped (and initialized) for `TRANSFER_VMO_SIZE`
396        // bytes and `len` must be less than or equal to that.
397        let buf = unsafe {
398            std::slice::from_raw_parts_mut(transfer_vmo_addr as *mut u8, TRANSFER_VMO_SIZE as usize)
399        };
400
401        let mut current_offset = range.start;
402        let mut supply_helper =
403            SupplyHelper::new(transfer_vmo, buf, &file.vmo, current_offset, &*self.pager);
404
405        while ix < file.extents.len() && current_offset < range.end {
406            let extent = &file.extents[ix];
407
408            let logical_start = extent.logical.start as u64 * self.block_size;
409
410            // Deal with holes.
411            if current_offset < logical_start {
412                if let Err(e) = supply_helper.zero(logical_start - current_offset) {
413                    let supplied_end = supply_helper.supplied_end();
414                    supply_helper.fail_to(range.end, e);
415                    return supplied_end;
416                }
417                current_offset = logical_start;
418            }
419
420            let end = std::cmp::min(extent.logical.end as u64 * self.block_size, range.end);
421
422            while current_offset < end {
423                let phys_offset =
424                    extent.physical_block * self.block_size + current_offset - logical_start;
425
426                match supply_helper.fill_buf(|buf| {
427                    let amount = std::cmp::min(buf.len() as u64, end - current_offset) as usize;
428                    self.backing_vmo.read(&mut buf[..amount], phys_offset)?;
429                    Ok(amount)
430                }) {
431                    Ok(amount) => {
432                        // We don't need the pages in the backing VMO any more.  Don't worry about
433                        // errors; this is purely a hint.
434                        let _ = self.backing_vmo.op_range(
435                            zx::VmoOp::DONT_NEED,
436                            phys_offset,
437                            amount as u64,
438                        );
439                        current_offset += amount as u64;
440                    }
441                    Err(e) => {
442                        let supplied_end = supply_helper.supplied_end();
443                        supply_helper.fail_to(range.end, e);
444                        return supplied_end;
445                    }
446                }
447            }
448
449            ix += 1;
450        }
451
452        if let Err(e) = supply_helper.finish(range.end) {
453            let supplied_end = supply_helper.supplied_end();
454            supply_helper.fail_to(range.end, e);
455            return supplied_end;
456        }
457
458        supply_helper.supplied_end()
459    }
460
461    /// Count the pages in `supplied` that have already been supplied before as refaults, and mark
462    /// all of them for future refaults.
463    fn record_refaults(&self, file: &PagedFile, supplied: Range<u64>) {
464        let page_size = *PAGE_SIZE;
465        let pages_per_chunk = READAHEAD_ALIGNMENT / page_size;
466
467        let first_chunk = supplied.start / READAHEAD_ALIGNMENT;
468        let full_end_chunk = supplied.end / READAHEAD_ALIGNMENT;
469
470        let mut refault_pages =
471            file.chunks_supplied.test_and_set_range(first_chunk, full_end_chunk) * pages_per_chunk;
472
473        // The end of the range may not be chunk-aligned if it's the last chunk (or if supplying
474        // stopped early).
475        let trailing_pages = (supplied.end % READAHEAD_ALIGNMENT) / page_size;
476        if trailing_pages > 0 {
477            refault_pages +=
478                file.chunks_supplied.test_and_set_range(full_end_chunk, full_end_chunk + 1)
479                    * trailing_pages;
480        }
481
482        if refault_pages > 0 {
483            self.pager.page_refault_counter.increment(refault_pages, Ordering::Relaxed);
484        }
485    }
486
487    fn watch_for_zero_children(&self, vmo: &zx::Vmo, inode_num: u32) -> Result<(), zx::Status> {
488        vmo.wait_async(
489            &self.pager.port,
490            self.port_key_for_inode(inode_num),
491            zx::Signals::VMO_ZERO_CHILDREN,
492            zx::WaitAsyncOpts::empty(),
493        )
494    }
495
496    fn on_zero_children(&self, inode_num: u32) -> Result<(), Errno> {
497        let mut files = self.files_by_inode.lock();
498        let file = files.entry(inode_num);
499        if let Entry::Occupied(o) = file {
500            let vmo = &o.get().vmo;
501            match vmo.info() {
502                Ok(info) => {
503                    if info.num_children == 0 {
504                        // This is a true signal, so we can remove this entry.
505                        o.remove();
506                    } else {
507                        // This shouldn't fail, and there's not much we can do
508                        // if it does.
509                        if let Err(error) = self.watch_for_zero_children(vmo, inode_num) {
510                            log_error!(
511                                error:?;
512                                "watch_for_zero_children failed"
513                            );
514                        }
515                    }
516                }
517                Err(error) => log_error!(error:?; "Vmo::info failed"),
518            }
519        }
520        Ok(())
521    }
522
523    fn port_key_for_inode(&self, inode_num: u32) -> u64 {
524        (self.id as u64) << 32 | inode_num as u64
525    }
526
527    fn id(&self) -> u32 {
528        self.id
529    }
530}
531
532/// Per file state needed by the pager.
533struct PagedFile {
534    /// The main VMO.  We always hand out children of this VMO.
535    vmo: zx::Vmo,
536
537    /// The extents for the file, which will be sorted and not overlapping.  There can be holes i.e.
538    /// zeroed ranges within the file.
539    extents: Box<[PagerExtent]>,
540
541    /// Tracks `READAHEAD_ALIGNMENT`-sized chunks that have already been supplied to the kernel for
542    /// this file.
543    chunks_supplied: AtomicBitVec,
544}
545
546/// A single extent.
547pub struct PagerExtent {
548    pub logical: Range<u32>,
549    pub physical_block: u64,
550}
551
552/// SupplyHelper exists to make dealing with misalignment easier.
553struct SupplyHelper<'a> {
554    transfer_vmo: &'a zx::Vmo,
555    buffer: &'a mut [u8],
556    target_vmo: &'a zx::Vmo,
557    offset: u64,
558    pager: &'a Pager,
559    page_size: u64,
560    buf_len: usize,
561}
562
563impl<'a> SupplyHelper<'a> {
564    fn new(
565        transfer_vmo: &'a zx::Vmo,
566        buffer: &'a mut [u8],
567        target_vmo: &'a zx::Vmo,
568        offset: u64,
569        pager: &'a Pager,
570    ) -> Self {
571        Self { transfer_vmo, buffer, target_vmo, offset, pager, page_size: *PAGE_SIZE, buf_len: 0 }
572    }
573
574    /// Returns the offset up to which pages have been successfully supplied.
575    fn supplied_end(&self) -> u64 {
576        self.offset
577    }
578
579    /// Zeroes `len` bytes.
580    fn zero(&mut self, mut len: u64) -> Result<(), zx::Status> {
581        let unaligned = self.buf_len as u64 % self.page_size;
582        if unaligned > 0 {
583            let amount = std::cmp::min(self.page_size - unaligned, len);
584            self.buffer[self.buf_len..self.buf_len + amount as usize].fill(0);
585            self.buf_len += amount as usize;
586            len -= amount;
587            self.supply_pages()?;
588        }
589        // Zero whole pages by supplying pages from the zero VMO.
590        while len >= self.page_size {
591            let amount =
592                if len >= ZERO_VMO_SIZE { ZERO_VMO_SIZE } else { len - len % self.page_size };
593            self.pager.pager.supply_pages(
594                self.target_vmo,
595                self.offset..self.offset + amount,
596                &self.pager.zero_vmo,
597                0,
598            )?;
599            self.offset += amount;
600            len -= amount;
601        }
602        // And now the remaining partial page...
603        self.buffer[self.buf_len..self.buf_len + len as usize].fill(0);
604        self.buf_len += len as usize;
605        Ok(())
606    }
607
608    /// Flushes whole pages.
609    fn supply_pages(&mut self) -> Result<(), zx::Status> {
610        if self.buf_len as u64 >= self.page_size {
611            let len = self.buf_len - self.buf_len % self.page_size as usize;
612            self.pager.pager.supply_pages(
613                self.target_vmo,
614                self.offset..self.offset + len as u64,
615                self.transfer_vmo,
616                0,
617            )?;
618            // Move any remaining data to the beginning of the buffer.
619            self.buffer.copy_within(len..self.buf_len, 0);
620            self.buf_len -= len;
621            self.offset += len as u64;
622        }
623        Ok(())
624    }
625
626    /// Fills the buffer by calling the provided callback.  Returns the amount of data filled.
627    fn fill_buf(
628        &mut self,
629        f: impl FnOnce(&mut [u8]) -> Result<usize, zx::Status>,
630    ) -> Result<usize, zx::Status> {
631        let amount = f(&mut self.buffer[self.buf_len..])?;
632        self.buf_len += amount;
633        self.supply_pages()?;
634        Ok(amount)
635    }
636
637    /// Zeroes out to at least `end`, then pads to a page boundary and supplies those pages.
638    fn finish(&mut self, mut end: u64) -> Result<(), zx::Status> {
639        let byte_offset = self.offset + self.buf_len as u64;
640        end = std::cmp::max(end, byte_offset);
641        end = end + self.page_size - 1;
642        end -= end % self.page_size;
643        self.zero(end - byte_offset)
644    }
645
646    /// Fails the request up to the given offset with `error`.
647    fn fail_to(&mut self, end: u64, error: zx::Status) {
648        if self.offset < end {
649            log_warn!(error:?; "Failing page-in, range: {:?}", self.offset..end);
650            // The pager is fussy about what errors we can return here, so we always return IO.
651            match self.pager.pager.op_range(
652                zx::PagerOp::Fail(zx::Status::IO),
653                self.target_vmo,
654                self.offset..end,
655            ) {
656                Ok(()) => {}
657                Err(error) => log_error!(error:?; "Failed to report error"),
658            }
659            self.offset = end;
660            self.buf_len = 0;
661        }
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use super::{Filesystem, Pager, PagerExtent, READAHEAD_ALIGNMENT};
668
669    use starnix_core::mm::PAGE_SIZE;
670    use std::sync::Arc;
671    use std::sync::atomic::Ordering;
672    use std::time::Duration;
673
674    // Synchronize with the single pager thread by issuing a page request on a fresh file.
675    // Since `run_pager_thread` processes packets sequentially and calls `record_refaults`
676    // before waiting on the port again, completion of this read guarantees that
677    // `record_refaults` has finished for all prior reads.
678    fn sync_with_pager(filesystem: &Filesystem, inode: u32) {
679        let sync_vmo =
680            filesystem.register("sync", inode, 1, Box::new([])).expect("register failed");
681        let mut byte = [0u8; 1];
682        sync_vmo.read(&mut byte, 0).expect("sync read failed");
683    }
684
685    #[::fuchsia::test]
686    async fn test_pager() {
687        let backing_vmo = zx::Vmo::create(1 * 1024 * 1024).expect("Vmo::craete failed");
688        let backing_vmo_clone =
689            backing_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed handle dup");
690
691        let pager = Arc::new(Pager::new().expect("Pager::new failed"));
692        let filesystem = Arc::new(
693            Filesystem::new(pager.clone(), backing_vmo_clone, 1024)
694                .expect("Filesystem::new failed"),
695        );
696
697        pager.add_filesystem(filesystem.clone());
698
699        {
700            pager.start_threads();
701
702            // With no extent, we expect it to return zeroed data.
703            let vmo = filesystem.register("a".into(), 1, 5, Box::new([])).expect("register failed");
704
705            let mut buf = vec![1; 5];
706            vmo.read(&mut buf, 0).expect("read failed");
707
708            assert_eq!(&buf, &[0; 5]);
709
710            // A single extent:
711            let vmo = filesystem
712                .register(
713                    "b".into(),
714                    2,
715                    5,
716                    Box::new([PagerExtent { logical: 0..1, physical_block: 0 }]),
717                )
718                .expect("register failed");
719            backing_vmo.write(b"hello", 0).expect("write failed");
720            vmo.read(&mut buf, 0).expect("read failed");
721
722            assert_eq!(&buf, b"hello");
723
724            // A file with sparse ranges: 6 sparse, 1 extent, 5 more sparse, 1 extent, 4 sparse + a
725            // bit.
726            let file_size = (6 + 1 + 5 + 4) * 1024 + 100;
727            let vmo = filesystem
728                .register(
729                    "c".into(),
730                    3,
731                    file_size,
732                    Box::new([
733                        PagerExtent { logical: 6..7, physical_block: 0 },
734                        PagerExtent { logical: 12..13, physical_block: 1 },
735                    ]),
736                )
737                .expect("register failed");
738            backing_vmo.write(b"there", 1024).expect("write failed");
739            let mut buf = vec![1; file_size as usize];
740            vmo.read(&mut buf, 0).expect("read failed");
741
742            let mut expected = vec![0; file_size as usize];
743            expected[6 * 1024..6 * 1024 + 5].copy_from_slice(b"hello");
744            expected[12 * 1024..12 * 1024 + 5].copy_from_slice(b"there");
745            assert_eq!(&buf, &expected);
746
747            // Use the same file, but initiate a read that starts after the first extent.
748            let vmo = filesystem
749                .register(
750                    "d".into(),
751                    4,
752                    file_size,
753                    Box::new([
754                        PagerExtent { logical: 6..7, physical_block: 0 },
755                        PagerExtent { logical: 12..13, physical_block: 1 },
756                    ]),
757                )
758                .expect("register failed");
759
760            let offset = 9000;
761            let mut buf = vec![1; (file_size - offset) as usize];
762            vmo.read(&mut buf, offset).expect("read failed");
763
764            assert_eq!(&buf, &expected[offset as usize..]);
765
766            sync_with_pager(&filesystem, 100);
767
768            // Initial reads should not register any refaults.
769            assert_eq!(pager.page_refault_counter().read(Ordering::Relaxed), 0);
770
771            let file = filesystem.files_by_inode.lock().get(&4).unwrap().clone();
772            let num_pages = file_size.div_ceil(*PAGE_SIZE);
773
774            // The kernel asks for pages it has already been given: every page supplied again
775            // counts as a refault (even when the last chunk is smaller than READAHEAD_ALIGNMENT).
776            filesystem.record_refaults(&file, 0..num_pages * *PAGE_SIZE);
777            assert_eq!(pager.page_refault_counter().read(Ordering::Relaxed), num_pages);
778
779            // If nothing could be supplied, nothing is counted.
780            filesystem.record_refaults(&file, 0..0);
781            assert_eq!(pager.page_refault_counter().read(Ordering::Relaxed), num_pages);
782
783            // If supplying stops early, only the pages that were supplied are counted.
784            filesystem.record_refaults(&file, 0..*PAGE_SIZE);
785            assert_eq!(pager.page_refault_counter().read(Ordering::Relaxed), num_pages + 1);
786
787            // Verify refault accounting across full chunks and a partial trailing chunk.
788            let pages_per_chunk = READAHEAD_ALIGNMENT / *PAGE_SIZE;
789            let multi_chunk_size = 2 * READAHEAD_ALIGNMENT + 3 * *PAGE_SIZE;
790            let _vmo = filesystem
791                .register("e".into(), 5, multi_chunk_size, Box::new([]))
792                .expect("register failed");
793            let multi_chunk_file = filesystem.files_by_inode.lock().get(&5).unwrap().clone();
794
795            filesystem.record_refaults(&multi_chunk_file, 0..multi_chunk_size);
796            assert_eq!(pager.page_refault_counter().read(Ordering::Relaxed), num_pages + 1);
797
798            filesystem.record_refaults(&multi_chunk_file, READAHEAD_ALIGNMENT..multi_chunk_size);
799            assert_eq!(
800                pager.page_refault_counter().read(Ordering::Relaxed),
801                num_pages + 1 + pages_per_chunk + 3
802            );
803        }
804
805        // After dropping all VMOs, we expect the pager to clean up.
806        loop {
807            if filesystem.files_by_inode.lock().is_empty() {
808                break;
809            }
810            // The pager is running on different threads, hence:
811            std::thread::sleep(Duration::from_millis(10));
812        }
813
814        // When a file is unloaded entirely and re-registered, its refault data is discarded.
815        // The first read of the newly registered file must not count as a refault.
816        let file_size = (6 + 1 + 5 + 4) * 1024 + 100;
817        let vmo = filesystem
818            .register(
819                "d".into(),
820                4,
821                file_size,
822                Box::new([
823                    PagerExtent { logical: 6..7, physical_block: 0 },
824                    PagerExtent { logical: 12..13, physical_block: 1 },
825                ]),
826            )
827            .expect("register failed");
828        let refaults_before = pager.page_refault_counter().read(Ordering::Relaxed);
829        let mut buf = vec![1; 100];
830        vmo.read(&mut buf, 0).expect("read failed");
831        sync_with_pager(&filesystem, 101);
832        assert_eq!(pager.page_refault_counter().read(Ordering::Relaxed), refaults_before);
833    }
834}