blob: 06ecf902e747e083944b9985e90766d988aa1aad [file] [edit]
// Copyright 2023 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#ifndef ZIRCON_KERNEL_VM_INCLUDE_VM_VM_ADDRESS_REGION_OBSERVER_H_
#define ZIRCON_KERNEL_VM_INCLUDE_VM_VM_ADDRESS_REGION_OBSERVER_H_
#include <assert.h>
#include <lib/page/size.h>
#include <stdint.h>
#include <zircon/types.h>
#include <ktl/algorithm.h>
#include <ktl/optional.h>
// # VmAddressRegion Augmented B-Tree Support
//
// VmAddressRegion maintains an ordered set of non-overlapping subregions in a RegionList,
// which is implemented as a B-Tree. To efficiently find gaps for new allocations,
// the tree is augmented with subtree metadata:
// 1. min_addr: The lowest address in the subtree.
// 2. max_addr: The highest (inclusive) address in the subtree.
// 3. max_gap: The largest unallocated gap between any two adjacent regions in the subtree.
//
// ## Calculation Logic
//
// The augmentation state is maintained such that each entry in the B-Tree represents the aggregated
// state of the subtree rooted at that entry.
//
// ### Leaf Nodes
// For a leaf node containing a sequence of regions [R0, R1, ..., Rn]:
// - min_addr = R0.base
// - max_addr = Rn.end - 1
// - max_gap = max(R1.base - R0.end, R2.base - R1.end, ..., Rn.base - R(n-1).end)
//
// ### Intermediate Nodes
// For an intermediate node with entries [E0, E1, ..., Em], where each entry represents a child
// subtree:
// - min_addr = E0.min_addr
// - max_addr = Em.max_addr
// - max_gap = max(
// E0.max_gap, E1.max_gap, ..., Em.max_gap, // Gaps within child subtrees
// E1.min_addr - E0.max_addr - 1, // Gaps between child subtrees
// E2.min_addr - E1.max_addr - 1,
// ...
// )
//
// ## Example B-Tree Node Diagram
//
// The following diagram illustrates an augmented B-Tree node with four entries (E0, E1, E2, E3).
// Each entry is a subtree with its own min_addr, max_addr, and max_gap values.
//
// Node State
// +-------------------------------------------------------+
// | min_addr: 0, max_addr: 59, max_gap: 15 |
// +-------------------------------------------------------+
// | | |
// _____________| __________|__________ |____________
// | | | |
// V V V V
// +-------------+ +-------------+ +-------------+ +-------------+
// | Subtree E0 | (Gap) | Subtree E1 | (Gap)| Subtree E2 | (Gap) | Subtree E3 |
// | [0, 9] | 10 | [20, 29] | 5 | [35, 39] | 15 | [55, 59] |
// | max_gap: 5 | | max_gap: 2 | | max_gap: 8 | | max_gap: 3 |
// +-------------+ +-------------+ +-------------+ +-------------+
//
// Calculation:
// - min_addr = E0.min (0)
// - max_addr = E3.max (59)
// - max_gap = max(E0.max_gap(5), Gap(E0,E1)(10), E1.max_gap(2), Gap(E1,E2)(5),
// E2.max_gap(8), Gap(E2,E3)(15), E3.max_gap(3))
// = max(5, 10, 2, 5, 8, 15, 3) = 15
//
struct VmAddressRegionObserver {
struct State {
// We store the min_addr, max_addr and max_gap in 16 bytes.
// Since addresses and sizes are always multiples of kPageSize (and thus
// page-aligned), we can pack the max_gap (measured in pages) into the
// lower bits of the first uint64_t, which otherwise stores the page-aligned
// min_addr.
uint64_t max_gap_pages : kPageShift;
uint64_t min_addr_page : (64 - kPageShift);
// max_addr is the inclusive top byte of the range, and is therefore not page aligned.
uint64_t max_addr;
// Due to the limited bits from the page alignment there is a maximum size of gap we can store.
// The largest gap, kMaxGapPages, therefore becomes a sentinel value representing infinity. A
// consequence is that when performing a search a subtree with a gap of kMaxGapPages must always
// be descended into. Pragmatically the main optimization of tracking max gaps is for when
// entropy is at or near 0, and we are attempting to skip runs of adjacent mappings, i.e. where
// max_gap is zero.
static constexpr uint64_t kMaxGapPages = (1UL << kPageShift) - 1;
vaddr_t min_addr() const { return static_cast<vaddr_t>(min_addr_page) << kPageShift; }
void set_min_addr(vaddr_t addr) {
DEBUG_ASSERT(IsPageRounded(addr));
// GCC is unable to understand that a value shifted by a certain number of bits will always
// fit into a bitfield reduced by that many bits, so just disable the warning for it.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
min_addr_page = addr >> kPageShift;
#pragma GCC diagnostic pop
}
ktl::optional<size_t> max_gap() const {
if (max_gap_pages != kMaxGapPages) {
return static_cast<size_t>(max_gap_pages) << kPageShift;
}
return ktl::nullopt;
}
void set_max_gap(size_t gap) {
// GCC is unable to understand that we will never store a value larger than kMaxGapPages,
// which is a constexpr that fits in the number of bits in max_gap_pages by definition and so
// we disable the relevant warning.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
max_gap_pages = ktl::min(gap >> kPageShift, kMaxGapPages);
#pragma GCC diagnostic pop
}
bool operator==(const State& other) const {
return max_gap_pages == other.max_gap_pages && min_addr_page == other.min_addr_page &&
max_addr == other.max_addr;
}
bool operator!=(const State& other) const { return !(*this == other); }
};
using AugmentedState = State;
// Implementation of BTree Observer::Calculate. Find the min_addr, max_addr and max_gap for the
// provided iterator range.
template <typename iterator>
static State Calculate(iterator node_start, iterator node_end) {
State state = {};
uint64_t max_gap = 0;
state.set_min_addr((*node_start).second->base());
auto it = node_start;
auto prev = it;
auto endd = node_end;
endd++;
for (it++; it != endd; it++) {
vaddr_t prev_top = (*prev).second->base() + ((*prev).second->size() - 1);
// Regions can temporarily overlap, so only consider if there is actually a gap.
if (prev_top < (*it).second->base()) {
uint64_t gap = ((*it).second->base() - prev_top) - 1;
max_gap = ktl::max(max_gap, gap);
}
prev = it;
}
state.max_addr = (*node_end).second->base() + ((*node_end).second->size() - 1);
state.set_max_gap(max_gap);
return state;
}
// Implementation of BTree Observer::Fold. Determines the min_addr, max_addr and max_gap of two
// adjacent subtrees based on their provided State.
static State Fold(State left, State right) {
ASSERT(left.min_addr() <= right.min_addr());
ASSERT(left.max_addr <= right.max_addr);
State state = {};
state.set_min_addr(left.min_addr());
state.max_addr = right.max_addr;
uint64_t max_gap = ktl::max(left.max_gap_pages, right.max_gap_pages) << kPageShift;
uint64_t inter_gap = 0;
// Regions can temporarily overlap, so only consider if there is actually a gap.
if (left.max_addr < right.min_addr()) {
inter_gap = (right.min_addr() - left.max_addr) - 1;
}
max_gap = ktl::max(max_gap, inter_gap);
state.set_max_gap(max_gap);
return state;
}
};
#endif // ZIRCON_KERNEL_VM_INCLUDE_VM_VM_ADDRESS_REGION_OBSERVER_H_