blob: 7a6bc6a0b0e2cb16ddb0f910db943f60b63b8629 [file] [edit]
// 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