blob: 95993fdb7945ec840d63250ac0fa28556b14e8b1 [file] [edit]
// 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_OBJECT_H_
#define ZIRCON_KERNEL_VM_INCLUDE_VM_VM_OBJECT_H_
#include <align.h>
#include <lib/fit/function.h>
#include <lib/page/size.h>
#include <lib/user_copy/user_iovec.h>
#include <lib/user_copy/user_ptr.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <stdint.h>
#include <zircon/syscalls-next.h>
#include <zircon/types.h>
#include <arch/aspace.h>
#include <fbl/array.h>
#include <fbl/canary.h>
#include <fbl/enum_bits.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/intrusive_single_list.h>
#include <fbl/macros.h>
#include <fbl/name.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_counted_upgradeable.h>
#include <fbl/ref_ptr.h>
#include <kernel/lockdep.h>
#include <kernel/mutex.h>
#include <ktl/utility.h>
#include <vm/attribution.h>
#include <vm/page.h>
#include <vm/stream_size_manager.h>
#include <vm/vm.h>
#include <vm/vm_mapping_observer.h>
#include <vm/vm_page_list.h>
class VmMapping;
class MultiPageRequest;
class VmObjectPaged;
class VmObjectPhysical;
class VmAspace;
class VmObject;
class VmHierarchyBase;
class VmObjectChildObserver {
public:
// Called anytime a VMO has zero children. This call is synchronized with
// |VmObject::SetChildObserver|, but is not otherwise synchronized with other VMO operations
// such as creating additional children. As such it is the users responsibility to synchronize
// with child creation.
virtual void OnZeroChild() = 0;
};
// Typesafe enum for resizability arguments.
enum class Resizability {
Resizable,
NonResizable,
};
// Argument which specifies the required snapshot semantics for the clone.
enum class SnapshotType {
// All pages must appear as if a snapshot is performed at the moment of the clone.
Full,
// Only pages already modified in the hierarchy need to appear as if a snapshot is performed at
// the moment of the clone.
Modified,
// No pages need to be initially snapshot, but they must have a snapshot taken if written.
OnWrite,
};
// Argument that specifies the context in which we are supplying pages.
enum class SupplyOptions : uint8_t {
PagerSupply,
TransferData,
PhysicalPageProvider,
};
namespace internal {
struct ChildListTag {};
struct GlobalListTag {};
} // namespace internal
// Base for opting an object into a deferred deletion strategy that allows for object chains to be
// deleted without causing unbounded recursion due to dropping refptrs in destructors.
template <typename T>
class VmDeferredDeleter {
public:
// Calls MaybeDeadTransition and then drops the refptr to the given object by either placing it on
// the deferred delete list for another thread already running deferred delete to drop, or drops
// itself.
// This can be used to avoid unbounded recursion when dropping chained refptrs, as found in
// vmo parent_ refs.
static void DoDeferredDelete(fbl::RefPtr<T> object) {
Guard<CriticalMutex> guard{DeferredDeleteLock::Get()};
// If a parent has multiple children then it's possible for a given object to already be
// queued for deletion.
if (!object->deferred_delete_state_.InContainer()) {
delete_list_.push_front(ktl::move(object));
} else {
// We know a refptr is being held by the container (which we are holding the lock to), so can
// safely drop the vmo ref.
object.reset();
}
if (!running_delete_) {
running_delete_ = true;
while (!delete_list_.is_empty()) {
guard.CallUnlocked([ptr = delete_list_.pop_front()]() mutable {
ptr->MaybeDeadTransition();
ptr.reset();
});
}
running_delete_ = false;
}
}
private:
struct ListTraits {
static fbl::SinglyLinkedListNodeState<fbl::RefPtr<T>>& node_state(VmDeferredDeleter<T>& node) {
return node.deferred_delete_state_;
}
};
// Mutex that protects the global delete list. As this class is templated we actually end up with
// a single mutex and a global list for each unique object type.
DECLARE_SINGLETON_CRITICAL_MUTEX(DeferredDeleteLock);
static fbl::SinglyLinkedListCustomTraits<fbl::RefPtr<T>, ListTraits> delete_list_
TA_GUARDED(DeferredDeleteLock::Get());
static bool running_delete_ TA_GUARDED(DeferredDeleteLock::Get());
using DeferredDeleteState = fbl::SinglyLinkedListNodeState<fbl::RefPtr<T>>;
DeferredDeleteState deferred_delete_state_ TA_GUARDED(DeferredDeleteLock::Get());
};
template <typename T>
bool VmDeferredDeleter<T>::running_delete_ = false;
template <typename T>
fbl::SinglyLinkedListCustomTraits<fbl::RefPtr<T>, typename VmDeferredDeleter<T>::ListTraits>
VmDeferredDeleter<T>::delete_list_;
// Cursor to allow for walking global vmo lists without needing to hold the lock protecting them all
// the time. This can be required to enforce order of acquisition with another lock (as in the case
// of |discardable_reclaim_candidates_|), or it can be desirable for performance reasons (as in the
// case of |all_vmos_|).
// In practice at most one cursor is expected to exist, but as the cursor list is global the
// overhead of being generic to support multiple cursors is negligible.
//
// |ObjType| is the type of object being tracked in the list (VmObject, VmCowPages etc).
// |LockType| is the singleton global lock used to protect the list.
// |ListType| is the type of the global vmo list.
// |ListIteratorType| is the iterator for |ListType|.
template <typename ObjType, typename LockType, typename ListType, typename ListIteratorType>
class VmoCursor
: public fbl::DoublyLinkedListable<VmoCursor<ObjType, LockType, ListType, ListIteratorType>*> {
public:
VmoCursor() = delete;
using CursorsList =
fbl::DoublyLinkedList<VmoCursor<ObjType, LockType, ListType, ListIteratorType>*>;
// Constructor takes as arguments the global lock, the global vmo list, and the global list of
// cursors to add the newly created cursor to. Should be called while holding the global |lock|.
VmoCursor(LockType* lock, ListType& vmos, CursorsList& cursors)
: lock_(*lock), vmos_list_(vmos), cursors_list_(cursors) {
AssertHeld(lock_);
if (!vmos_list_.is_empty()) {
vmos_iter_ = vmos_list_.begin();
} else {
vmos_iter_ = vmos_list_.end();
}
cursors_list_.push_front(this);
}
// Destructor removes this cursor from the global list of all cursors.
~VmoCursor() TA_REQ(lock_) { cursors_list_.erase(*this); }
// Advance the cursor and return the next element or nullptr if at the end of the list.
//
// Once |Next| has returned nullptr, all subsequent calls will return nullptr.
//
// The caller must hold the global |lock_|.
ObjType* Next() TA_REQ(lock_) {
if (vmos_iter_ == vmos_list_.end()) {
return nullptr;
}
ObjType* result = &*vmos_iter_;
vmos_iter_++;
return result;
}
// If the next element is |h|, advance the cursor past it.
//
// The caller must hold the global |lock_|.
void AdvanceIf(const ObjType* h) TA_REQ(lock_) {
if (vmos_iter_ != vmos_list_.end()) {
if (&*vmos_iter_ == h) {
vmos_iter_++;
}
}
}
// Advances all the cursors in |cursors_list|, calling |AdvanceIf(h)| on each cursor.
//
// The caller must hold the global lock protecting the |cursors_list|.
static void AdvanceCursors(CursorsList& cursors_list, const ObjType* h) {
for (auto& cursor : cursors_list) {
AssertHeld(cursor.lock_ref());
cursor.AdvanceIf(h);
}
}
LockType& lock_ref() TA_RET_CAP(lock_) { return lock_; }
private:
VmoCursor(const VmoCursor&) = delete;
VmoCursor& operator=(const VmoCursor&) = delete;
VmoCursor(VmoCursor&&) = delete;
VmoCursor& operator=(VmoCursor&&) = delete;
LockType& lock_;
ListType& vmos_list_ TA_GUARDED(lock_);
CursorsList& cursors_list_ TA_GUARDED(lock_);
ListIteratorType vmos_iter_ TA_GUARDED(lock_);
};
enum class VmObjectReadWriteOptions : uint8_t {
None = 0,
// If set, attempts to read past the end of a VMO will not cause a failure and only copy the
// existing bytes instead (i.e. the requested length will be trimmed to the actual VMO size).
TrimLength = (1 << 0),
};
FBL_ENABLE_ENUM_BITS(VmObjectReadWriteOptions)
extern "C" void cpp_vm_object_free(VmObject* vmo);
// The base vm object that holds a range of bytes of data
//
// Can be created without mapping and used as a container of data, or mappable
// into an address space via VmAddressRegion::CreateVmMapping
class VmObject : public fbl::ContainableBaseClasses<
fbl::TaggedDoublyLinkedListable<VmObject*, internal::ChildListTag>,
fbl::TaggedDoublyLinkedListable<VmObject*, internal::GlobalListTag>>,
public fbl::RefCountedUpgradeable<VmObject> {
public:
// public API
virtual zx_status_t Resize(uint64_t size) { return ZX_ERR_NOT_SUPPORTED; }
virtual Lock<CriticalMutex>* lock() const = 0;
virtual Lock<CriticalMutex>& lock_ref() const = 0;
virtual uint64_t size_locked() const TA_REQ(lock()) = 0;
uint64_t size() const TA_EXCL(lock()) {
Guard<CriticalMutex> guard{lock()};
return size_locked();
}
virtual uint32_t create_options() const { return 0; }
// Returns true if the object is backed by RAM and this object can be cast to a VmObjectPaged, if
// false this is a VmObjectPhysical.
bool is_paged() const { return options_ & kPaged; }
// Returns true if the object is backed by a contiguous range of physical
// memory.
virtual bool is_contiguous() const { return false; }
// Returns true if the object size can be changed.
virtual bool is_resizable() const { return false; }
// Returns true if the object's pages are discardable by the kernel.
virtual bool is_discardable() const { return false; }
// Returns true if the VMO was created via CreatePagerVmo().
virtual bool is_user_pager_backed() const { return false; }
// Returns true if the VMO's pages require dirty bit tracking.
virtual bool is_dirty_tracked() const { return false; }
// Returns true if streams are supported on the VMO.
virtual bool is_stream_compatible() const { return false; }
// Marks the VMO as modified if the VMO tracks modified state (only supported for pager-backed
// VMOs).
virtual void mark_modified_locked() TA_REQ(lock()) {}
using AttributionCounts = struct vm::AttributionCounts;
// Returns the number of physical bytes currently attributed to a range of this VMO.
// The range is `[offset_bytes, offset_bytes+len_bytes)`.
virtual AttributionCounts GetAttributedMemoryInRange(uint64_t offset_bytes,
uint64_t len_bytes) const {
return AttributionCounts{};
}
// Returns the number of physical bytes currently attributed to this VMO's parent when this VMO
// is a reference.
virtual AttributionCounts GetAttributedMemoryInReferenceOwner() const {
return AttributionCounts{};
}
// Returns the number of physical bytes currently attributed to this VMO.
AttributionCounts GetAttributedMemory() const { return GetAttributedMemoryInRange(0, size()); }
// find physical pages to back the range of the object
// May block on user pager requests and must be called without locks held.
virtual zx_status_t CommitRange(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
// Fetches content in the given range of the object. This should operate logically equivalent to
// reading such that future reads are quicker.
// May block on user pager requests and must be called without locks held.
virtual zx_status_t PrefetchRange(uint64_t offset, uint64_t len) = 0;
// find physical pages to back the range of the object and pin them.
// |len| must be non-zero. |write| indicates whether the range is being pinned for a write or a
// read.
// May block on user pager requests and must be called without locks held.
virtual zx_status_t CommitRangePinned(uint64_t offset, uint64_t len, bool write) = 0;
// free a range of the vmo back to the default state
virtual zx_status_t DecommitRange(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
// Zero a range of the VMO. May release physical pages in the process.
// May block on user pager requests and must be called without locks held.
virtual zx_status_t ZeroRange(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
// Zero a range of the VMO and also untrack it from any kind of dirty tracking. For committed
// pages, this means that they are released. And any kind of zero markers or intervals that are
// inserted will not subscribe to dirty tracking.
virtual zx_status_t ZeroRangeUntracked(uint64_t offset, uint64_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
// Unpin the given range of the vmo. This asserts if it tries to unpin a
// page that is already not pinned (do not expose this function to
// usermode).
virtual void Unpin(uint64_t offset, uint64_t len) = 0;
// Checks if all pages in the provided range are pinned.
// This is only intended to be used for debugging checks.
virtual bool DebugIsRangePinned(uint64_t offset, uint64_t len) = 0;
// Lock a range from being discarded by the kernel. Can fail if the range was already discarded.
virtual zx_status_t TryLockRange(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
// Lock a range from being discarded by the kernel. Guaranteed to succeed. |lock_state_out| is
// populated with relevant information about the locked and discarded ranges.
virtual zx_status_t LockRange(uint64_t offset, uint64_t len,
zx_vmo_lock_state_t* lock_state_out) {
return ZX_ERR_NOT_SUPPORTED;
}
// Unlock a range, making it available for the kernel to discard. The range could have been locked
// either by |TryLockRange| or |LockRange|.
virtual zx_status_t UnlockRange(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
// read/write operators against kernel pointers only
// May block on user pager requests and must be called without locks held.
virtual zx_status_t Read(void* ptr, uint64_t offset, size_t len) { return ZX_ERR_NOT_SUPPORTED; }
virtual zx_status_t Write(const void* ptr, uint64_t offset, size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
// execute lookup_fn on a given range of physical addresses within the vmo. Only pages that are
// present and writable in this VMO will be enumerated. Any copy-on-write pages in our parent
// will not be enumerated. The physical addresses given to the lookup_fn should not be retained in
// any way unless the range has also been pinned by the caller. Offsets provided will be in
// relation to the object being queried, even if pages are actually from a parent object where
// this is a slice.
// Ranges of length zero are considered invalid and will return ZX_ERR_INVALID_ARGS. The lookup_fn
// can terminate iteration early by returning ZX_ERR_STOP.
using LookupFunction =
fit::inline_function<zx_status_t(uint64_t offset, paddr_t pa), 4 * sizeof(void*)>;
virtual zx_status_t Lookup(uint64_t offset, uint64_t len, LookupFunction lookup_fn) {
return ZX_ERR_NOT_SUPPORTED;
}
// Attempts to lookup the given range in the VMO. If it exists and is physically contiguous
// returns the paddr of the start of the range. The offset must be page aligned.
// Ranges of length zero are considered invalid and will return ZX_ERR_INVALID_ARGS.
// A null |paddr| may be passed to just check for contiguity.
virtual zx_status_t LookupContiguous(uint64_t offset, uint64_t len, paddr_t* out_paddr) {
return ZX_ERR_NOT_SUPPORTED;
}
// read/write operators against user space pointers only
//
// The number of bytes successfully processed is always returned, even upon error. This allows for
// callers to still pass on this bytes transferred if a particular error was expected.
//
// May block on user pager requests and must be called without locks held.
//
// Bytes are guaranteed to be transferred in order from low to high offset.
virtual ktl::pair<zx_status_t, size_t> ReadUser(user_out_ptr<char> ptr, uint64_t offset,
size_t len, VmObjectReadWriteOptions options) {
return {ZX_ERR_NOT_SUPPORTED, 0};
}
// |OnWriteBytesTransferredCallback| is guaranteed to be called after bytes have been successfully
// transferred from the user source to the VMO and will be called before the VMO lock is dropped.
// As a result, operations performed within the callback should not take any other locks or be
// long-running.
using OnWriteBytesTransferredCallback = fit::inline_function<void(uint64_t offset, size_t len)>;
virtual ktl::pair<zx_status_t, size_t> WriteUser(
user_in_ptr<const char> ptr, uint64_t offset, size_t len, VmObjectReadWriteOptions options,
const OnWriteBytesTransferredCallback& on_bytes_transferred) {
return {ZX_ERR_NOT_SUPPORTED, 0};
}
// Removes the pages from this vmo in the range [offset, offset + len) and returns
// them in pages. This vmo must be a paged vmo with no parent, and it cannot have any
// pinned pages in the source range. |offset| and |len| must be page aligned.
virtual zx_status_t TakePages(uint64_t offset, uint64_t len, VmPageSpliceList* pages) {
return ZX_ERR_NOT_SUPPORTED;
}
// Supplies this vmo with pages for the range [offset, offset + len). If this vmo
// already has pages in the target range, the |options| field will dictate what happens:
// If options is SupplyOptions::TransferData, the pages in the target range will be overwritten,
// Otherwise, the corresponding pages in |pages| will be freed.
// |offset| and |len| must be page aligned.
virtual zx_status_t SupplyPages(uint64_t offset, uint64_t len, VmPageSpliceList* pages,
SupplyOptions options) {
return ZX_ERR_NOT_SUPPORTED;
}
// Indicates that page requests in the range [offset, offset + len) could not be fulfilled.
// |error_status| specifies the error encountered. |offset| and |len| must be page aligned.
virtual zx_status_t FailPageRequests(uint64_t offset, uint64_t len, zx_status_t error_status) {
return ZX_ERR_NOT_SUPPORTED;
}
// Dirties pages in the vmo in the range [offset, offset + len).
virtual zx_status_t DirtyPages(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
using DirtyRangeEnumerateFunction = fit::inline_function<zx_status_t(
uint64_t range_offset, uint64_t range_len, bool range_is_zero)>;
// Enumerates dirty ranges in the range [offset, offset + len) in ascending order, updating any
// relevant VMO internal state required to perform the enumeration, and calls |dirty_range_fn| on
// each dirty range (spanning [range_offset, range_offset + range_len) where |range_is_zero|
// indicates whether the range is all zeros). |dirty_range_fn| can return ZX_ERR_NEXT to continue
// with the enumeration, ZX_ERR_STOP to terminate the enumeration successfully, and any other
// error code to terminate the enumeration early with that error code.
virtual zx_status_t EnumerateDirtyRanges(uint64_t offset, uint64_t len,
DirtyRangeEnumerateFunction&& dirty_range_fn) {
return ZX_ERR_NOT_SUPPORTED;
}
// Query pager relevant VMO stats, e.g. whether the VMO has been modified. If |reset| is set to
// true, the queried stats are reset as well, potentially affecting the queried state returned by
// future calls to this function.
virtual zx_status_t QueryPagerVmoStats(bool reset, zx_pager_vmo_stats_t* stats) {
return ZX_ERR_NOT_SUPPORTED;
}
// Indicates start of writeback for the range [offset, offset + len). Any Dirty pages in the range
// are transitioned to AwaitingClean, in preparation for transition to Clean when the writeback is
// done (See VmCowPages::DirtyState for details of these states). |offset| and |len| must be page
// aligned. |is_zero_range| specifies whether the caller intends to write back the specified range
// as zeros.
virtual zx_status_t WritebackBegin(uint64_t offset, uint64_t len, bool is_zero_range) {
return ZX_ERR_NOT_SUPPORTED;
}
// Indicates end of writeback for the range [offset, offset + len). Any AwaitingClean pages in the
// range are transitioned to Clean (See VmCowPages::DirtyState for details of these states).
// |offset| and |len| must be page aligned.
virtual zx_status_t WritebackEnd(uint64_t offset, uint64_t len) { return ZX_ERR_NOT_SUPPORTED; }
enum EvictionHint {
DontNeed,
AlwaysNeed,
};
// Hint how the specified range is intended to be used, so that the hint can be taken into
// consideration when reclaiming pages under memory pressure (if applicable).
// May block on user pager requests and must be called without locks held.
virtual zx_status_t HintRange(uint64_t offset, uint64_t len, EvictionHint hint) {
// Hinting trivially succeeds for unsupported VMO types.
return ZX_OK;
}
// Performs any page commits necessary for a VMO with high memory priority over the given range.
// This method is always safe to call as it will internally check the memory priority status and
// skip if necessary, so the caller does not need to worry about races with the VMO no longer
// being high priority.
// As this may need to acquire the lock even to check the memory priority, if the caller knows
// they have not caused this VMO to become high priority (i.e. they have not called
// ChangeHighPriorityCountLocked with a positive value), then calling this should be skipped for
// performance.
// This method has no return value as it is entirely best effort and no part of its operation is
// needed for correctness.
virtual void CommitHighPriorityPages(uint64_t offset, uint64_t len) TA_EXCL(lock()) {
// This does nothing by default.
}
// Provides the VMO with a user defined queryable byte aligned size. This provided size can then
// be referenced in other operations, but otherwise has no effect. The VMO will never read or act
// on this value unless instructed by user operations, and it is therefore the responsibility of
// the user to ensure any synchronization of the reported value with the operation being
// requested.
virtual void SetUserStreamSize(fbl::RefPtr<StreamSizeManager> ssm) = 0;
// The associated VmObjectDispatcher will set an observer to notify user mode.
void SetChildObserver(VmObjectChildObserver* child_observer);
bool is_child_observer_valid() const {
Guard<Mutex> guard{ChildObserverLock::Get()};
return child_observer_ != nullptr;
}
// Returns a null-terminated name, or the empty string if set_name() has not
// been called.
void get_name(char* out_name, size_t len) const TA_EXCL(lock());
void get_name_locked(char* out_name, size_t len) const TA_REQ(lock());
// Sets the name of the object. May truncate internally. |len| is the size
// of the buffer pointed to by |name|.
zx_status_t set_name(const char* name, size_t len) TA_EXCL(lock());
// Returns a user ID associated with this VMO, or zero.
// Used to hold a zircon koid for Dispatcher-wrapped VMOs.
uint64_t user_id() const;
// Returns the parent's user_id() if this VMO has a parent,
// otherwise returns zero.
virtual uint64_t parent_user_id() const = 0;
// Sets the value returned by |user_id()|. May only be called once.
void set_user_id(uint64_t user_id);
// Returns the maximum possible size of a VMO.
static size_t max_size() { return MAX_SIZE; }
virtual void Dump(uint depth, bool verbose) = 0;
// Returns the number of lookup steps that might be done by operations on this VMO. This would
// typically be the depth of a parent chain and represent how many parents might need to be
// traversed to find a page.
// What this returns is imprecise and not well defined, and so is for debug / diagnostic usage
// only.
virtual uint32_t DebugLookupDepth() const { return 0; }
// perform a cache maintenance operation against the vmo.
enum class CacheOpType { Invalidate, Clean, CleanInvalidate, Sync };
virtual zx_status_t CacheOp(uint64_t offset, uint64_t len, CacheOpType type) {
return ZX_ERR_NOT_SUPPORTED;
}
arch_mmu_flags_t GetMappingCachePolicyLocked() const TA_REQ(lock()) { return cache_policy_; }
arch_mmu_flags_t GetMappingCachePolicy() const {
Guard<CriticalMutex> guard{lock()};
return GetMappingCachePolicyLocked();
}
virtual zx_status_t SetMappingCachePolicy(arch_mmu_flags_t cache_policy) = 0;
// create a copy-on-write clone vmo at the page-aligned offset and length
// note: it's okay to start or extend past the size of the parent
virtual zx_status_t CreateClone(Resizability resizable, SnapshotType type, uint64_t offset,
uint64_t size, bool copy_name, fbl::RefPtr<VmObject>* child_vmo) {
return ZX_ERR_NOT_SUPPORTED;
}
virtual zx_status_t CreateChildSlice(uint64_t offset, uint64_t size, bool copy_name,
fbl::RefPtr<VmObject>* child_vmo) {
return ZX_ERR_NOT_SUPPORTED;
}
// TODO: use a zx::result return instead of multiple out parameters and be consistent with the
// other Create* methods.
virtual zx_status_t CreateChildReference(Resizability resizable, uint64_t offset, uint64_t size,
bool copy_name, bool* first_child,
fbl::RefPtr<VmObject>* child_vmo) {
return ZX_ERR_NOT_SUPPORTED;
}
// Extend this enum when new child types are supported with zx_vmo_create_child().
// All SNAPSHOT* types are reported as kCowClone, because they all implement CoW semantics, albeit
// in different ways to provide different guarantees.
enum ChildType { kNotChild, kCowClone, kSlice, kReference };
virtual ChildType child_type() const = 0;
virtual uint64_t HeapAllocationBytes() const { return 0; }
// Number of times pages have been evicted over the lifetime of this VMO. Evicted counts for any
// decommit style event such as user pager eviction or zero page merging. One eviction event
// could count for multiple pages being evicted, if those pages were evicted as a group.
virtual uint64_t ReclamationEventCount() const { return 0; }
// Get a pointer to the page structure and/or physical address at the specified offset.
// valid flags are VMM_PF_FLAG_*.
//
// |page_request| must be non-null if any flags in VMM_PF_FLAG_FAULT_MASK are set, unless
// the caller knows that the vm object is not paged.
//
// Returns ZX_ERR_SHOULD_WAIT if the caller should try again after waiting on the
// PageRequest.
//
// Returns ZX_ERR_NEXT if |page_request| supports batching and the current request
// can be batched. The caller should continue to make successive GetPage requests
// until this returns ZX_ERR_SHOULD_WAIT. If the caller runs out of requests, it
// should finalize the request with PageSource::FinalizeRequest.
virtual zx_status_t GetPage(uint64_t offset, uint pf_flags, list_node* alloc_list,
MultiPageRequest* page_request, vm_page_t** page, paddr_t* pa) = 0;
// Helper variant of GetPage that will retry the operation after waiting on a PageRequest if
// required.
// Must not be called with any locks held.
zx_status_t GetPageBlocking(uint64_t offset, uint pf_flags, list_node* alloc_list,
vm_page_t** page, paddr_t* pa);
// Attempt to add a new mapping to the mapping list of this object. Can fail with
// ZX_ERR_OUT_OF_MEMORY.
zx_status_t AddMappingLocked(VmMapping* r) TA_REQ(lock());
void RemoveMappingLocked(VmMapping* r) TA_REQ(lock());
uint32_t num_mappings() const;
uint32_t num_mappings_locked() const TA_REQ(lock()) { return mapping_list_len_; }
// Returns true if this VMO is mapped into any VmAspace whose is_user()
// returns true.
bool IsMappedByUser() const;
// Returns an estimate of the number of unique VmAspaces that this object
// is mapped into.
uint32_t share_count() const;
// Adds a child to this VMO and returns true if the dispatcher which matches
// user_id should be notified about the first child being added.
bool AddChildLocked(VmObject* child) TA_REQ(ChildListLock::Get());
bool AddChild(VmObject* child) TA_EXCL(ChildListLock::Get());
// Removes the child |child| from this VMO and notifies the child observer if the new child count
// is zero. The |guard| must be this VMO's lock.
void RemoveChild(VmObject* child, Guard<CriticalMutex>::Adoptable adopt);
// Drops |c| from the child list without going through the full removal
// process. ::RemoveChild is probably what you want here.
void DropChildLocked(VmObject* c) TA_REQ(ChildListLock::Get());
uint32_t num_children() const;
// Helper to round to the VMO size multiple (which is kPageSize) without overflowing.
static zx_status_t RoundSize(uint64_t size, uint64_t* out_size) {
*out_size = RoundUpPageSize(size);
if (*out_size < size) {
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
}
// Calls the provided |func(const VmObject&)| on every VMO in the system,
// from oldest to newest. Stops if |func| returns an error, returning the
// error value.
template <typename T>
static zx_status_t ForEach(T func) {
Guard<CriticalMutex> guard{AllVmosLock::Get()};
for (const auto& iter : all_vmos_) {
zx_status_t s = func(iter);
if (s != ZX_OK) {
return s;
}
}
return ZX_OK;
}
// Same as ForEach but works with VmObject*.
template <typename T>
static zx_status_t ForEachRef(T func) {
Guard<CriticalMutex> guard{AllVmosLock::Get()};
for (auto& iter : all_vmos_) {
zx_status_t s = func(&iter);
if (s != ZX_OK) {
return s;
}
}
return ZX_OK;
}
// If this VMO has any mappings, return the maximal range spanning all the mappings.
ktl::optional<ktl::pair<uint64_t, uint64_t>> GetMaximalMappedRange() const;
// Detaches the underlying page source, if present. Can be called multiple times.
virtual void DetachSource() {}
// If this VMO has a backing page source, and that page source has a koid, then it is returned.
// Otherwise returns a nullopt.
virtual ktl::optional<zx_koid_t> GetPageSourceKoid() const { return ktl::nullopt; }
// Different operations that RangeChangeUpdate* can perform against any VmMappings that are found.
enum class RangeChangeOp {
Unmap,
// Specialized case of Unmap where the caller is stating that it knows that any pages that might
// need to be unmapped are all read instances of the shared zero page.
UnmapZeroPage,
// Unmap, harvest accessed bit & update the page queues.
UnmapAndHarvest,
RemoveWrite,
// Unpin is not a 'real' operation in that it does not cause any actions, and is simply used as
// a mechanism to allow the VmCowPages to trigger a search for any kernel mappings that are
// still referencing an unpinned page.
DebugUnpin,
};
// Apply the specified operation to all mappings in the given range. The provided offset and len
// must both be page aligned.
void RangeChangeUpdateMappingsLocked(uint64_t offset, uint64_t len, RangeChangeOp op)
TA_REQ(lock());
// Mappings are keyed in the btree primarily by their offset, however as there can be
// multiple mappings starting at the same base offset the address of the mapping object is used
// as a tiebreaker.
struct MappingKey {
uint64_t object_offset;
VmMapping* mapping;
bool operator<(const MappingKey& a) const {
if (object_offset != a.object_offset) {
return object_offset < a.object_offset;
}
return mapping < a.mapping;
}
bool operator==(const MappingKey& a) const {
return object_offset == a.object_offset && mapping == a.mapping;
}
};
using MappingTree =
btree::BTree<MappingKey, VmMapping*, VmMappingObserver, btree::GlobalSlabAllocator,
btree::DefaultTypeTraits<VmMapping*>, 512>;
protected:
explicit VmObject(uint32_t options);
// private destructor, only called from refptr
virtual ~VmObject();
friend fbl::RefPtr<VmObject>;
friend void ::cpp_vm_object_free(VmObject*);
DISALLOW_COPY_ASSIGN_AND_MOVE(VmObject);
void AddToGlobalList();
void RemoveFromGlobalList();
bool InGlobalList() const { return fbl::InContainer<internal::GlobalListTag>(*this); }
// Performs the requested cache op against a physical address range. The requested physical range
// must be accessible via the physmap.
static void CacheOpPhys(paddr_t pa, uint64_t length, CacheOpType op,
ArchVmICacheConsistencyManager& cm);
// magic value
fbl::Canary<fbl::magic("VMO_")> canary_;
// Options is extensible by any derived classes. VmObject only defines one bit:
static constexpr uint32_t kPaged = (1u << 0);
const uint32_t options_;
// Cache policy to use for mappings.
arch_mmu_flags_t cache_policy_ TA_GUARDED(lock()) = ARCH_MMU_FLAG_CACHED;
// list of every mapping
MappingTree mapping_list_;
// list of every child. Usage of this lock happens on VmObject creation/deletion in situations
// where we are also either manipulating the heap and/or the AllVmosList lock. As such this lock
// does not end up receiving any contention, due to both being an infrequent operation and already
// effectively serialized by the aforementioned other global locks.
// This lock is expected to be acquired after the VMO lock.
DECLARE_SINGLETON_CRITICAL_MUTEX(ChildListLock);
fbl::TaggedDoublyLinkedList<VmObject*, internal::ChildListTag> children_list_
TA_GUARDED(ChildListLock::Get());
// The user_id_ is semi-const in that it is set once, before the VMO becomes publicly visible, by
// the dispatcher layer. While the dispatcher setting the ID and querying it is trivially
// synchronized by the dispatcher, other parts of the VMO code (mostly debug related) may racily
// inspect this ID before it gets set and so to avoid technical undefined behavior use a relaxed
// atomic.
RelaxedAtomic<uint64_t> user_id_ = 0;
uint32_t mapping_list_len_ TA_GUARDED(lock()) = 0;
uint32_t children_list_len_ TA_GUARDED(ChildListLock::Get()) = 0;
// The user-friendly VMO name. For debug purposes only. That
// is, there is no mechanism to get access to a VMO via this name.
fbl::Name<ZX_MAX_NAME_LEN, fbl::ThreadSafe::No> name_ TA_GUARDED(lock());
static constexpr uint64_t MAX_SIZE = VmPageList::MAX_SIZE;
// Ensure that MAX_SIZE + kPageSize doesn't overflow so no VmObjects
// need to worry about overflow for loop bounds.
static_assert(MAX_SIZE <= RoundDownPageSize(UINT64_MAX) - kPageSize);
static_assert(MAX_SIZE % kPageSize == 0);
private:
// Usage of this lock happens on VmObject creation/deletion in situations where we are also either
// manipulating the heap and/or the AllVmosList lock. As such this lock does not end up receiving
// any contention, due to both being an infrequent operation and already effectively serialized by
// the aforementioned other global locks.
// This lock is expected to be acquired before the VMO lock.
DECLARE_SINGLETON_MUTEX(ChildObserverLock);
// This member, if not null, is used to signal the user facing Dispatcher.
VmObjectChildObserver* child_observer_ TA_GUARDED(ChildObserverLock::Get()) = nullptr;
using GlobalList = fbl::TaggedDoublyLinkedList<VmObject*, internal::GlobalListTag>;
DECLARE_SINGLETON_CRITICAL_MUTEX(AllVmosLock);
static GlobalList all_vmos_ TA_GUARDED(AllVmosLock::Get());
};
namespace internal {
template <typename T>
struct VmObjectTypeTag;
template <>
struct VmObjectTypeTag<VmObjectPaged> {
static constexpr bool PAGED = true;
};
template <>
struct VmObjectTypeTag<VmObjectPhysical> {
static constexpr bool PAGED = false;
};
} // namespace internal
template <typename T>
fbl::RefPtr<T> DownCastVmObject(fbl::RefPtr<VmObject> vmo) {
if (likely(internal::VmObjectTypeTag<T>::PAGED == vmo->is_paged())) {
return fbl::RefPtr<T>::Downcast(ktl::move(vmo));
}
return nullptr;
}
template <typename T>
inline T* DownCastVmObject(VmObject* vmo) {
if (likely(internal::VmObjectTypeTag<T>::PAGED == vmo->is_paged())) {
return static_cast<T*>(vmo);
}
return nullptr;
}
#endif // ZIRCON_KERNEL_VM_INCLUDE_VM_VM_OBJECT_H_