| // Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <lib/zx/pager.h> | |
| #include <lib/zx/port.h> | |
| #include <lib/zx/vmar.h> | |
| #include <lib/zx/vmo.h> | |
| #include <zircon/assert.h> | |
| #include <zircon/errors.h> | |
| #include <zircon/syscalls.h> | |
| #include <zircon/syscalls/port.h> | |
| #include <algorithm> | |
| #include <atomic> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <latch> | |
| #include <random> | |
| #include <thread> | |
| #include <vector> | |
| #include <perftest/perftest.h> | |
| #include "assert.h" | |
| namespace { | |
| // This microbenchmark measures the latency of page faults on VMOs backed by a user pager. | |
| // It uses a `SimplePager` to supply trivial data (a pre-allocated VMO filled with '1's) | |
| // to satisfy page requests as quickly as possible. This allows us to measure the pure | |
| // overhead of the kernel/user context switch, message passing on the pager port, and | |
| // the MMU mapping updates, without being bottlenecked by actual disk or network I/O. | |
| class SimplePager { | |
| public: | |
| explicit SimplePager(size_t size) { | |
| ASSERT_OK(zx::pager::create(0, &pager_)); | |
| ASSERT_OK(zx::port::create(0, &port_)); | |
| ASSERT_OK(zx::vmo::create(size, 0, &supply_vmo_)); | |
| uintptr_t addr; | |
| ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, supply_vmo_, 0, | |
| size, &addr)); | |
| std::memset(reinterpret_cast<void*>(addr), 1, size); | |
| ASSERT_OK(zx::vmar::root_self()->unmap(addr, size)); | |
| ASSERT_OK(pager_.create_vmo(0, port_, 0, size, &vmo_)); | |
| std::latch started(1); | |
| thread_ = std::thread([this, &started]() { | |
| started.count_down(); | |
| PagerLoop(); | |
| }); | |
| // We wait here for the thread_ to actually start before returning, ensuring that we don't | |
| // measure thread startup time in the user pager benchmark. | |
| started.wait(); | |
| } | |
| ~SimplePager() { | |
| zx_port_packet_t packet = {}; | |
| packet.type = ZX_PKT_TYPE_USER; | |
| packet.key = kQuitKey; | |
| ASSERT_OK(port_.queue(&packet)); | |
| thread_.join(); | |
| } | |
| SimplePager(const SimplePager&) = delete; | |
| SimplePager(SimplePager&&) = delete; | |
| SimplePager& operator=(const SimplePager&) = delete; | |
| SimplePager& operator=(SimplePager&&) = delete; | |
| zx::unowned_vmo vmo() const { return zx::unowned_vmo(vmo_); } | |
| private: | |
| // The port wait blocks indefinitely. We need a way to cleanly unblock and terminate | |
| // the pager thread when the SimplePager is destroyed at the end of each benchmark run. | |
| // We queue a user packet with this specific key to signal the thread to exit. | |
| static constexpr uint64_t kQuitKey = 1; | |
| void PagerLoop() { | |
| while (true) { | |
| zx_port_packet_t packet; | |
| ASSERT_OK(port_.wait(zx::time::infinite(), &packet)); | |
| if (packet.type == ZX_PKT_TYPE_USER) { | |
| if (packet.key == kQuitKey) { | |
| break; | |
| } | |
| continue; | |
| } | |
| if (packet.type == ZX_PKT_TYPE_PAGE_REQUEST && | |
| packet.page_request.command == ZX_PAGER_VMO_READ) { | |
| uint64_t req_offset = packet.page_request.offset; | |
| uint64_t req_length = packet.page_request.length; | |
| ASSERT_OK(pager_.supply_pages(vmo_, req_offset, req_length, supply_vmo_, req_offset)); | |
| } | |
| } | |
| } | |
| zx::pager pager_; | |
| zx::port port_; | |
| zx::vmo supply_vmo_; | |
| zx::vmo vmo_; | |
| std::thread thread_; | |
| }; | |
| // This test measures the time it takes to fault pages sequentially in a user-pager-backed VMO. | |
| bool UserPagerSequentialFaultsTest(perftest::RepeatState* state) { | |
| const size_t kPageSize = zx_system_get_page_size(); | |
| const size_t kNumPages = 1024; // 4MB with 4K pages | |
| const size_t kTotalSize = kNumPages * kPageSize; | |
| state->DeclareStep("setup_ignore"); | |
| state->DeclareStep("page_fault"); | |
| state->DeclareStep("teardown_ignore"); | |
| std::optional<SimplePager> pager; | |
| size_t vmo_offset = 0; | |
| uintptr_t mapping_addr = 0; | |
| auto cleanup = [&]() { | |
| if (mapping_addr != 0) { | |
| zx::vmar::root_self()->unmap(mapping_addr, kTotalSize); | |
| pager.reset(); | |
| mapping_addr = 0; | |
| } | |
| }; | |
| while (state->KeepRunning()) { | |
| if (mapping_addr == 0) { | |
| pager.emplace(kTotalSize); | |
| zx::unowned_vmo vmo = pager->vmo(); | |
| ASSERT_OK(zx::vmar::root_self()->map(ZX_VM_PERM_READ, 0, *vmo, 0, kTotalSize, &mapping_addr)); | |
| } | |
| state->NextStep(); | |
| perftest::DoNotOptimize(reinterpret_cast<uint8_t*>(mapping_addr)[vmo_offset]); | |
| state->NextStep(); | |
| vmo_offset += kPageSize; | |
| if (vmo_offset >= kTotalSize) { | |
| cleanup(); | |
| vmo_offset = 0; | |
| } | |
| } | |
| cleanup(); | |
| return true; | |
| } | |
| void RegisterTests() { | |
| perftest::RegisterTest("UserPager/PageFault/Sequential", UserPagerSequentialFaultsTest); | |
| } | |
| PERFTEST_CTOR(RegisterTests) | |
| } // namespace |