| // Copyright 2016 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_PAGE_LIST_H_ |
| #define ZIRCON_KERNEL_VM_INCLUDE_VM_VM_PAGE_LIST_H_ |
| |
| #include <align.h> |
| #include <bits.h> |
| #include <lib/btree.h> |
| #include <lib/fit/function.h> |
| #include <lib/page/size.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <fbl/canary.h> |
| #include <fbl/intrusive_wavl_tree.h> |
| #include <fbl/macros.h> |
| #include <ktl/algorithm.h> |
| #include <ktl/unique_ptr.h> |
| #include <vm/page.h> |
| #include <vm/pmm.h> |
| |
| class VmPageList; |
| class VmPageSpliceList; |
| class VMPLCursor; |
| |
| // RAII helper for representing content in a page list node. This supports being in one of five |
| // states |
| // * Empty - Contains nothing. |
| // * Page p - Contains a vm_page 'p'. This 'p' is considered owned by this wrapper and |
| // `ReleasePage` must be called to give up ownership. |
| // * Reference r - Contains a reference 'r' to some content. This 'r' is considered owned by this |
| // wrapper and `ReleaseReference` must be called to give up ownership. |
| // * Marker - Indicates that whilst not a page, it is also not empty. Markers can be used to |
| // separate the distinction between "there's no page because we've deduped to the |
| // zero page" (a `Marker` is inserted) and "there's no page because our parent |
| // contains the content" (which is represented as `Empty`). |
| // * Interval - Indicates that this page is part of a sparse page interval. An interval will |
| // have a Start sentinel, and an End sentinel, and all offsets that lie between the |
| // two will be empty. If the interval spans a single page, it will be represented |
| // as a Slot sentinel, which is conceptually the same as both a Start and an End |
| // sentinel. |
| // * ParentContent - Indicates that there might be content for this slot, but the page list in the |
| // parent must be checked for it. The different between `Empty`, which can also |
| // indicate that the parent must be searched, and `ParentContent` is up to the |
| // specific VMO. |
| // |
| // There are certain invariants that the page list tries to maintain at all times. It might not |
| // always be possible to enforce these as the checks involved might be expensive, however it is |
| // important that any code that manipulates the page list abide by them, primarily to keep the |
| // memory occupied by the page list nodes in check. |
| // 1. Page list nodes cannot be completely empty i.e. they must contain at least one non-empty slot. |
| // 2. Any intervals in the page list should span a maximal range. In other words, there should not |
| // be consecutive intervals in the page list which it would have been possible to represent with a |
| // single interval instead. |
| class VmPageOrMarker { |
| public: |
| // A PageType that otherwise holds a null pointer is considered to be Empty. |
| VmPageOrMarker() : raw_(kPageType) {} |
| ~VmPageOrMarker() { DEBUG_ASSERT(!IsPageOrRef()); } |
| VmPageOrMarker(VmPageOrMarker&& other) noexcept : raw_(other.Release()) {} |
| VmPageOrMarker(const VmPageOrMarker&) = delete; |
| VmPageOrMarker& operator=(const VmPageOrMarker&) = delete; |
| |
| // Minimal wrapper around a uint32_t to provide stronger typing in code to prevent accidental |
| // mixing of references and other values. |
| // Provides a way to query the required alignment of the references and does debug enforcement of |
| // this. |
| class ReferenceValue { |
| public: |
| // kAlignBits represents the number of low bits in a reference that must be zero so they can be |
| // used for internal metadata. This is declared here for convenience, and is asserted to be in |
| // sync with the private VmPageOrMarker::kTypeBits. |
| static constexpr int kAlignBits = 3; |
| |
| explicit constexpr ReferenceValue(uint32_t raw) : value_(raw) { |
| DEBUG_ASSERT((value_ & BIT_MASK32(kAlignBits)) == 0); |
| } |
| |
| uint32_t value() const { return value_; } |
| |
| private: |
| uint32_t value_; |
| }; |
| |
| // Returns a reference to the underlying vm_page*. Is only valid to call if `IsPage` is true. |
| vm_page* Page() const { |
| DEBUG_ASSERT(IsPage()); |
| // Do not need to mask any bits out of raw_, since Page has 0's for the type anyway. |
| static_assert(kPageType == 0); |
| return Pmm::Node().IndexToPage(raw_); |
| } |
| |
| // Returns the paddr_t of the underlying vm_page*. Is only valid to call if `IsPage` is true. Can |
| // be more efficient than performing |Page()->paddr()| as it saves a memory de-reference. |
| paddr_t PageAsPaddr() const { |
| DEBUG_ASSERT(IsPage()); |
| // Do not need to mask any bits out of raw_, since Page has 0's for the type anyway. |
| static_assert(kPageType == 0); |
| return Pmm::Node().IndexToPaddr(raw_); |
| } |
| |
| ReferenceValue Reference() const { |
| DEBUG_ASSERT(IsReference()); |
| return ReferenceValue(raw_ & ~BIT_MASK32(ReferenceValue::kAlignBits)); |
| } |
| |
| // If this is a page, moves the underlying vm_page* out and returns it. After this IsPage will |
| // be false and IsEmpty will be true. |
| [[nodiscard]] vm_page* ReleasePage() { |
| DEBUG_ASSERT(IsPage()); |
| // Do not need to mask any bits out of the Release since Page has 0's for the type |
| // anyway. |
| static_assert(kPageType == 0); |
| return Pmm::Node().IndexToPage(Release()); |
| } |
| |
| [[nodiscard]] ReferenceValue ReleaseReference() { |
| DEBUG_ASSERT(IsReference()); |
| return ReferenceValue(Release() & ~BIT_MASK32(ReferenceValue::kAlignBits)); |
| } |
| |
| // Changes the content from a reference to a page and returns the original reference. |
| [[nodiscard]] VmPageOrMarker::ReferenceValue SwapReferenceForPage(vm_page_t* p) { |
| DEBUG_ASSERT(p); |
| |
| VmPageOrMarker::ReferenceValue ref = ReleaseReference(); |
| *this = VmPageOrMarker::Page(p); |
| |
| return ref; |
| } |
| |
| // Changes the content from a page to a reference and returns the original page. |
| [[nodiscard]] vm_page_t* SwapPageForReference(VmPageOrMarker::ReferenceValue ref) { |
| vm_page_t* page = ReleasePage(); |
| *this = VmPageOrMarker::Reference(ref); |
| |
| return page; |
| } |
| |
| // Changes the content from one reference to a different one and returns the original reference. |
| [[nodiscard]] VmPageOrMarker::ReferenceValue SwapReferenceForReference( |
| VmPageOrMarker::ReferenceValue ref) { |
| const VmPageOrMarker::ReferenceValue old = ReleaseReference(); |
| *this = VmPageOrMarker::Reference(ref); |
| |
| return old; |
| } |
| |
| uint32_t GetMarkerShareCount() const { |
| DEBUG_ASSERT(IsMarker()); |
| return raw_ >> kMarkerShareCountShift; |
| } |
| |
| void SetMarkerShareCount(uint32_t share_count) { |
| DEBUG_ASSERT(IsMarker()); |
| raw_ = kZeroMarkerType | (share_count << (kMarkerShareCountShift)); |
| } |
| |
| void IncrementMarkerShareCount() { |
| DEBUG_ASSERT(IsMarker()); |
| raw_ += (1 << (kMarkerShareCountShift)); |
| } |
| |
| void DecrementMarkerShareCount() { |
| DEBUG_ASSERT(IsMarker()); |
| // It is invalid to decrement marker share count from zero. |
| DEBUG_ASSERT(GetMarkerShareCount() > 0); |
| |
| raw_ -= (1 << (kMarkerShareCountShift)); |
| } |
| |
| [[nodiscard]] VmPageOrMarker Swap(VmPageOrMarker&& other) { |
| uint32_t ret = raw_; |
| raw_ = other.Release(); |
| return VmPageOrMarker(ret); |
| } |
| |
| bool IsPage() const { return !IsEmpty() && (GetType() == kPageType); } |
| bool IsMarker() const { return GetType() == kZeroMarkerType; } |
| bool IsEmpty() const { |
| // A PageType that otherwise holds a null pointer is considered to be Empty. |
| return raw_ == kPageType; |
| } |
| bool IsReference() const { return GetType() == kReferenceType; } |
| bool IsPageOrRef() const { return IsPage() || IsReference(); } |
| bool IsInterval() const { return GetType() == kIntervalType; } |
| bool IsParentContent() const { return GetType() == kParentContentType; } |
| |
| VmPageOrMarker& operator=(VmPageOrMarker&& other) noexcept { |
| // Forbid overriding content, as that would leak it. |
| DEBUG_ASSERT(!IsPageOrRef()); |
| raw_ = other.Release(); |
| return *this; |
| } |
| |
| bool operator==(const VmPageOrMarker& other) const { return raw_ == other.raw_; } |
| |
| bool operator!=(const VmPageOrMarker& other) const { return raw_ != other.raw_; } |
| |
| // A PageType that otherwise holds a null pointer is considered to be Empty. |
| static VmPageOrMarker Empty() { return VmPageOrMarker{kPageType}; } |
| static VmPageOrMarker Marker() { return VmPageOrMarker{kZeroMarkerType}; } |
| static VmPageOrMarker Marker(uint32_t share_count) { |
| return VmPageOrMarker{kZeroMarkerType | (share_count << (kMarkerShareCountShift))}; |
| } |
| static VmPageOrMarker ParentContent() { return VmPageOrMarker{kParentContentType}; } |
| |
| [[nodiscard]] static VmPageOrMarker Page(vm_page* p) { |
| // Ensure the pmm page-to-index has enough zero bits. |
| static_assert(kTypeBits <= PmmNode::kIndexZeroBits); |
| // A null page is incorrect for two reasons |
| // 1. It's a violation of the API of this method |
| // 2. A null page cannot be represented internally as this is used to represent Empty |
| DEBUG_ASSERT(p); |
| const uint32_t raw = Pmm::Node().PageToIndex(p); |
| // Getting zero in |raw| means that |p| lives on the stack or the heap. This is not supported. |
| DEBUG_ASSERT(raw != 0u); |
| // A pointer should be aligned by definition, and hence the low bits should always be zero, but |
| // assert this anyway just in case kTypeBits is increased or someone passed an invalid pointer. |
| DEBUG_ASSERT((raw & BIT_MASK32(kTypeBits)) == 0); |
| return VmPageOrMarker{raw | kPageType}; |
| } |
| |
| [[nodiscard]] static VmPageOrMarker Reference(ReferenceValue ref) { |
| return VmPageOrMarker(ref.value() | kReferenceType); |
| } |
| |
| // Interval type full bit allocation, from LSB to MSB: |
| // Type: 2b = 11 | SentinelType: 2b | IntervalType: 2b | DirtyState: 2b | Length: 24b |
| // note: Length is only valid when the DirtyState is Dirty. |
| |
| // The types of sparse page interval types that are supported. |
| enum class IntervalType : uint32_t { |
| // Represents a range of zero pages. |
| Zero = 0, |
| NumTypes, |
| }; |
| |
| // Sentinel types that are used to represent a sparse page interval. |
| enum class SentinelType : uint32_t { |
| // Represents a single page interval. |
| Slot = 0, |
| // The first page of a multi-page interval. |
| Start, |
| // The last page of a multi-page interval. |
| End, |
| NumSentinels, |
| }; |
| |
| // The remaining bits of an interval type store any information specific to the type of interval |
| // being tracked. The ZeroRange class is defined here to group together the encoding of these bits |
| // specific to IntervalType::Zero. |
| class ZeroRange { |
| public: |
| // This is the same as kIntervalBits. Equality is asserted later where kIntervalBits is defined. |
| static constexpr int kAlignBits = 7; |
| |
| explicit constexpr ZeroRange(uint32_t val) : value_(val) { |
| DEBUG_ASSERT((value_ & BIT_MASK32(kAlignBits)) == 0); |
| } |
| // The various dirty states that a zero interval can be in. Refer to VmCowPages::DirtyState for |
| // an explanation of the states. Note that an AwaitingClean state is not encoded in the interval |
| // state bits. This information is instead stored using the AwaitingCleanLength for convenience, |
| // where a non-zero length indicates that the interval is AwaitingClean. Doing this affords |
| // more convenient splitting and merging of intervals. |
| enum class DirtyState : uint32_t { |
| Untracked = 0, |
| Clean, |
| Dirty, |
| NumStates, |
| }; |
| |
| ZeroRange(uint32_t val, DirtyState state) : value_(val) { |
| DEBUG_ASSERT((value_ & BIT_MASK32(kAlignBits)) == 0); |
| DEBUG_ASSERT(GetDirtyState() == DirtyState::Untracked); |
| SetDirtyState(state); |
| } |
| uint32_t value() const { return value_; } |
| |
| // For zero range tracking, we also need to track dirty state information, and if the interval |
| // is AwaitingClean, the length that is AwaitingClean. |
| static constexpr uint64_t kDirtyStateBits = VM_PAGE_OBJECT_DIRTY_STATE_BITS; |
| static_assert(static_cast<uint32_t>(DirtyState::NumStates) <= (1 << kDirtyStateBits)); |
| static constexpr int kDirtyStateShift = kAlignBits; |
| DirtyState GetDirtyState() const { |
| return static_cast<DirtyState>((value_ & (BIT_MASK32(kDirtyStateBits) << kDirtyStateShift)) >> |
| kDirtyStateShift); |
| } |
| void SetDirtyState(DirtyState state) { |
| // Only allow dirty and untracked zero ranges for now. |
| DEBUG_ASSERT(state == DirtyState::Dirty || state == DirtyState::Untracked); |
| // Clear the old state. |
| value_ &= ~(BIT_MASK32(kDirtyStateBits) << kDirtyStateShift); |
| // Set the new state. |
| value_ |= static_cast<uint32_t>(state) << kDirtyStateShift; |
| } |
| |
| static constexpr uint64_t kAwaitingCleanLengthShift = kAlignBits + kDirtyStateBits; |
| // Assert that we are not overlapping with the dirty state bits. |
| static_assert(kAwaitingCleanLengthShift >= kDirtyStateShift + kDirtyStateBits); |
| static_assert(kAwaitingCleanLengthShift <= kPageShift); |
| |
| // The AwaitingCleanLength will always be a page-aligned length, so we can mask out the low |
| // kPageShift bits and store only the upper bits. |
| void SetAwaitingCleanLength(uint64_t len) { |
| DEBUG_ASSERT(len == 0 || GetDirtyState() == DirtyState::Dirty); |
| DEBUG_ASSERT(IS_ROUNDED(len, kPageSize)); |
| len = (len >> kPageShift) << kAwaitingCleanLengthShift; |
| // Clear the old value. |
| value_ &= BIT_MASK32(kAwaitingCleanLengthShift); |
| // Set the new value. |
| value_ |= static_cast<uint32_t>(len); |
| } |
| uint64_t GetAwaitingCleanLength() const { |
| uint64_t len = value_ & ~BIT_MASK32(kAwaitingCleanLengthShift); |
| return (len >> kAwaitingCleanLengthShift) << kPageShift; |
| } |
| |
| private: |
| uint32_t value_; |
| }; |
| using IntervalDirtyState = ZeroRange::DirtyState; |
| |
| // Getters and setters for the interval type. |
| bool IsIntervalStart() const { |
| return IsInterval() && GetIntervalSentinel() == SentinelType::Start; |
| } |
| bool IsIntervalEnd() const { return IsInterval() && GetIntervalSentinel() == SentinelType::End; } |
| bool IsIntervalSlot() const { |
| return IsInterval() && GetIntervalSentinel() == SentinelType::Slot; |
| } |
| bool IsIntervalZero() const { return IsInterval() && GetIntervalType() == IntervalType::Zero; } |
| |
| // Getters and setter for the zero interval type. |
| bool IsZeroIntervalClean() const { |
| DEBUG_ASSERT(IsIntervalZero()); |
| return ZeroRange(raw_ & ~BIT_MASK32(kIntervalBits)).GetDirtyState() == |
| ZeroRange::DirtyState::Clean; |
| } |
| bool IsZeroIntervalDirty() const { |
| DEBUG_ASSERT(IsIntervalZero()); |
| return ZeroRange(raw_ & ~BIT_MASK32(kIntervalBits)).GetDirtyState() == |
| ZeroRange::DirtyState::Dirty; |
| } |
| bool IsZeroIntervalUntracked() const { |
| DEBUG_ASSERT(IsIntervalZero()); |
| return ZeroRange(raw_ & ~BIT_MASK32(kIntervalBits)).GetDirtyState() == |
| ZeroRange::DirtyState::Untracked; |
| } |
| ZeroRange::DirtyState GetZeroIntervalDirtyState() const { |
| DEBUG_ASSERT(IsIntervalZero()); |
| return ZeroRange(raw_ & ~BIT_MASK32(kIntervalBits)).GetDirtyState(); |
| } |
| void SetZeroIntervalAwaitingCleanLength(uint64_t len) { |
| DEBUG_ASSERT(IsIntervalZero()); |
| DEBUG_ASSERT(IsIntervalStart() || IsIntervalSlot()); |
| auto interval = ZeroRange(raw_ & ~BIT_MASK32(kIntervalBits)); |
| interval.SetAwaitingCleanLength(len); |
| raw_ = (raw_ & BIT_MASK32(kIntervalBits)) | interval.value(); |
| } |
| uint64_t GetZeroIntervalAwaitingCleanLength() const { |
| DEBUG_ASSERT(IsIntervalZero()); |
| DEBUG_ASSERT(IsIntervalStart() || IsIntervalSlot()); |
| return ZeroRange(raw_ & ~BIT_MASK32(kIntervalBits)).GetAwaitingCleanLength(); |
| } |
| |
| private: |
| explicit VmPageOrMarker(uint32_t raw) : raw_(raw) {} |
| |
| // The low 3 bits of raw_ are reserved to represent the type, any other data has to fit into |
| // the remaining high bits. Note that there is no explicit Empty type, rather a PageType with a |
| // zero pointer is used to represent Empty. |
| static constexpr uint32_t kTypeBits = 3; |
| static constexpr uint32_t kPageType = 0b000; |
| static constexpr uint32_t kZeroMarkerType = 0b001; |
| static constexpr uint32_t kReferenceType = 0b010; |
| static constexpr uint32_t kIntervalType = 0b011; |
| static constexpr uint32_t kParentContentType = 0b100; |
| |
| static constexpr uint32_t kMarkerShareCountShift = kTypeBits; |
| |
| // Ensure the reference values have alignment such the type bits can be set without overlapping |
| // actual ref being stored. Unlike the page type, which does not allow the 0 value to be stored, a |
| // ref value of 0 is valid and may be stored. |
| static_assert(ReferenceValue::kAlignBits == kTypeBits); |
| |
| // In addition to storing the type for an interval, we also need to track the type of interval |
| // sentinel: the start, the end, or a single slot marker. |
| static constexpr int kIntervalSentinelBits = 2; |
| static_assert(static_cast<int>(SentinelType::NumSentinels) <= (1 << kIntervalSentinelBits)); |
| static constexpr int kIntervalSentinelShift = kTypeBits; |
| SentinelType GetIntervalSentinel() const { |
| return static_cast<SentinelType>( |
| (raw_ & (BIT_MASK(kIntervalSentinelBits) << kIntervalSentinelShift)) >> |
| kIntervalSentinelShift); |
| } |
| void SetIntervalSentinel(SentinelType sentinel) { |
| // Clear the old sentinel type. |
| raw_ &= ~(BIT_MASK32(kIntervalSentinelBits) << kIntervalSentinelShift); |
| // Set the new sentinel type. |
| raw_ |= static_cast<uint32_t>(sentinel) << kIntervalSentinelShift; |
| } |
| // Next we also need to store the type of interval being represented; reserve a couple of bits for |
| // this. Currently we only support one type of interval: a range of zero pages, but reserving 2 |
| // bits allows for more types in the future. |
| static constexpr uint64_t kIntervalTypeBits = 2; |
| static_assert(static_cast<uint32_t>(IntervalType::NumTypes) <= (1 << kIntervalTypeBits)); |
| static constexpr uint64_t kIntervalTypeShift = kIntervalSentinelShift + kIntervalSentinelBits; |
| IntervalType GetIntervalType() const { |
| return static_cast<IntervalType>((raw_ & (BIT_MASK(kIntervalTypeBits) << kIntervalTypeShift)) >> |
| kIntervalTypeShift); |
| } |
| static constexpr uint64_t kIntervalBits = kTypeBits + kIntervalSentinelBits + kIntervalTypeBits; |
| static_assert(ZeroRange::kAlignBits == kIntervalBits); |
| |
| // Only support creation of zero interval type for now. |
| // Private and only friended with VmPageList so that an external caller cannot arbitrarily create |
| // interval sentinels. |
| [[nodiscard]] static VmPageOrMarker ZeroInterval(SentinelType sentinel, |
| IntervalDirtyState state) { |
| uint32_t sentinel_bits = static_cast<uint32_t>(sentinel) << kIntervalSentinelShift; |
| uint32_t type_bits = static_cast<uint32_t>(IntervalType::Zero) << kIntervalTypeShift; |
| return VmPageOrMarker(ZeroRange(0, state).value() | type_bits | sentinel_bits | kIntervalType); |
| } |
| |
| // Change the interval sentinel type for an existing interval, while preserving the rest of the |
| // original state. Only valid to call on an existing interval type. The only permissible |
| // transitions are from Slot to Start/End and vice versa, as these are the only valid transitions |
| // when extending or clipping intervals. |
| // Private and only friended with VmPageList so that an external caller cannot arbitrarily |
| // manipulate interval sentinels. |
| void ChangeIntervalSentinel(SentinelType new_sentinel) { |
| #if ZX_DEBUG_ASSERT_IMPLEMENTED |
| DEBUG_ASSERT(IsInterval()); |
| auto old_sentinel = GetIntervalSentinel(); |
| DEBUG_ASSERT(old_sentinel != new_sentinel); |
| if (old_sentinel == SentinelType::Start || old_sentinel == SentinelType::End) { |
| DEBUG_ASSERT(new_sentinel == SentinelType::Slot); |
| } else { |
| DEBUG_ASSERT(old_sentinel == SentinelType::Slot); |
| DEBUG_ASSERT(new_sentinel == SentinelType::Start || new_sentinel == SentinelType::End); |
| } |
| #endif |
| SetIntervalSentinel(new_sentinel); |
| } |
| |
| uint32_t GetType() const { return raw_ & BIT_MASK(kTypeBits); } |
| |
| uint32_t Release() { |
| const uint32_t p = raw_; |
| raw_ = 0; |
| return p; |
| } |
| |
| uint32_t raw_; |
| |
| friend VmPageList; |
| }; |
| |
| // Limited reference to a VmPageOrMarker. This reference provides unrestricted const access to the |
| // underlying VmPageOrMarker, but as it holds a non-const VmPageOrMarker* it has the ability to |
| // modify the underlying entry. However, the interface for modification is very limited. |
| // |
| // This allows for the majority of VmPageList iterations that are not intended to allow for clearing |
| // entries to the Empty state to allow limited mutation (such as between different content states), |
| // without being completely mutable. |
| class VmPageOrMarkerRef { |
| public: |
| VmPageOrMarkerRef() = default; |
| explicit VmPageOrMarkerRef(VmPageOrMarker* page_or_marker) : page_or_marker_(page_or_marker) {} |
| ~VmPageOrMarkerRef() = default; |
| |
| const VmPageOrMarker& operator*() const { |
| DEBUG_ASSERT(page_or_marker_); |
| return *page_or_marker_; |
| } |
| |
| const VmPageOrMarker* operator->() const { |
| DEBUG_ASSERT(page_or_marker_); |
| return page_or_marker_; |
| } |
| |
| explicit operator bool() const { return !!page_or_marker_; } |
| |
| // Changing the kind of content is an allowed mutation and this takes ownership of the provided |
| // page and returns ownership of the previous reference. |
| [[nodiscard]] VmPageOrMarker::ReferenceValue SwapReferenceForPage(vm_page_t* p) { |
| DEBUG_ASSERT(page_or_marker_); |
| return page_or_marker_->SwapReferenceForPage(p); |
| } |
| // Similar to SwapReferenceForPage, but takes ownership of the ref and returns ownership of the |
| // previous page. |
| [[nodiscard]] vm_page_t* SwapPageForReference(VmPageOrMarker::ReferenceValue ref) { |
| DEBUG_ASSERT(page_or_marker_); |
| return page_or_marker_->SwapPageForReference(ref); |
| } |
| // Similar to SwapReferenceForPage, but changes one reference for another. |
| [[nodiscard]] VmPageOrMarker::ReferenceValue SwapReferenceForReference( |
| VmPageOrMarker::ReferenceValue ref) { |
| DEBUG_ASSERT(page_or_marker_); |
| return page_or_marker_->SwapReferenceForReference(ref); |
| } |
| |
| // Replaces the contents of this VmPageOrMarker with some non-empty contents, and returns what was |
| // previously present. The previous content is allowed to be empty, but the provided content must |
| // be non-empty. |
| [[nodiscard]] VmPageOrMarker SwapContent(VmPageOrMarker&& content) { |
| DEBUG_ASSERT(!content.IsEmpty()); |
| DEBUG_ASSERT(page_or_marker_); |
| return page_or_marker_->Swap(ktl::move(content)); |
| } |
| |
| // Forward dirty state updates as an allowed mutation. |
| void SetZeroIntervalAwaitingCleanLength(uint64_t len) { |
| DEBUG_ASSERT(page_or_marker_); |
| page_or_marker_->SetZeroIntervalAwaitingCleanLength(len); |
| } |
| |
| uint32_t GetMarkerShareCount() { |
| DEBUG_ASSERT(page_or_marker_); |
| return page_or_marker_->GetMarkerShareCount(); |
| } |
| |
| void IncrementMarkerShareCount() { |
| DEBUG_ASSERT(page_or_marker_); |
| page_or_marker_->IncrementMarkerShareCount(); |
| } |
| |
| void DecrementMarkerShareCount() { |
| DEBUG_ASSERT(page_or_marker_); |
| page_or_marker_->DecrementMarkerShareCount(); |
| } |
| |
| private: |
| VmPageOrMarker* page_or_marker_ = nullptr; |
| }; |
| |
| class VmPageListNode; |
| // Deletes VmPageListNode's allocated from VmPageListNode::Create. Must not be used with nodes |
| // created any other way. |
| struct VmPageListNodeDeleter { |
| void operator()(VmPageListNode*); |
| }; |
| |
| // VmPlnOwner wraps VmPageListNode in a unique_ptr that has single ownership and deletes it using |
| // VmPageListNodeDeleter when it goes out of scope. |
| using VmPlnOwner = ktl::unique_ptr<VmPageListNode, VmPageListNodeDeleter>; |
| class VmPageListNode final { |
| public: |
| VmPageListNode() = default; |
| ~VmPageListNode(); |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(VmPageListNode); |
| |
| static constexpr size_t kPageFanOut = 16; |
| |
| // Creates a new VmPageListNode. Must be deleted using the specified deleter. |
| static VmPlnOwner Create(); |
| |
| static uint64_t end_offset(uint64_t base_offset) { |
| DEBUG_ASSERT(NodeOffset(base_offset) == base_offset); |
| const uint64_t end = base_offset + (kPageFanOut * kPageSize); |
| // By construction the node cannot overflow, but the compiler does not know this. By explicitly |
| // telling it some checks can be avoided as the compiler does not have to consider the case |
| // where end wrapped. |
| if (end <= base_offset) { |
| __builtin_unreachable(); |
| } |
| return end; |
| } |
| |
| // for every page or marker in the node call the passed in function. |
| template <typename PTR_TYPE, typename F> |
| zx_status_t ForEveryPage(uint64_t base, F func) { |
| return ForEveryPageInRange<PTR_TYPE>(this, base, func, base, end_offset(base)); |
| } |
| |
| // for every page or marker in the node call the passed in function. |
| template <typename PTR_TYPE, typename F> |
| zx_status_t ForEveryPage(uint64_t base, F func) const { |
| return ForEveryPageInRange<PTR_TYPE>(this, base, func, base, end_offset(base)); |
| } |
| |
| // for every page or marker in the node in the range call the passed in function. The range is |
| // assumed to be within the nodes object range. |
| template <typename PTR_TYPE, typename F> |
| zx_status_t ForEveryPageInRange(uint64_t base, F func, uint64_t start_offset, |
| uint64_t end_offset) { |
| return ForEveryPageInRange<PTR_TYPE>(this, base, func, start_offset, end_offset); |
| } |
| |
| // for every page or marker in the node in the range call the passed in function. The range is |
| // assumed to be within the nodes object range. |
| template <typename PTR_TYPE, typename F> |
| zx_status_t ForEveryPageInRange(uint64_t base, F func, uint64_t start_offset, |
| uint64_t end_offset) const { |
| return ForEveryPageInRange<PTR_TYPE>(this, base, func, start_offset, end_offset); |
| } |
| |
| // Checks if the given offset is part of an interval involving this node. This method cannot find |
| // the full interval, since that may require looking at an additional node, but can determine if |
| // in an interval or not. Returns any interval sentinel found, otherwise a nullptr. |
| const VmPageOrMarker* IsOffsetInInterval(uint64_t obj_offset, uint64_t off) const { |
| DEBUG_ASSERT(off >= obj_offset); |
| DEBUG_ASSERT(off < end_offset(obj_offset)); |
| const size_t index = (off - obj_offset) / kPageSize; |
| // If the target slot is any kind of interval (start, end, individual slot), then we are in an |
| // interval. |
| if (!pages_[index].IsEmpty()) { |
| return (pages_[index].IsInterval()) ? &pages_[index] : nullptr; |
| } |
| // Check if there is an interval end to the right, which would cause this to be in an interval. |
| // Finding anything else indicates we cannot be in an interval. |
| for (size_t i = index + 1; i < kPageFanOut; i++) { |
| if (!pages_[i].IsEmpty()) { |
| return pages_[i].IsIntervalEnd() ? &pages_[i] : nullptr; |
| } |
| } |
| // Nothing to our right, so check for an interval start to our left. |
| for (size_t i = index; i > 0; i--) { |
| if (!pages_[i - 1].IsEmpty()) { |
| return pages_[i - 1].IsIntervalStart() ? &pages_[i - 1] : nullptr; |
| } |
| } |
| panic("Unexpected empty node"); |
| return nullptr; |
| } |
| |
| // Check if this node begins in an interval, that is if an interval start was in a preceding node |
| // and this nodes contains the end. If the first non-empty slot is an interval end it is returned, |
| // otherwise we cannot have started in an interval and a nullptr is returned. |
| const VmPageOrMarker* NodeStartsInInterval() const { |
| for (size_t i = 0; i < kPageFanOut; i++) { |
| if (!pages_[i].IsEmpty()) { |
| return pages_[i].IsIntervalEnd() ? &pages_[i] : nullptr; |
| } |
| } |
| panic("Unexpected empty node"); |
| return nullptr; |
| } |
| |
| const VmPageOrMarker& Lookup(size_t index) const { |
| DEBUG_ASSERT(index < kPageFanOut); |
| return pages_[index]; |
| } |
| |
| VmPageOrMarker& Lookup(size_t index) { |
| DEBUG_ASSERT(index < kPageFanOut); |
| return pages_[index]; |
| } |
| |
| // A node is empty if it contains no pages, page interval sentinels, references, or markers. |
| bool IsEmpty() const { |
| for (const auto& p : pages_) { |
| if (!p.IsEmpty()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Returns true if there are no pages or references owned by this node. Meant to check whether the |
| // node has any resource that needs to be returned. |
| bool HasNoPageOrRef() const { |
| for (const auto& p : pages_) { |
| if (p.IsPageOrRef()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Returns true if there are no pages, references or markers owned by this node. Meant to check |
| // whether the node has any resource that needs to be returned. |
| bool HasNoPageRefOrMarker() const { |
| for (const auto& p : pages_) { |
| if (p.IsPageOrRef() || p.IsMarker()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // Returns true if there are no interval sentinels owned by this node. |
| bool HasNoIntervalSentinel() const { |
| for (const auto& p : pages_) { |
| if (p.IsInterval()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <typename F> |
| void MergeRangeOnto(uint64_t base, uint64_t other_base, F migrate_fn, VmPageListNode& other, |
| uint64_t start_offset, uint64_t end_offset, uint64_t other_start_offset) { |
| DEBUG_ASSERT(other_start_offset >= other_base); |
| DEBUG_ASSERT(other_start_offset + (end_offset - start_offset) <= |
| VmPageListNode::end_offset(other_base)); |
| ForEveryPageInRange<VmPageOrMarker*>( |
| base, |
| [&](VmPageOrMarker* slot, uint64_t offset) { |
| const uint64_t other_offset = offset - start_offset + other_start_offset; |
| DEBUG_ASSERT(NodeOffset(other_offset) == other_base); |
| migrate_fn(slot, &other.pages_[NodeIndex(other_offset)], other_offset); |
| return ZX_ERR_NEXT; |
| }, |
| start_offset, end_offset); |
| } |
| |
| // Converts the supplied offset into a VmPageListNode base offset. |
| static uint64_t NodeOffset(uint64_t offset) { |
| return ROUNDDOWN(offset, kPageSize * VmPageListNode::kPageFanOut); |
| } |
| |
| // Converts the supplied offset into a VmPageListNode index. |
| static uint64_t NodeIndex(uint64_t offset) { |
| return (offset >> kPageShift) % VmPageListNode::kPageFanOut; |
| } |
| |
| private: |
| template <typename PTR_TYPE, typename S, typename F> |
| static zx_status_t ForEveryPageInRange(S self, uint64_t base, F func, uint64_t start_offset, |
| uint64_t end_offset) { |
| // Assert that the requested range is sensible and falls within our nodes actual offset range. |
| DEBUG_ASSERT(end_offset >= start_offset); |
| DEBUG_ASSERT(start_offset >= base); |
| DEBUG_ASSERT(end_offset <= VmPageListNode::end_offset(base)); |
| const size_t start = (start_offset - base) / kPageSize; |
| const size_t end = (end_offset - base) / kPageSize; |
| for (size_t i = start; i < end; i++) { |
| if (!self->pages_[i].IsEmpty()) { |
| zx_status_t status = func(PTR_TYPE{&self->pages_[i]}, base + i * kPageSize); |
| if (status != ZX_ERR_NEXT) { |
| return status; |
| } |
| } |
| } |
| return ZX_ERR_NEXT; |
| } |
| |
| VmPageOrMarker pages_[kPageFanOut]; |
| |
| friend VMPLCursor; |
| }; |
| |
| // Cursor that can be used for iterating over contiguous blocks of entries in a page list. The |
| // underlying page list must not have any entries removed while using this cursor, as the cursor |
| // retains iterators into the page list. It is, however, safe to insert new entries. |
| // The cursor can be used to iterate over empty contiguous slots, however iteration will always |
| // cease if entries are not contiguous. |
| class VMPLCursor : private btree::BTree<uint64_t, VmPlnOwner>::iterator { |
| public: |
| VMPLCursor() : index_(kPageFanOut) {} |
| |
| // See VMPLCursor::current. |
| VmPageOrMarkerRef current_ref() const { |
| if (valid()) { |
| auto [node_offset, node] = get(); |
| return VmPageOrMarkerRef(&node->pages_[index_]); |
| } |
| return VmPageOrMarkerRef(nullptr); |
| } |
| |
| // Retrieve the current VmPageOrMarker pointed at by the cursor. This will be a nullptr if the |
| // cursor is no longer valid. The slot pointed at may itself be empty. |
| // Note that it is up to the caller to know the offset, which it can track by remembering how |
| // many |step|s it has done. |
| const VmPageOrMarker* current() const { |
| if (valid()) { |
| auto [node_offset, node] = get(); |
| return &node->pages_[index_]; |
| } |
| return nullptr; |
| } |
| |
| // Move the cursor to the next entry. The next entry can then be retrieved by calling |current|, |
| // and if there is no next entry then current will return a nullptr. |
| void step() { |
| if (valid()) { |
| index_++; |
| if (index_ == kPageFanOut) { |
| inc_node(); |
| } |
| } |
| } |
| |
| // Calls the provided callback of type [](const VmPageOrMarker*)->zx_status_t on every entry as |
| // long as they are contiguous. This is equivalent a loop calling |step| and |current|, but can |
| // produce more optimal code gen with the internal loop. |
| // The callback can return ZX_ERR_NEXT to continue, ZX_ERR_STOP to cease iteration gracefully, or |
| // any other status to terminate with that status code. |
| template <typename F> |
| zx_status_t ForEveryContiguous(F func) { |
| while (valid()) { |
| auto [node_offset, node] = get(); |
| while (index_ < kPageFanOut) { |
| const VmPageOrMarker* slot = &node->pages_[index_]; |
| zx_status_t status = func(slot); |
| if (status != ZX_ERR_NEXT) { |
| return status == ZX_ERR_STOP ? ZX_OK : status; |
| } |
| index_++; |
| } |
| if (!inc_node()) { |
| return ZX_OK; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| // Returns the offset of the |current| position of the cursor. This is invalid to call if |
| // |current| is returning a nullptr. |
| uint64_t offset() const { |
| DEBUG_ASSERT(valid()); |
| auto [node_offset, node] = get(); |
| return node_offset + (static_cast<uint64_t>(index_) * kPageSize); |
| } |
| |
| private: |
| static constexpr size_t kPageFanOut = VmPageListNode::kPageFanOut; |
| |
| VMPLCursor(btree::BTree<uint64_t, VmPlnOwner>::iterator node, uint index) |
| : btree::BTree<uint64_t, VmPlnOwner>::iterator(node), index_(index) {} |
| |
| // Helper to increment the underlying node_, testing for contiguity. |
| bool inc_node() { |
| // Should only be incrementing if index is at the end, as otherwise we're not being contiguous. |
| DEBUG_ASSERT(index_ == kPageFanOut); |
| auto [prev_offset, _] = get(); |
| (*this)++; |
| if (IsValid()) { |
| auto [node_offset, node] = get(); |
| if (node_offset == prev_offset + kPageSize * kPageFanOut) { |
| // node is valid and contiguous, reset the index_ to both remove the terminal sentinel, and |
| // resume iteration from the beginning. |
| index_ = 0; |
| // TODO: Once cursor is in use benchmark the impact of validating that the node is not |
| // empty. |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Helper to check if the node is valid or not by checking index for its sentinel value. |
| bool valid() const { return index_ < kPageFanOut; } |
| |
| // The index into node_ that is currently being pointed at to be returned by |current|. The |
| // sentinel value of kPageFanOut is used to indicate that node_ is no longer valid. |
| uint index_; |
| |
| friend VmPageList; |
| }; |
| |
| class VmPageList final { |
| public: |
| VmPageList(); |
| ~VmPageList(); |
| |
| VmPageList& operator=(VmPageList&& other); |
| VmPageList(VmPageList&& other); |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(VmPageList); |
| |
| // walk the page tree, calling the passed in function on every tree node. |
| template <typename F> |
| zx_status_t ForEveryPage(F per_page_func) const { |
| return ForEveryPage<const VmPageOrMarker*>(this, per_page_func); |
| } |
| |
| // similar to ForEveryPage, but the per_page_func gets called with a VmPageOrMarkerRef instead of |
| // a const VmPageOrMarker*, allowing for limited mutation. |
| template <typename F> |
| zx_status_t ForEveryPageMutable(F per_page_func) { |
| return ForEveryPage<VmPageOrMarkerRef>(this, per_page_func); |
| } |
| |
| // walk the page tree, calling the passed in function on every tree node. |
| template <typename F> |
| zx_status_t ForEveryPageInRange(F per_page_func, uint64_t start_offset, |
| uint64_t end_offset) const { |
| return ForEveryPageInRange<const VmPageOrMarker*>(this, per_page_func, start_offset, |
| end_offset); |
| } |
| |
| // similar to ForEveryPageInRange but uses a valid VMPLCursor as the starting point. |
| template <typename F> |
| zx_status_t ForEveryPageInCursorRange(F per_page_func, VMPLCursor cursor, |
| uint64_t end_offset) const { |
| const uint64_t start_offset = cursor.offset(); |
| if (start_offset >= end_offset) { |
| return ZX_OK; |
| } |
| return ForEveryPageInRangeInternal<const VmPageOrMarker*, NodeCheck::Skip>( |
| this, per_page_func, *static_cast<btree::BTree<uint64_t, VmPlnOwner>::iterator*>(&cursor), |
| start_offset, end_offset); |
| } |
| |
| // similar to ForEveryPageInRange, but the per_page_func gets called with a VmPageOrMarkerRef |
| // instead of a const VmPageOrMarker*, allowing for limited mutation. |
| template <typename F> |
| zx_status_t ForEveryPageInRangeMutable(F per_page_func, uint64_t start_offset, |
| uint64_t end_offset) { |
| return ForEveryPageInRange<VmPageOrMarkerRef>(this, per_page_func, start_offset, end_offset); |
| } |
| |
| // walk the page tree, calling |per_page_func| on every page/marker and |per_gap_func| on every |
| // gap. |
| template <typename PAGE_FUNC, typename GAP_FUNC> |
| zx_status_t ForEveryPageAndGapInRange(PAGE_FUNC per_page_func, GAP_FUNC per_gap_func, |
| uint64_t start_offset, uint64_t end_offset) const { |
| return ForEveryPageAndGapInRange<const VmPageOrMarker*>(this, per_page_func, per_gap_func, |
| start_offset, end_offset); |
| } |
| template <typename PAGE_FUNC, typename GAP_FUNC> |
| zx_status_t ForEveryPageAndGapInRangeMutable(PAGE_FUNC per_page_func, GAP_FUNC per_gap_func, |
| uint64_t start_offset, uint64_t end_offset) { |
| return ForEveryPageAndGapInRange<VmPageOrMarkerRef>(this, per_page_func, per_gap_func, |
| start_offset, end_offset); |
| } |
| |
| // walk the page tree, calling |per_page_func| on every page/marker/interval that fulfills |
| // (returns true) the |compare_func|. Also call |contiguous_run_func| on every contiguous range of |
| // such pages/markers/intervals encountered, whose signature is: |
| // zx_status_t contiguous_run_func(uint64_t start, uint64_t end, bool is_interval) |
| // |
| // Intervals are treated as distinct contiguous runs, i.e. they won't be merged into a contiguous |
| // run of pages/markers for invocation of |contiguous_run_func|. For intervals, |
| // |contiguous_run_func| will be called with |is_interval| set to true; for other page types it |
| // will be false. Additionally, the entire interval should fulfill |compare_func| for |
| // |contiguous_run_func| to be called on the portion that falls in [start_offset, end_offset). |
| template <typename COMPARE_FUNC, typename PAGE_FUNC, typename CONTIGUOUS_RUN_FUNC> |
| zx_status_t ForEveryPageAndContiguousRunInRange(COMPARE_FUNC compare_func, |
| PAGE_FUNC per_page_func, |
| CONTIGUOUS_RUN_FUNC contiguous_run_func, |
| uint64_t start_offset, |
| uint64_t end_offset) const { |
| return ForEveryPageAndContiguousRunInRange<const VmPageOrMarker*>( |
| this, compare_func, per_page_func, contiguous_run_func, start_offset, end_offset); |
| } |
| |
| // Returns true if any pages (actual pages, references, or markers) are in the given range, or if |
| // the range forms a part of a sparse page interval. |
| bool AnyPagesOrIntervalsInRange(uint64_t start_offset, uint64_t end_offset) const { |
| bool found_page = false; |
| ForEveryPageInRange( |
| [&found_page](const VmPageOrMarker* page, uint64_t offset) { |
| found_page = true; |
| return ZX_ERR_STOP; |
| }, |
| start_offset, end_offset); |
| // It is possible that the range forms a part of an interval even if no nodes in the range have |
| // populated slots. We can determine that by checking to see if the start offset in the range |
| // falls in an interval (we could technically perform this check for any inclusive offset in the |
| // range since the range is entirely unpopulated and hence would only fall in the same interval |
| // if applicable). |
| return found_page ? true : IsOffsetInInterval(start_offset); |
| } |
| |
| // Similar to |AnyPagesOrIntervalsInRange| but skips over any ParentContent markers, as these do |
| // not represent content owned by this page list, but rather content owned by a parent. |
| bool AnyOwnedPagesOrIntervalsInRange(uint64_t start_offset, uint64_t end_offset) const { |
| bool found_page = false; |
| ForEveryPageInRange( |
| [&found_page](const VmPageOrMarker* page, uint64_t offset) { |
| if (page->IsParentContent()) { |
| return ZX_ERR_NEXT; |
| } |
| found_page = true; |
| return ZX_ERR_STOP; |
| }, |
| start_offset, end_offset); |
| // It is possible that the range forms a part of an interval even if no nodes in the range have |
| // populated slots. We can determine that by checking to see if the start offset in the range |
| // falls in an interval (we could technically perform this check for any inclusive offset in the |
| // range since the range is entirely unpopulated and hence would only fall in the same interval |
| // if applicable). |
| return found_page ? true : IsOffsetInInterval(start_offset); |
| } |
| |
| // Attempts to return a reference to the VmPageOrMarker at the specified offset. The returned |
| // pointer is valid until the VmPageList is destroyed or any of the Remove*/Take/Merge etc |
| // functions are called. |
| // |
| // Lookup may return 'nullptr' if there is no slot allocated for the given offset. If non-null |
| // is returned it may still be the case that IsEmpty() on the returned PageOrMarker is true. |
| const VmPageOrMarker* Lookup(uint64_t offset) const { |
| // lookup the tree node that holds this offset |
| if (auto pln = list_.find(NodeOffset(offset)); pln.IsValid()) { |
| auto [node_offset, node] = *pln; |
| return &node->Lookup(NodeIndex(offset)); |
| } |
| return nullptr; |
| } |
| |
| // Similar to `Lookup` but returns a VmPageOrMarkerRef that allows for limited mutation of the |
| // slot. General mutation requires calling `LookupOrAllocate`. |
| VmPageOrMarkerRef LookupMutable(uint64_t offset) { |
| // lookup the tree node that holds this offset |
| if (auto pln = list_.find(NodeOffset(offset)); pln.IsValid()) { |
| auto [node_offset, node] = *pln; |
| return VmPageOrMarkerRef(&node->Lookup(NodeIndex(offset))); |
| } |
| return VmPageOrMarkerRef(nullptr); |
| } |
| |
| // Similar to `LookupMutable` but returns a VMPLCursor that allows for iterating over any |
| // contiguous slots from the provided offset. |
| VMPLCursor LookupMutableCursor(uint64_t offset) { |
| // lookup the tree node that holds this offset |
| NodeList::iterator pln = list_.find(NodeOffset(offset)); |
| if (!pln.IsValid()) { |
| return VMPLCursor(); |
| } |
| return VMPLCursor(pln, static_cast<uint>(NodeIndex(offset))); |
| } |
| |
| // Similar to `LookupMutableCursor` but does a lower_bound search instead of a find, returning the |
| // first slot >= offset, if any exists. |
| VMPLCursor LookupNearestMutableCursor(uint64_t offset) { |
| // lookup the tree node that holds this offset or a larger one. |
| const uint64_t node_offset = NodeOffset(offset); |
| if (NodeList::iterator pln = list_.lower_bound(node_offset); pln.IsValid()) { |
| auto [found_offset, _] = *pln; |
| const uint64_t index = found_offset == node_offset ? NodeIndex(offset) : 0; |
| return VMPLCursor(pln, static_cast<uint>(index)); |
| } |
| return VMPLCursor(); |
| } |
| |
| // The interval handling flag to be used by LookupOrAllocate. See comments near LookupOrAllocate. |
| enum class IntervalHandling : uint8_t { |
| NoIntervals, |
| CheckForInterval, |
| SplitInterval, |
| }; |
| // Similar to `Lookup` but only returns `nullptr` if a slot cannot be allocated either due to out |
| // of memory, due to offset being invalid, or |interval_handling| not allowing for a slot to be |
| // safely returned. |
| // |
| // The returned slot, if not a `nullptr`, may generally be freely manipulated with the exception |
| // that if it started !Empty, then it is an error to set it to Empty. In this case the |
| // `RemovePage` method must be used. |
| // |
| // If the returned slot started Empty, as it not made !Empty, then the slot must be returned with |
| // ReturnEmptySlot, to ensure no empty nodes are retained. |
| // |
| // The bool in the ktl::pair returns whether the offset falls inside a sparse interval. And |
| // whether a valid VmPageOrMarker* is returned in the ktl::pair depends on the specified |
| // |interval_handling|. |
| // - NoIntervals: The page list does not contain any intervals, so there is no special handling |
| // to check for or split intervals. In other words, each slot in the page list can be manipulated |
| // independently. |
| // - CheckForIntervals: The page list can contain intervals, and the bool in the returned |
| // ktl::pair indicates whether the offset fell inside an interval. Note that this only checks for |
| // intervals but does not allow manipulating them, so a valid VmPageOrMarker* will be returned |
| // only if the offset can safely be manipulated independently. |
| // - SplitInterval: The page list can contain intervals and we are allowed to split intervals to |
| // return the required slot. The returned VmPageOrMarker* can be manipulated freely. (See |
| // comments near LookupOrAllocateCheckForInterval for an explanation of how splitting works.) |
| ktl::pair<VmPageOrMarker*, bool> LookupOrAllocate(uint64_t offset, |
| IntervalHandling interval_handling) { |
| switch (interval_handling) { |
| case IntervalHandling::NoIntervals: |
| // The page list does not expect any intervals. Short circuit any checks for intervals. |
| return {LookupOrAllocateInternal(offset), false}; |
| case IntervalHandling::CheckForInterval: |
| // Check for intervals but do not allow splitting them. |
| return LookupOrAllocateCheckForInterval(offset, false); |
| case IntervalHandling::SplitInterval: |
| // Check for intervals and also split them. |
| return LookupOrAllocateCheckForInterval(offset, true); |
| } |
| return {nullptr, false}; |
| } |
| |
| // Helper object for performing repeated LookupOrAllocate operations that are likely to be close |
| // to each other. While using this object other (modifying) methods on this VmPageList must not be |
| // performed, if they are the |reset| method needs to be used before continuing. |
| class BatchInserter { |
| public: |
| // Construct a BatchInserter for the specified VmPageList. The list must be kept alive for the |
| // duration of this object. |
| explicit BatchInserter(VmPageList& list) : list_(list.list_) {} |
| ~BatchInserter() = default; |
| |
| BatchInserter(const BatchInserter&) = delete; |
| BatchInserter& operator=(const BatchInserter&) = delete; |
| |
| // Similar to VmPageList::LookupOrAllocate but is implicitly NoIntervals. If repeated offsets |
| // are 'near' each other (in the same node, or in following nodes) then this will be more |
| // efficient than the VmPageList::LookupOrAllocate. However, regardless of the |offset| pattern |
| // this will always return correct results. |
| VmPageOrMarker* LookupOrAllocate(uint64_t offset); |
| |
| // Reset the batch inserter. This makes it safe to use again if other VmPageList operations had |
| // been performed. |
| void reset() { node_ = {}; } |
| |
| private: |
| btree::BTree<uint64_t, VmPlnOwner>& list_; |
| btree::BTree<uint64_t, VmPlnOwner>::iterator node_; |
| }; |
| |
| // Returns a slot that was empty after LookupOrAllocate, and that the caller did not end up |
| // filling. |
| // This ensures that if LookupOrAllocate allocated a new underlying list node, then that list node |
| // needs to be free'd otherwise it might not get cleaned up for the lifetime of the page list. |
| // |
| // This is only correct to call on an offset for which LookupOrAllocate had just returned a non |
| // null slot, and that slot was Empty and is still Empty. |
| void ReturnEmptySlot(uint64_t offset); |
| |
| // Removes any item at |offset| from the list and returns it, or VmPageOrMarker::Empty() if none. |
| VmPageOrMarker RemoveContent(uint64_t offset); |
| |
| // Release and call free_content_fn on every item in the page list. Gives free_content_fn |
| // ownership of the content. After calling this method, all slots in the page list are empty. |
| template <typename T> |
| void RemoveAllContent(T free_content_fn) { |
| // per page get a reference to the page pointer inside the page list node |
| auto per_page_func = [&free_content_fn](VmPageOrMarker* p, uint64_t offset) { |
| free_content_fn(ktl::move(*p)); |
| *p = VmPageOrMarker::Empty(); |
| return ZX_ERR_NEXT; |
| }; |
| |
| // walk the tree in order, freeing all the pages on every node |
| ForEveryPage<VmPageOrMarker*>(this, per_page_func); |
| |
| // empty the tree |
| Clear(); |
| } |
| |
| // Clears the tree of any remaining slots, leaving it in the initially allocated state. It is an |
| // error, and will trigger a panic, for any of the slots to hold pages or references, as clearing |
| // them would otherwise result in a memory leak. |
| void Clear() { list_.clear(); } |
| |
| // Calls the provided callback for every page or marker in the range [start_offset, end_offset). |
| // The callback can modify the VmPageOrMarker and take ownership of any pages, or leave them in |
| // place. The difference between this and ForEveryPage is as this allows for modifying the |
| // underlying pages any intermediate data structures can be checked and potentially freed if no |
| // longer needed. |
| template <typename T> |
| zx_status_t RemovePages(T per_page_fn, uint64_t start_offset, uint64_t end_offset) { |
| return ForEveryPageInRange<VmPageOrMarker*, NodeCheck::CleanupEmpty>(this, per_page_fn, |
| start_offset, end_offset); |
| } |
| |
| // Similar to RemovePages but also takes a |per_gap_fn| callback to allow for iterating over any |
| // gaps encountered as well. This can be used when the intent is to modify the underlying pages |
| // and/or gaps, while checking any intermediate data structures to potentially free ones that are |
| // no longer needed. |
| template <typename P, typename G> |
| zx_status_t RemovePagesAndIterateGaps(P per_page_fn, G per_gap_fn, uint64_t start_offset, |
| uint64_t end_offset) { |
| return ForEveryPageAndGapInRange<VmPageOrMarker*, NodeCheck::CleanupEmpty>( |
| this, per_page_fn, per_gap_fn, start_offset, end_offset); |
| } |
| |
| // Returns true if there are no pages, references, markers, or intervals in the page list. |
| bool IsEmpty() const { return list_.is_empty(); } |
| |
| // Returns true if the page list does not own any pages or references. Meant to check whether the |
| // page list has any resource that needs to be returned. |
| bool HasNoPageOrRef() const; |
| |
| // Similar to `HasNoPageOrRef` but returns false if there is a marker. |
| bool HasNoPageRefOrMarker() const; |
| |
| // Merges the pages in the specified range in |this| onto the |other| with |offset| in this |
| // mapping to the offset of 0 in |other|. |
| // |
| // For any offset in |this| that is not empty then the given |migrate_fn| is called with a |
| // reference to |this| and the corresponding slot in |other| and has the signature of: |
| // void migrate_fn(VmPageOrMarker* this_slot, VmPageOrMarker* other_slot, uint64_t other_offset); |
| // |
| // If this returns |true| then migrate_fn was called on everything in range of |offset| to |
| // |end_offset|. If false is returned then merging did not complete due to inability to allocates |
| // nodes in |other|. The partial merge is not rolled back and is up to the caller to deal with. |
| template <typename F> |
| bool MergeRangeOnto(F migrate_fn, VmPageList& other, uint64_t offset, uint64_t end_offset) { |
| // Iterate the range in this we are merging. |
| for (auto iter = list_.lower_bound(NodeOffset(offset)); iter && (*iter).first < end_offset;) { |
| auto [node_obj_offset, node] = *iter; |
| |
| DEBUG_ASSERT(node->HasNoIntervalSentinel()); |
| // Calculate start and end in |node| |
| uint64_t node_start_off = ktl::max(node_obj_offset, offset); |
| const uint64_t node_end_off = |
| ktl::min(VmPageListNode::end_offset(node_obj_offset), end_offset); |
| |
| // If offset is not a multiple of kNodeSize then items in this node will need to be split |
| // across two different nodes in other. |
| while (node_start_off < node_end_off) { |
| // Translate the start in |node| to an offset in |other| |
| const uint64_t other_start_off = node_start_off - offset; |
| |
| // Find the node in |other| that contains the start address. |
| const uint64_t other_obj_offset = NodeOffset(other_start_off); |
| auto cur_other = other.list_.lower_bound(other_obj_offset); |
| |
| // Allocate a new node if none was found. |
| if (!cur_other || (*cur_other).first != other_obj_offset) { |
| fbl::AllocChecker ac; |
| VmPlnOwner pl = VmPageListNode::Create(); |
| if (!pl) { |
| return false; |
| } |
| cur_other = other.list_.insert(cur_other, other_obj_offset, ktl::move(pl)); |
| if (!cur_other) { |
| return false; |
| } |
| } |
| VmPageListNode* cur_other_node = (*cur_other).second; |
| |
| // Cap the range to the end of |cur_other|, which could be less than length of the range in |
| // |node|. |
| const uint64_t other_end_off = |
| ktl::min(VmPageListNode::end_offset(other_obj_offset), node_end_off - offset); |
| |
| const uint64_t len = other_end_off - other_start_off; |
| |
| node->MergeRangeOnto(node_obj_offset, other_obj_offset, migrate_fn, *cur_other_node, |
| node_start_off, node_start_off + len, other_start_off); |
| |
| // If either nothing was transferred, or all existing content was removed, then remove |
| // |cur_other|. |
| if (cur_other_node->IsEmpty()) { |
| other.list_.erase(cur_other); |
| } |
| |
| node_start_off += len; |
| } |
| // If all the content was moved out of |node| then remove it. |node| could still have content |
| // if |migrate_fn| chose to leave it, or if the range being processed only partially covered |
| // |node|. |
| if (node->IsEmpty()) { |
| iter = list_.erase(iter); |
| } else { |
| iter++; |
| } |
| } |
| return true; |
| } |
| |
| uint64_t HeapAllocationBytes() const { |
| auto utilization = list_.calculate_utilization_slow(); |
| return utilization.nodes_in_bytes() + (utilization.stored_values * sizeof(VmPageListNode)); |
| } |
| |
| // Allow the implementation to use a one-past-the-end for VmPageListNode offsets. |
| static constexpr uint64_t MAX_SIZE = |
| ROUNDDOWN(UINT64_MAX, VmPageListNode::kPageFanOut* kPageSize); |
| |
| // Add a sparse zero interval spanning the range [start_offset, end_offset) with the specified |
| // dirty_state. The specified range must be previously unpopulated. This will try to merge the new |
| // zero interval with existing intervals to the left and/or right, if the dirty_state allows it. |
| zx_status_t AddZeroInterval(uint64_t start_offset, uint64_t end_offset, |
| VmPageOrMarker::IntervalDirtyState dirty_state) { |
| return AddZeroIntervalInternal(start_offset, end_offset, dirty_state, 0); |
| } |
| |
| // Populates individual interval slots in the range [start_offset, end_offset) that falls inside a |
| // sparse interval. The intent of this function is to allow the caller to prepare the range for |
| // overwriting (replacing with pages) by populating the required slots upfront, so that slot |
| // lookup does not fail after this call. Essentially simulates interval splits |
| // (LookupOrAllocateCheckForInterval) for every offset in the specified range, but does so more |
| // efficiently, instead of having to search the tree repeatedly for every single offset. |
| zx_status_t PopulateSlotsInInterval(uint64_t start_offset, uint64_t end_offset); |
| |
| // Helper to return an unused interval slot so that it can be merged back into the interval it was |
| // populated/split from. |
| void ReturnIntervalSlot(uint64_t offset); |
| |
| // Clips an interval from the start by len, i.e. moves the start from interval_start to |
| // interval_start + len. The total length of the interval must be larger than len. |
| zx_status_t ClipIntervalStart(uint64_t interval_start, uint64_t len); |
| |
| // Clips an interval from the end by len, i.e. moves the end from interval_end to |
| // interval_end - len. The total length of the interval must be larger than len. |
| zx_status_t ClipIntervalEnd(uint64_t interval_end, uint64_t len); |
| |
| // Returns true if the specified offset falls in a sparse zero interval. |
| bool IsOffsetInZeroInterval(uint64_t offset) const; |
| |
| // Replace an existing page at offset with a zero interval, and return the released page. The |
| // caller takes ownership of the released page and is responsible for freeing it. |
| vm_page_t* ReplacePageWithZeroInterval(uint64_t offset, |
| VmPageOrMarker::IntervalDirtyState dirty_state); |
| |
| // Overwrite a zero interval either fully or partially with a new zero interval, breaking off the |
| // old interval into two if required. old_start_offset and old_end_offset specify the start and |
| // end sentinels of the old interval that is being overwritten; either one of these or both can be |
| // specified, with the other set to UINT64_MAX. The new zero interval that overwrites the old |
| // spans [new_start_offset, new_end_offset] with its state set to new_dirty_state. |
| // - For full overwrites, both old_start_offset and old_end_offset must be provided, and should |
| // be equal to new_start_offset and new_end_offset respectively. At the end of the call, the old |
| // interval will have been completely replaced by the new one. |
| // - For partial overwrites from the start, old_start_offset must be provided and be equal to |
| // new_start_offset, and old_end_offset must be UINT64_MAX. At the end of this call, the start of |
| // the old interval will have been overwritten by the new interval, with the remainder of the old |
| // interval now starting at |new_end_offset + kPageSize|. |
| // - For partial overwrites from the end, old_end_offset must be provided and be equal to |
| // new_end_offset, and old_start_offset must be UINT64_MAX. At the end of this call, the end of |
| // the old interval will have been overwritten by the new interval, with the remainder of the old |
| // interval now ending at |new_start_offset - kPageSize|. |
| // - Partial overwrites in the middle are not allowed. In other words, either old_start_offset |
| // must be the same as new_start_offset, or old_end_offset must be the same as new_end_offset, or |
| // both. |
| zx_status_t OverwriteZeroInterval(uint64_t old_start_offset, uint64_t old_end_offset, |
| uint64_t new_start_offset, uint64_t new_end_offset, |
| VmPageOrMarker::IntervalDirtyState new_dirty_state); |
| |
| private: |
| static uint64_t NodeOffset(uint64_t offset) { return VmPageListNode::NodeOffset(offset); } |
| |
| static uint64_t NodeIndex(uint64_t offset) { return VmPageListNode::NodeIndex(offset); } |
| |
| // Returns true if the specified offset falls in a sparse page interval. |
| bool IsOffsetInInterval(uint64_t offset) const; |
| |
| // Internal helper used when checking whether the offset falls in an interval. |
| // lower_bound is the node that was queried with a lower_bound() lookup on the list using the |
| // offset. This node is passed in here so that we can reuse the node the callsite has looked up |
| // and avoid an extra lookup. Any interval sentinel found is returned, otherwise a nullptr is |
| // returned. |
| template <typename T> |
| static const VmPageOrMarker* IsOffsetInIntervalHelper( |
| uint64_t offset, btree::BTree<uint64_t, VmPlnOwner>::iterator_impl<T> lower_bound) { |
| auto [obj_offset, node] = *lower_bound; |
| if (offset < obj_offset) { |
| return node->NodeStartsInInterval(); |
| } |
| return node->IsOffsetInInterval(obj_offset, offset); |
| } |
| |
| // Internal helper for AddZeroInterval. |
| // |replace_existing_slot| can optionally be set to true if a zero interval spanning a single page |
| // is being added, and the slot at that offset is already populated (but Empty) and can be reused. |
| zx_status_t AddZeroIntervalInternal(uint64_t start_offset, uint64_t end_offset, |
| VmPageOrMarker::IntervalDirtyState dirty_state, |
| uint64_t awaiting_clean_len, |
| bool replace_existing_slot = false); |
| |
| // Internal helper for LookupOrAllocate. |
| VmPageOrMarker* LookupOrAllocateInternal(uint64_t offset); |
| |
| // Similar to LookupOrAllocateInternal but also checks if offset falls in a sparse page interval, |
| // returning true via the bool in ktl::pair if it does, along with the slot. Also splits the |
| // interval around offset if split_interval is set to true. This allows the caller to freely |
| // manipulate the slot at offset similar to LookupOrAllocate. If offset is found in an interval, |
| // but split_interval was false, no VmPageOrMarker* is returned, as it is not safe to manipulate |
| // any slot in an interval without also splitting the interval around it. |
| // |
| // In other words, the return values fall into three categories. |
| // 1. {page, false} : offset does not lie in an interval. |slot| is the required slot. |
| // 2. {page, true} : offset lies in an interval and split_interval was true. |page| is the |
| // required slot. The interval has been correctly split around the slot, so |page| can be treated |
| // similar to any non-interval type. |
| // 3. {nullptr, true} : offset lies in an interval but split_interval was false. No slot is |
| // returned. |
| // |
| // Splitting the interval would look as follows. If the interval previously was: |
| // [start, end) where start < offset < end. |
| // After the split we would have three intervals: |
| // [start, offset) [offset, offset + kPageSize) [offset + kPageSize, end) |
| // The middle interval containing offset spans only a single page, i.e. offset is an |
| // IntervalSentinel::Slot, which can now be manipulated independently. |
| ktl::pair<VmPageOrMarker*, bool> LookupOrAllocateCheckForInterval(uint64_t offset, |
| bool split_interval); |
| |
| template <typename PTR_TYPE, typename S, typename F> |
| static zx_status_t ForEveryPage(S self, F per_page_func) { |
| for (auto [node_offset, node] : self->list_) { |
| zx_status_t status = node->template ForEveryPage<PTR_TYPE>(node_offset, per_page_func); |
| if (status != ZX_ERR_NEXT) { |
| if (status == ZX_ERR_STOP) { |
| break; |
| } |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| // Calls the provided callback for every page in the given range. If the CleanupNodes template |
| // argument is true then it is assumed the per_page_func may remove pages and page nodes will be |
| // checked to see if they are empty and can be cleaned up. |
| enum class NodeCheck : bool { |
| Skip = false, |
| CleanupEmpty = true, |
| }; |
| template <typename PTR_TYPE, NodeCheck NODE_CHECK, typename S, typename F> |
| static zx_status_t ForEveryPageInRangeInternal(S self, F per_page_func, auto cur, |
| uint64_t start_offset, uint64_t end_offset) { |
| DEBUG_ASSERT(IsPageRounded(start_offset)); |
| DEBUG_ASSERT(IsPageRounded(end_offset)); |
| |
| while (cur) { |
| auto [cur_offset, node] = *cur; |
| if (cur_offset >= end_offset) { |
| break; |
| } |
| uint64_t start = ktl::max(start_offset, cur_offset); |
| uint64_t end = ktl::min(VmPageListNode::end_offset(cur_offset), end_offset); |
| zx_status_t status = |
| node->template ForEveryPageInRange<PTR_TYPE, F>(cur_offset, per_page_func, start, end); |
| if constexpr (NODE_CHECK == NodeCheck::CleanupEmpty) { |
| if (node->IsEmpty()) { |
| cur = self->list_.erase(cur); |
| } else { |
| cur++; |
| } |
| } else { |
| cur++; |
| } |
| if (status != ZX_ERR_NEXT) { |
| return status; |
| } |
| } |
| |
| return ZX_ERR_NEXT; |
| } |
| template <typename PTR_TYPE, NodeCheck NODE_CHECK = NodeCheck::Skip, typename S, typename F> |
| static zx_status_t ForEveryPageInRange(S self, F per_page_func, uint64_t start_offset, |
| uint64_t end_offset) { |
| // Find the first node (if any) that will contain our starting offset. |
| auto cur = self->list_.lower_bound(self->NodeOffset(start_offset)); |
| zx_status_t status = ForEveryPageInRangeInternal<PTR_TYPE, NODE_CHECK>( |
| self, per_page_func, ktl::move(cur), start_offset, end_offset); |
| if (status != ZX_ERR_NEXT) { |
| if (status == ZX_ERR_STOP) { |
| return ZX_OK; |
| } |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| template <typename PTR_TYPE, NodeCheck NODE_CHECK = NodeCheck::Skip, typename S, |
| typename PAGE_FUNC, typename GAP_FUNC> |
| static zx_status_t ForEveryPageAndGapInRange(S self, PAGE_FUNC per_page_func, |
| GAP_FUNC per_gap_func, uint64_t start_offset, |
| uint64_t end_offset) { |
| auto cur = self->list_.lower_bound(self->NodeOffset(start_offset)); |
| |
| uint64_t expected_next_off = start_offset; |
| // Set to true when we encounter an interval start but haven't yet encountered the end. |
| bool in_interval = false; |
| auto per_page_wrapper_fn = [&](auto p, uint64_t off) { |
| // Update our interval tracking first. Should the callbacks later request an early exit then |
| // this work is wasted, but doing it first, and unconditionally, lets the compiler perform |
| // better common expression elimination with the per_gap_func check next. |
| if (p->IsIntervalStart()) { |
| // We should not already have been tracking an interval. |
| DEBUG_ASSERT(!in_interval); |
| // Start and end sentinel interval types should match. Since we only support zero |
| // intervals currently, we can simply check for that. |
| DEBUG_ASSERT(p->IsIntervalZero()); |
| in_interval = true; |
| } else if (p->IsIntervalEnd()) { |
| // If this is not the first populated slot we encountered, we should have been tracking a |
| // valid interval. |
| DEBUG_ASSERT(in_interval || expected_next_off == start_offset); |
| // Start and end sentinel interval types should match. Since we only support zero |
| // intervals currently, we can simply check for that. |
| DEBUG_ASSERT(p->IsIntervalZero()); |
| // Reset interval tracking. |
| in_interval = false; |
| } |
| zx_status_t status = ZX_ERR_NEXT; |
| // We can move ahead of expected_next_off in the case of an interval too, which represents a |
| // run of pages. Make sure this is not an interval before calling the per_gap_func. |
| if (expected_next_off != off && !p->IsIntervalEnd()) { |
| status = per_gap_func(expected_next_off, off); |
| } |
| expected_next_off = off + kPageSize; |
| if (status == ZX_ERR_NEXT) { |
| status = per_page_func(p, off); |
| } |
| return status; |
| }; |
| |
| zx_status_t status = ForEveryPageInRangeInternal<PTR_TYPE, NODE_CHECK>( |
| self, per_page_wrapper_fn, cur, start_offset, end_offset); |
| if (status != ZX_ERR_NEXT) { |
| if (status == ZX_ERR_STOP) { |
| return ZX_OK; |
| } |
| return status; |
| } |
| |
| // Handle the last gap after checking that we are not in an interval. Note that simply checking |
| // for in_interval is not sufficient, as it is possible to have started the traversal partway |
| // into an interval, in which case we would not have seen the interval start and in_interval |
| // would be false. So we perform a quick check for in_interval first and if that fails perform |
| // the more expensive IsOffsetInInterval() check. The IsOffsetInInterval() call is further gated |
| // by whether we encountered any page at all in the traversal above. If we saw at least one |
| // page in the traversal, we know that we could not be in an interval without in_interval being |
| // true because we would have seen the interval start. |
| if (expected_next_off != end_offset) { |
| // Traversal ended in an interval if in_interval was true, OR if the traversal did not see any |
| // page at all and the start_offset is in an interval (Note that in this latter case all |
| // offsets in the range [start_offset, end_offset) would lie in the same interval, so we can |
| // just check one of them). |
| bool ended_in_interval = in_interval || (expected_next_off == start_offset && cur && |
| !!IsOffsetInIntervalHelper(start_offset, cur)); |
| if (!ended_in_interval) { |
| status = per_gap_func(expected_next_off, end_offset); |
| if (status != ZX_ERR_NEXT && status != ZX_ERR_STOP) { |
| return status; |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Internal helpers to return the start (or end) of an interval given the end (or start) along |
| // with the corresponding offset. |
| ktl::pair<const VmPageOrMarker*, uint64_t> FindIntervalStartForEnd(uint64_t end_offset) const; |
| ktl::pair<const VmPageOrMarker*, uint64_t> FindIntervalEndForStart(uint64_t start_offset) const; |
| |
| template <typename PTR_TYPE, typename S, typename COMPARE_FUNC, typename PAGE_FUNC, |
| typename CONTIGUOUS_RUN_FUNC> |
| static zx_status_t ForEveryPageAndContiguousRunInRange(S self, COMPARE_FUNC compare_func, |
| PAGE_FUNC per_page_func, |
| CONTIGUOUS_RUN_FUNC contiguous_run_func, |
| uint64_t start_offset, |
| uint64_t end_offset) { |
| if (start_offset == end_offset) { |
| return ZX_OK; |
| } |
| |
| // Track contiguous range of pages fulfilling compare_func. |
| uint64_t contiguous_run_start = start_offset; |
| uint64_t contiguous_run_len = 0; |
| |
| // Tracks whether we enter the ForEveryPageAndGap traversal at all. |
| bool found_page_or_gap = false; |
| // Tracks information if we encounter an interval start, to be used when we encounter the |
| // corresponding end. |
| struct { |
| uint64_t interval_start_offset; |
| bool start_compare_status; |
| bool started_interval; |
| } interval_tracker = {.started_interval = false}; |
| |
| zx_status_t status = ForEveryPageAndGapInRange<PTR_TYPE>( |
| self, |
| [&](auto* p, uint64_t off) { |
| found_page_or_gap = true; |
| zx_status_t st = ZX_ERR_NEXT; |
| const bool compare_result = compare_func(p, off); |
| |
| // Handle interval types first. |
| if (p->IsInterval()) { |
| // If we are going to start an interval, end any contiguous run being tracked, and call |
| // contiguous_run_func on it. This is because intervals are treated as contiguous ranges |
| // distinct from pages or markers. Do this before per_page_func because we don't want to |
| // have processed extra pages if contiguous_run_func on the range prior would have |
| // failed. Also do this irrespective of whether this interval passes the compare_func or |
| // not, since we are processing pages prior to the interval. |
| if (p->IsIntervalStart() || p->IsIntervalSlot()) { |
| if (contiguous_run_len > 0) { |
| st = contiguous_run_func(contiguous_run_start, |
| contiguous_run_start + contiguous_run_len, |
| /*is_interval=*/false); |
| // Reset contiguous range tracking. |
| contiguous_run_len = 0; |
| if (st != ZX_ERR_NEXT) { |
| return st; |
| } |
| } |
| } |
| DEBUG_ASSERT(contiguous_run_len == 0); |
| |
| // Run the per-page function on the interval sentinel first. Then proceed to the more |
| // complicated logic for the contiguous function. |
| if (compare_result) { |
| st = per_page_func(p, off); |
| if (st != ZX_ERR_NEXT && st != ZX_ERR_STOP) { |
| return st; |
| } |
| } |
| |
| // A slot is a contiguous run of a single page. |
| if (p->IsIntervalSlot()) { |
| // We should not have been already tracking an interval. |
| DEBUG_ASSERT(!interval_tracker.started_interval); |
| if (compare_result) { |
| return contiguous_run_func(off, off + kPageSize, /*is_interval=*/true); |
| } |
| return ZX_ERR_NEXT; |
| } |
| |
| if (p->IsIntervalStart()) { |
| // Start tracking a new run. We should not have been already tracking an interval. |
| DEBUG_ASSERT(!interval_tracker.started_interval); |
| interval_tracker.started_interval = true; |
| interval_tracker.interval_start_offset = off; |
| // Stash the comparison result for the interval start. |
| interval_tracker.start_compare_status = compare_result; |
| return ZX_ERR_NEXT; |
| } |
| |
| DEBUG_ASSERT(p->IsIntervalEnd()); |
| // If the interval end does not pass the check, there is nothing more to be done. |
| if (!compare_result) { |
| interval_tracker.started_interval = false; |
| return ZX_ERR_NEXT; |
| } |
| |
| // If this is the end of an interval, call contiguous_run_func on the interval if the |
| // compare_func passes for *both* the start and the end, and proceed. |
| // It is possible that we don't have the interval start if we started the traversal |
| // partway inside an interval. Find the start and evaluate compare_func on it. |
| if (!interval_tracker.started_interval) { |
| auto [start, interval_start_offset] = self->FindIntervalStartForEnd(off); |
| DEBUG_ASSERT(start); |
| DEBUG_ASSERT(start->IsIntervalStart()); |
| DEBUG_ASSERT(interval_start_offset < start_offset); |
| interval_tracker.started_interval = true; |
| interval_tracker.start_compare_status = compare_func(start, interval_start_offset); |
| // Pretend that the interval begins at start_offset since we're not considering the |
| // range before it. |
| interval_tracker.interval_start_offset = start_offset; |
| } |
| DEBUG_ASSERT(interval_tracker.started_interval); |
| interval_tracker.started_interval = false; |
| if (interval_tracker.start_compare_status) { |
| return contiguous_run_func(interval_tracker.interval_start_offset, off + kPageSize, |
| /*is_interval=*/true); |
| } |
| return ZX_ERR_NEXT; |
| } |
| |
| // Handle any non-interval types. |
| DEBUG_ASSERT(!p->IsInterval()); |
| DEBUG_ASSERT(!interval_tracker.started_interval); |
| |
| if (compare_result) { |
| st = per_page_func(p, off); |
| // Return any errors early before considering this page for contiguous_run_func. |
| if (st != ZX_ERR_NEXT && st != ZX_ERR_STOP) { |
| // If there was an outstanding contiguous run, process it since it had to have ended |
| // before the failing offset. |
| if (contiguous_run_len > 0) { |
| zx_status_t prev_range_status = contiguous_run_func( |
| contiguous_run_start, contiguous_run_start + contiguous_run_len, |
| /*is_interval=*/false); |
| contiguous_run_len = 0; |
| // If there was an error encountered, surface that instead of st, as it occurred on |
| // a range prior to this offset. |
| if (prev_range_status != ZX_ERR_NEXT && prev_range_status != ZX_ERR_STOP) { |
| return prev_range_status; |
| } |
| } |
| return st; |
| } |
| |
| // Start tracking a contiguous run if none was being tracked. |
| if (contiguous_run_len == 0) { |
| contiguous_run_start = off; |
| } |
| // Append this page to the contiguous range being tracked. |
| contiguous_run_len += kPageSize; |
| // In the case that st is ZX_ERR_STOP, we will include this page in the contiguous run |
| // and stop traversal *after* this page. |
| return st; |
| } |
| // We were already tracking a contiguous range when we encountered this page that does not |
| // fulfill compare_func. Invoke contiguous_run_func on the range so far and start tracking |
| // a new one skipping over this page. |
| if (contiguous_run_len > 0) { |
| st = |
| contiguous_run_func(contiguous_run_start, contiguous_run_start + contiguous_run_len, |
| /*is_interval=*/false); |
| // Reset contiguous_run_len to zero to track a new range later if required. |
| // Do this irrespective of the return status to ensure we don't erroneously have a |
| // remaining range to process below after exiting the traversal. |
| contiguous_run_len = 0; |
| } |
| return st; |
| }, |
| [&](uint64_t start, uint64_t end) { |
| found_page_or_gap = true; |
| // We should not encounter any gaps in the midst of an interval we were tracking. |
| DEBUG_ASSERT(!interval_tracker.started_interval); |
| zx_status_t st = ZX_ERR_NEXT; |
| // We were already tracking a contiguous range when we encountered this gap. Invoke |
| // contiguous_run_func on the range so far and start tracking a new one skipping over this |
| // gap. |
| if (contiguous_run_len > 0) { |
| st = |
| contiguous_run_func(contiguous_run_start, contiguous_run_start + contiguous_run_len, |
| /*is_interval=*/false); |
| // Reset contiguous_run_len to zero to track a new range later if required. |
| // Do this irrespective of the return status to ensure we don't erroneously have a |
| // remaining range to process below after exiting the traversal. |
| contiguous_run_len = 0; |
| } |
| return st; |
| }, |
| start_offset, end_offset); |
| |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // If we did not execute either the per-page or per-gap function, we could only have been inside |
| // an interval. In that case, we need to find both the start and the end of this interval and |
| // evaluate compare_func on them. |
| if (!found_page_or_gap) { |
| DEBUG_ASSERT(self->IsOffsetInInterval(start_offset)); |
| DEBUG_ASSERT(self->IsOffsetInInterval(end_offset - kPageSize)); |
| |
| uint64_t interval_end_offset = UINT64_MAX; |
| bool end_compare_status = false; |
| status = ForEveryPageInRange<PTR_TYPE>( |
| self, |
| [&](auto* p, uint64_t off) { |
| // The first populated slot should be an interval end. |
| DEBUG_ASSERT(p->IsIntervalEnd()); |
| interval_end_offset = off; |
| end_compare_status = compare_func(p, off); |
| return ZX_ERR_STOP; |
| }, |
| end_offset, VmPageList::MAX_SIZE); |
| DEBUG_ASSERT(status == ZX_OK); |
| |
| if (end_compare_status) { |
| auto [start, interval_start_offset] = self->FindIntervalStartForEnd(interval_end_offset); |
| DEBUG_ASSERT(start); |
| DEBUG_ASSERT(start->IsIntervalStart()); |
| DEBUG_ASSERT(interval_start_offset < start_offset); |
| if (compare_func(start, interval_start_offset)) { |
| status = contiguous_run_func(start_offset, end_offset, /*is_interval=*/true); |
| if (status != ZX_ERR_NEXT && status != ZX_ERR_STOP) { |
| return status; |
| } |
| } |
| } |
| return ZX_OK; |
| } |
| |
| // Process the last contiguous range if there is one, or an interval that we started tracking |
| // but did not end. |
| if (contiguous_run_len > 0) { |
| status = contiguous_run_func(contiguous_run_start, contiguous_run_start + contiguous_run_len, |
| /*is_interval=*/false); |
| if (status != ZX_ERR_NEXT && status != ZX_ERR_STOP) { |
| return status; |
| } |
| } else if (interval_tracker.started_interval && interval_tracker.start_compare_status) { |
| auto [end, interval_end_offset] = |
| self->FindIntervalEndForStart(interval_tracker.interval_start_offset); |
| DEBUG_ASSERT(end); |
| DEBUG_ASSERT(end->IsIntervalEnd()); |
| if (compare_func(end, interval_end_offset)) { |
| status = contiguous_run_func(interval_tracker.interval_start_offset, end_offset, |
| /*is_interval=*/true); |
| if (status != ZX_ERR_NEXT && status != ZX_ERR_STOP) { |
| return status; |
| } |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| using NodeList = btree::BTree<uint64_t, VmPlnOwner>; |
| NodeList list_; |
| }; |
| |
| // Class which holds the list of vm_page structs removed from a VmPageList |
| // by AddPagesFrom. The list include information about uncommitted pages and markers. |
| // Every splice list is expected to go through the following series of states: |
| // 1. The splice list is created. |
| // 2. List is Initialized with the desired range. |
| // 3. Pages are added to the splice list. |
| // 4. The list is `Finalize`d, meaning that it can no longer be modified by `Append`. |
| // 5. Pages are then `Pop`d from the list. Once all the pages are popped, the list is considered |
| // "processed". |
| // 6. The list is then considered `Processed` and can be destroyed. |
| class VmPageSpliceList final { |
| public: |
| VmPageSpliceList() = default; |
| // Convenience constructor that calls Initialize. |
| explicit VmPageSpliceList(uint64_t length) { Initialize(length); } |
| ~VmPageSpliceList(); |
| |
| // For use by PhysicalPageProvider. The user-pager path doesn't use this. This returns a |
| // finalized list. |
| static zx_status_t CreateFromPageList(uint64_t length, VmPageDoublyLinkedList* pages, |
| VmPageSpliceList* splice); |
| |
| // Initialize the list with the given range. Can only be done once, and must be done before |
| // providing pages. |
| void Initialize(uint64_t length) { |
| DEBUG_ASSERT(IsEmpty() && state_ == State::Constructed); |
| length_ = length; |
| state_ = State::Initialized; |
| } |
| |
| // Pops the next page off of the splice list. It is invalid to pop a page from a non-finalized |
| // splice list. |
| VmPageOrMarker Pop(); |
| |
| // Takes the content out of |source| and places them in this splice list, which must have already |
| // been initialized but still be empty. The range to be taken is determined by the provided |
| // |offset| and the length of this splice list. May return ZX_ERR_OUT_OF_MEMORY, in which case an |
| // unspecified number of pages will have been moved into this splice list. |
| // |
| // Takes a function with the signature void(VmPageOrMarker *src, VmPageOrMarker *dst, uint64_t |
| // offset), which is expected to move content from |src| to |dst|. |
| template <typename F> |
| zx_status_t AddPagesFrom(F merge_func, VmPageList& source, uint64_t offset) { |
| const uint64_t end = offset + length_; |
| bool result = source.MergeRangeOnto(merge_func, page_list_, offset, end); |
| Finalize(); |
| return result ? ZX_OK : ZX_ERR_NO_MEMORY; |
| } |
| |
| // Iterates all pages in this splice list without taking ownership. Pages are not considered |
| // processed and can not be removed using this iterator. The callback is expected to have the |
| // signature: zx_status_t page_func(VmPageOrMarkerRef slot, uint64_t offset); |
| // The iterator will start at `start_offset` and iterate until the end of the VmPageSpliceList. |
| // start_offset must be paged aligned and less than length_. |
| template <typename PAGE_FUNC> |
| zx_status_t MutatePages(PAGE_FUNC page_func, uint64_t start_offset) { |
| DEBUG_ASSERT(IsFinalized()); |
| DEBUG_ASSERT(start_offset < length_); |
| DEBUG_ASSERT(IsPageRounded(start_offset)); |
| zx_status_t status = page_list_.ForEveryPageInRangeMutable( |
| [&](VmPageOrMarkerRef slot, uint64_t offset) { return page_func(slot, offset); }, |
| start_offset, length_); |
| |
| return status; |
| } |
| |
| // Iterates all the pages and gaps in this splice list. The page_func is given ownership of each |
| // page. It is invalid to process a non-finalized splice list. The two callbacks are expected to |
| // have the signature: |
| // zx_status_t page_func(VmPageOrMarker slot, uint64_t splice_offset); |
| // zx_status_t gap_func(uint64_t gap_start, uint64_t gap_end); |
| // Due to page_func always taking ownership of the content if it returns any kind of error, either |
| // a graceful request to stop iteration via ZX_ERR_STOP or an explicit error, then that offset is |
| // considered processed and will not be repeated in future calls. In contrast, if iteration is |
| // stopped during gap_func, that gap will not be considered processed and can be returned in |
| // future calls. |
| template <typename PAGE_FUNC, typename GAP_FUNC> |
| zx_status_t RemovePagesAndIterateGaps(PAGE_FUNC page_func, GAP_FUNC gap_func) { |
| DEBUG_ASSERT(IsFinalized()); |
| // Assume we will successfully process the whole range. This will get trimmed should any of the |
| // callbacks terminate early. |
| uint64_t processed = length_; |
| zx_status_t status = page_list_.RemovePagesAndIterateGaps( |
| [&](VmPageOrMarker* slot, uint64_t src_offset) { |
| // Move the content out of slot before passing to the page_func to ensure it *must* deal |
| // with it and it cannot be left in slot. |
| VmPageOrMarker content = ktl::move(*slot); |
| zx_status_t status = page_func(ktl::move(content), src_offset); |
| if (status != ZX_ERR_NEXT) { |
| processed = src_offset + kPageSize; |
| } |
| return status; |
| }, |
| [&](uint64_t gap_start, uint64_t gap_end) { |
| zx_status_t status = gap_func(gap_start, gap_end); |
| if (status != ZX_ERR_NEXT) { |
| processed = gap_start; |
| } |
| return status; |
| }, |
| pos_, length_); |
| pos_ = processed; |
| if (pos_ == length_) { |
| DEBUG_ASSERT(page_list_.IsEmpty()); |
| state_ = State::Processed; |
| } |
| return status; |
| } |
| |
| // Inserts the |content| into the splice list at the specified |offset|. |
| // The splice list takes ownership of `content` after this call. |
| // It is invalid to append to a finalized splice list. |
| zx_status_t Insert(uint64_t offset, VmPageOrMarker content); |
| |
| // Returns true after the whole collection has been processed by Pop. |
| bool IsProcessed() const { return state_ == State::Processed; } |
| |
| // Returns true if the collection has been Initialized. |
| bool IsInitialized() const { return state_ == State::Initialized; } |
| |
| // Returns true if this list is empty. |
| bool IsEmpty() const { return page_list_.IsEmpty(); } |
| |
| // Marks the list as finalized. |
| // See the comment at `VmPageSpliceList`'s declaration for more info on what this means and when |
| // to call it. Note that it is invalid to call `Finalize` twice on the same list. |
| void Finalize() { |
| DEBUG_ASSERT(IsInitialized()); |
| state_ = State::Finalized; |
| } |
| |
| // Returns true if the splice list is finalized. |
| // See the comment at `VmPageSpliceList`'s declaration for more info on what this means. |
| bool IsFinalized() const { return state_ == State::Finalized; } |
| |
| // Returns the current position in the list. |
| uint64_t Position() const { return pos_; } |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(VmPageSpliceList); |
| |
| private: |
| void FreeAllPages(); |
| |
| uint64_t length_ = 0; |
| uint64_t pos_ = 0; |
| |
| // States used to represent the life cycle of the VmPageSpliceList, as per the comment at the |
| // declaration. |
| enum class State : uint8_t { |
| Constructed, |
| Initialized, |
| Finalized, |
| Processed, |
| }; |
| State state_ = State::Constructed; |
| |
| VmPageList page_list_; |
| |
| friend VmPageList; |
| }; |
| |
| #endif // ZIRCON_KERNEL_VM_INCLUDE_VM_VM_PAGE_LIST_H_ |