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, PageFaultCounter};
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_fault_counter: PageFaultCounter,
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_fault_counter: PageFaultCounter::new().map_err(|error| {
147                log_error!(error:?; "PageFaultCounter::new failed");
148                errno!(EINVAL)
149            })?,
150        })
151    }
152
153    pub fn page_fault_counter(&self) -> &PageFaultCounter {
154        &self.page_fault_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_faults(&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 total pages in `supplied` as page faults, count the pages in `supplied` that have
462    /// already been supplied before as refaults, and mark all of them for future refaults.
463    fn record_faults(&self, file: &PagedFile, supplied: Range<u64>) {
464        let page_size = *PAGE_SIZE;
465        let total_pages = (supplied.end - supplied.start) / page_size;
466        if total_pages > 0 {
467            self.pager.page_fault_counter.increment_faults(total_pages, Ordering::Relaxed);
468        }
469
470        let pages_per_chunk = READAHEAD_ALIGNMENT / page_size;
471
472        let first_chunk = supplied.start / READAHEAD_ALIGNMENT;
473        let full_end_chunk = supplied.end / READAHEAD_ALIGNMENT;
474
475        let mut refault_pages =
476            file.chunks_supplied.test_and_set_range(first_chunk, full_end_chunk) * pages_per_chunk;
477
478        // The end of the range may not be chunk-aligned if it's the last chunk (or if supplying
479        // stopped early).
480        let trailing_pages = (supplied.end % READAHEAD_ALIGNMENT) / page_size;
481        if trailing_pages > 0 {
482            refault_pages +=
483                file.chunks_supplied.test_and_set_range(full_end_chunk, full_end_chunk + 1)
484                    * trailing_pages;
485        }
486
487        if refault_pages > 0 {
488            self.pager.page_fault_counter.increment_refaults(refault_pages, Ordering::Relaxed);
489        }
490    }
491
492    fn watch_for_zero_children(&self, vmo: &zx::Vmo, inode_num: u32) -> Result<(), zx::Status> {
493        vmo.wait_async(
494            &self.pager.port,
495            self.port_key_for_inode(inode_num),
496            zx::Signals::VMO_ZERO_CHILDREN,
497            zx::WaitAsyncOpts::empty(),
498        )
499    }
500
501    fn on_zero_children(&self, inode_num: u32) -> Result<(), Errno> {
502        let mut files = self.files_by_inode.lock();
503        let file = files.entry(inode_num);
504        if let Entry::Occupied(o) = file {
505            let vmo = &o.get().vmo;
506            match vmo.info() {
507                Ok(info) => {
508                    if info.num_children == 0 {
509                        // This is a true signal, so we can remove this entry.
510                        o.remove();
511                    } else {
512                        // This shouldn't fail, and there's not much we can do
513                        // if it does.
514                        if let Err(error) = self.watch_for_zero_children(vmo, inode_num) {
515                            log_error!(
516                                error:?;
517                                "watch_for_zero_children failed"
518                            );
519                        }
520                    }
521                }
522                Err(error) => log_error!(error:?; "Vmo::info failed"),
523            }
524        }
525        Ok(())
526    }
527
528    fn port_key_for_inode(&self, inode_num: u32) -> u64 {
529        (self.id as u64) << 32 | inode_num as u64
530    }
531
532    fn id(&self) -> u32 {
533        self.id
534    }
535}
536
537/// Per file state needed by the pager.
538struct PagedFile {
539    /// The main VMO.  We always hand out children of this VMO.
540    vmo: zx::Vmo,
541
542    /// The extents for the file, which will be sorted and not overlapping.  There can be holes i.e.
543    /// zeroed ranges within the file.
544    extents: Box<[PagerExtent]>,
545
546    /// Tracks `READAHEAD_ALIGNMENT`-sized chunks that have already been supplied to the kernel for
547    /// this file.
548    chunks_supplied: AtomicBitVec,
549}
550
551/// A single extent.
552pub struct PagerExtent {
553    pub logical: Range<u32>,
554    pub physical_block: u64,
555}
556
557/// SupplyHelper exists to make dealing with misalignment easier.
558struct SupplyHelper<'a> {
559    transfer_vmo: &'a zx::Vmo,
560    buffer: &'a mut [u8],
561    target_vmo: &'a zx::Vmo,
562    offset: u64,
563    pager: &'a Pager,
564    page_size: u64,
565    buf_len: usize,
566}
567
568impl<'a> SupplyHelper<'a> {
569    fn new(
570        transfer_vmo: &'a zx::Vmo,
571        buffer: &'a mut [u8],
572        target_vmo: &'a zx::Vmo,
573        offset: u64,
574        pager: &'a Pager,
575    ) -> Self {
576        Self { transfer_vmo, buffer, target_vmo, offset, pager, page_size: *PAGE_SIZE, buf_len: 0 }
577    }
578
579    /// Returns the offset up to which pages have been successfully supplied.
580    fn supplied_end(&self) -> u64 {
581        self.offset
582    }
583
584    /// Zeroes `len` bytes.
585    fn zero(&mut self, mut len: u64) -> Result<(), zx::Status> {
586        let unaligned = self.buf_len as u64 % self.page_size;
587        if unaligned > 0 {
588            let amount = std::cmp::min(self.page_size - unaligned, len);
589            self.buffer[self.buf_len..self.buf_len + amount as usize].fill(0);
590            self.buf_len += amount as usize;
591            len -= amount;
592            self.supply_pages()?;
593        }
594        // Zero whole pages by supplying pages from the zero VMO.
595        while len >= self.page_size {
596            let amount =
597                if len >= ZERO_VMO_SIZE { ZERO_VMO_SIZE } else { len - len % self.page_size };
598            self.pager.pager.supply_pages(
599                self.target_vmo,
600                self.offset..self.offset + amount,
601                &self.pager.zero_vmo,
602                0,
603            )?;
604            self.offset += amount;
605            len -= amount;
606        }
607        // And now the remaining partial page...
608        self.buffer[self.buf_len..self.buf_len + len as usize].fill(0);
609        self.buf_len += len as usize;
610        Ok(())
611    }
612
613    /// Flushes whole pages.
614    fn supply_pages(&mut self) -> Result<(), zx::Status> {
615        if self.buf_len as u64 >= self.page_size {
616            let len = self.buf_len - self.buf_len % self.page_size as usize;
617            self.pager.pager.supply_pages(
618                self.target_vmo,
619                self.offset..self.offset + len as u64,
620                self.transfer_vmo,
621                0,
622            )?;
623            // Move any remaining data to the beginning of the buffer.
624            self.buffer.copy_within(len..self.buf_len, 0);
625            self.buf_len -= len;
626            self.offset += len as u64;
627        }
628        Ok(())
629    }
630
631    /// Fills the buffer by calling the provided callback.  Returns the amount of data filled.
632    fn fill_buf(
633        &mut self,
634        f: impl FnOnce(&mut [u8]) -> Result<usize, zx::Status>,
635    ) -> Result<usize, zx::Status> {
636        let amount = f(&mut self.buffer[self.buf_len..])?;
637        self.buf_len += amount;
638        self.supply_pages()?;
639        Ok(amount)
640    }
641
642    /// Zeroes out to at least `end`, then pads to a page boundary and supplies those pages.
643    fn finish(&mut self, mut end: u64) -> Result<(), zx::Status> {
644        let byte_offset = self.offset + self.buf_len as u64;
645        end = std::cmp::max(end, byte_offset);
646        end = end + self.page_size - 1;
647        end -= end % self.page_size;
648        self.zero(end - byte_offset)
649    }
650
651    /// Fails the request up to the given offset with `error`.
652    fn fail_to(&mut self, end: u64, error: zx::Status) {
653        if self.offset < end {
654            log_warn!(error:?; "Failing page-in, range: {:?}", self.offset..end);
655            // The pager is fussy about what errors we can return here, so we always return IO.
656            match self.pager.pager.op_range(
657                zx::PagerOp::Fail(zx::Status::IO),
658                self.target_vmo,
659                self.offset..end,
660            ) {
661                Ok(()) => {}
662                Err(error) => log_error!(error:?; "Failed to report error"),
663            }
664            self.offset = end;
665            self.buf_len = 0;
666        }
667    }
668}
669
670#[cfg(test)]
671mod tests {
672    use super::{Filesystem, Pager, PagerExtent, READAHEAD_ALIGNMENT};
673
674    use starnix_core::mm::PAGE_SIZE;
675    use std::sync::Arc;
676    use std::sync::atomic::Ordering;
677    use std::time::Duration;
678
679    // Synchronize with the single pager thread by registering and immediately dropping a VMO.
680    // `vmo.read()` returns as soon as the pager thread supplies the requested pages, but before it
681    // calls `record_faults` for that packet. Since the pager thread processes packets in order,
682    // waiting for it to process the dropped VMO's `VMO_ZERO_CHILDREN` signal guarantees prior
683    // packets have completed.
684    fn sync_with_pager(filesystem: &Filesystem, inode: u32) {
685        let sync_vmo =
686            filesystem.register("sync", inode, 1, Box::new([])).expect("register failed");
687        drop(sync_vmo);
688        while filesystem.files_by_inode.lock().contains_key(&inode) {
689            std::thread::sleep(Duration::from_millis(1));
690        }
691    }
692
693    #[::fuchsia::test]
694    async fn test_pager() {
695        let backing_vmo = zx::Vmo::create(1 * 1024 * 1024).expect("Vmo::craete failed");
696        let backing_vmo_clone =
697            backing_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("failed handle dup");
698
699        let pager = Arc::new(Pager::new().expect("Pager::new failed"));
700        let filesystem = Arc::new(
701            Filesystem::new(pager.clone(), backing_vmo_clone, 1024)
702                .expect("Filesystem::new failed"),
703        );
704
705        pager.add_filesystem(filesystem.clone());
706
707        {
708            pager.start_threads();
709
710            // With no extent, we expect it to return zeroed data.
711            let vmo = filesystem.register("a".into(), 1, 5, Box::new([])).expect("register failed");
712
713            let mut buf = vec![1; 5];
714            vmo.read(&mut buf, 0).expect("read failed");
715
716            assert_eq!(&buf, &[0; 5]);
717
718            // A single extent:
719            let vmo = filesystem
720                .register(
721                    "b".into(),
722                    2,
723                    5,
724                    Box::new([PagerExtent { logical: 0..1, physical_block: 0 }]),
725                )
726                .expect("register failed");
727            backing_vmo.write(b"hello", 0).expect("write failed");
728            vmo.read(&mut buf, 0).expect("read failed");
729
730            assert_eq!(&buf, b"hello");
731
732            // A file with sparse ranges: 6 sparse, 1 extent, 5 more sparse, 1 extent, 4 sparse + a
733            // bit.
734            let file_size = (6 + 1 + 5 + 4) * 1024 + 100;
735            let vmo = filesystem
736                .register(
737                    "c".into(),
738                    3,
739                    file_size,
740                    Box::new([
741                        PagerExtent { logical: 6..7, physical_block: 0 },
742                        PagerExtent { logical: 12..13, physical_block: 1 },
743                    ]),
744                )
745                .expect("register failed");
746            backing_vmo.write(b"there", 1024).expect("write failed");
747            let mut buf = vec![1; file_size as usize];
748            vmo.read(&mut buf, 0).expect("read failed");
749
750            let mut expected = vec![0; file_size as usize];
751            expected[6 * 1024..6 * 1024 + 5].copy_from_slice(b"hello");
752            expected[12 * 1024..12 * 1024 + 5].copy_from_slice(b"there");
753            assert_eq!(&buf, &expected);
754
755            // Use the same file, but initiate a read that starts after the first extent.
756            let vmo = filesystem
757                .register(
758                    "d".into(),
759                    4,
760                    file_size,
761                    Box::new([
762                        PagerExtent { logical: 6..7, physical_block: 0 },
763                        PagerExtent { logical: 12..13, physical_block: 1 },
764                    ]),
765                )
766                .expect("register failed");
767
768            let offset = 9000;
769            let mut buf = vec![1; (file_size - offset) as usize];
770            vmo.read(&mut buf, offset).expect("read failed");
771
772            assert_eq!(&buf, &expected[offset as usize..]);
773
774            sync_with_pager(&filesystem, 100);
775
776            // Initial reads should not register any refaults.
777            assert_eq!(pager.page_fault_counter().read_refaults(Ordering::Relaxed), 0);
778
779            let file = filesystem.files_by_inode.lock().get(&4).unwrap().clone();
780            let num_pages = file_size.div_ceil(*PAGE_SIZE);
781            let initial_faults = pager.page_fault_counter().read_faults(Ordering::Relaxed);
782            assert_eq!(initial_faults, 2 + 2 * num_pages);
783
784            // The kernel asks for pages it has already been given: every page supplied again
785            // counts as a refault (even when the last chunk is smaller than READAHEAD_ALIGNMENT).
786            filesystem.record_faults(&file, 0..num_pages * *PAGE_SIZE);
787            assert_eq!(pager.page_fault_counter().read_refaults(Ordering::Relaxed), num_pages);
788            assert_eq!(
789                pager.page_fault_counter().read_faults(Ordering::Relaxed),
790                initial_faults + num_pages
791            );
792
793            // If nothing could be supplied, nothing is counted.
794            filesystem.record_faults(&file, 0..0);
795            assert_eq!(pager.page_fault_counter().read_refaults(Ordering::Relaxed), num_pages);
796
797            // If supplying stops early, only the pages that were supplied are counted.
798            filesystem.record_faults(&file, 0..*PAGE_SIZE);
799            assert_eq!(pager.page_fault_counter().read_refaults(Ordering::Relaxed), num_pages + 1);
800
801            // Verify refault accounting across full chunks and a partial trailing chunk.
802            let pages_per_chunk = READAHEAD_ALIGNMENT / *PAGE_SIZE;
803            let multi_chunk_size = 2 * READAHEAD_ALIGNMENT + 3 * *PAGE_SIZE;
804            let _vmo = filesystem
805                .register("e".into(), 5, multi_chunk_size, Box::new([]))
806                .expect("register failed");
807            let multi_chunk_file = filesystem.files_by_inode.lock().get(&5).unwrap().clone();
808
809            filesystem.record_faults(&multi_chunk_file, 0..multi_chunk_size);
810            assert_eq!(pager.page_fault_counter().read_refaults(Ordering::Relaxed), num_pages + 1);
811
812            filesystem.record_faults(&multi_chunk_file, READAHEAD_ALIGNMENT..multi_chunk_size);
813            assert_eq!(
814                pager.page_fault_counter().read_refaults(Ordering::Relaxed),
815                num_pages + 1 + pages_per_chunk + 3
816            );
817        }
818
819        // After dropping all VMOs, we expect the pager to clean up.
820        loop {
821            if filesystem.files_by_inode.lock().is_empty() {
822                break;
823            }
824            // The pager is running on different threads, hence:
825            std::thread::sleep(Duration::from_millis(10));
826        }
827
828        // When a file is unloaded entirely and re-registered, its refault data is discarded.
829        // The first read of the newly registered file must not count as a refault.
830        let file_size = (6 + 1 + 5 + 4) * 1024 + 100;
831        let vmo = filesystem
832            .register(
833                "d".into(),
834                4,
835                file_size,
836                Box::new([
837                    PagerExtent { logical: 6..7, physical_block: 0 },
838                    PagerExtent { logical: 12..13, physical_block: 1 },
839                ]),
840            )
841            .expect("register failed");
842        let refaults_before = pager.page_fault_counter().read_refaults(Ordering::Relaxed);
843        let mut buf = vec![1; 100];
844        vmo.read(&mut buf, 0).expect("read failed");
845        sync_with_pager(&filesystem, 101);
846        assert_eq!(pager.page_fault_counter().read_refaults(Ordering::Relaxed), refaults_before);
847    }
848}