blob: 71bd0306f4d79cfdf2fabea4869b64f5103272c0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_ZXDUMP_LIVE_MEMORY_CACHE_H_
#define SRC_LIB_ZXDUMP_LIVE_MEMORY_CACHE_H_
// Process::read_memory calls on live processes use a shared cache of past
// reads from any Process in the TaskHolder. Actual reads from the live
// process are always done one whole page at a time. The Buffer<> objects
// that are returned refer to portions of pages held in the cache.
//
// Each page read from a process is held in a LiveMemoryCache::Page object.
// There are three kinds of references to Page objects:
// * The TaskHolder maintains a shared list of cached pages in LRU order.
// * The Process has a map of vaddrs to cached pages read from that process.
// * Buffer<> objects returned to Process::read_memory callers point to pages.
//
// The TaskHolder::LiveMemoryCache is the ultimate owner of Page objects.
// Every live Page object for any Process in the TaskHolder is on its shared
// cache list. While live in the shared cache, each Page is also present in
// the Process::LiveMemory's local cache, which is indexed by vaddr.
//
// Each Buffer object returned by Process::read_memory on a live process owns a
// PageRef that keeps a Page live in the cache. Page has a reference count of
// all live PageRef (i.e. Buffer) objects. Only Page objects with zero
// references are eligible to be evicted from the cache.
#include <lib/zxdump/task.h>
#include <memory>
#include <fbl/intrusive_double_list.h>
#include <fbl/intrusive_wavl_tree.h>
namespace zxdump {
// This is embedded in the TaskHolder.
class TaskHolder::LiveMemoryCache {
public:
using PageContentsPtr = std::unique_ptr<std::byte[]>;
class PageRef; // Forward declaration.
// The Page just holds a PageContentsPtr and its vaddr, plus a whole bunch of
// bookkeeping necessary to be a reference-counted object in both an LRU list
// and a vaddr index.
class Page : public fbl::ContainableBaseClasses<
fbl::TaggedWAVLTreeContainable<Page*, Process,
fbl::NodeOptions::AllowMultiContainerUptr>,
fbl::TaggedDoublyLinkedListable<std::unique_ptr<Page>, TaskHolder,
fbl::NodeOptions::AllowMultiContainerUptr>> {
public:
Page(const Page&) = delete;
Page(Process::LiveMemory& owner, uint64_t vaddr, PageContentsPtr contents)
: vaddr_(vaddr), owner_(owner), contents_(std::move(contents)) {}
~Page();
cpp20::span<const std::byte> contents() const;
// fbl::TaggedWAVLTree uses this to get the sort key.
uint64_t GetKey() const { return vaddr_; }
bool has_refs() const { return refs_ > 0; }
private:
friend PageRef;
uint64_t vaddr_ = 0;
Process::LiveMemory& owner_;
PageContentsPtr contents_;
unsigned int refs_ = 0;
};
// The Buffer<> objects returned by read_memory will own PageRef objects.
// Creation and deletion of PageRef objects is the only way that a Page
// object's ref count is changed.
class PageRef final : public internal::BufferImpl {
public:
explicit PageRef(Page& page) : page_(page) { ++page_.refs_; }
~PageRef() override;
Page& operator*() const { return page_; }
Page* operator->() const { return &page_; }
private:
Page& page_;
};
using PageRefPtr = std::unique_ptr<PageRef>;
using PageCache = fbl::TaggedDoublyLinkedList<std::unique_ptr<Page>, TaskHolder>;
static constexpr size_t kDefaultCacheLimit = 2 << 20; // 2 MiB
size_t cache_limit() const { return cache_limit_; }
void set_cache_limit(size_t limit) {
cache_limit_ = limit;
PruneCache();
}
// Insert a new Page into the cache, becoming the MRU page.
// The returned PageRef owns its only ref.
PageRefPtr NewPage(Process::LiveMemory& owner, uint64_t vaddr, PageContentsPtr contents);
// Note the reuse of a page found in the cache, i.e. make it the MRU page.
PageRefPtr Reuse(Page& page);
// Account one page as contributing to the limited cache size. This is
// called when any Page reaches zero references. (Pages with refs don't
// count towards the cache limit, since the Process::read_memory caller is
// still actively using them.) Then discard LRU pages with no refs while the
// cache size is above the limit.
void OnCachedPage();
// Trim down the cache as needed to fit under the limit.
void PruneCache();
private:
PageCache cache_;
size_t cache_limit_ = kDefaultCacheLimit;
size_t cache_size_ = 0;
};
// The LiveMemory object just contains the local cache index. It's small
// enough to live directly in the Process object, but it's separately allocated
// on demand just to keep the class definition hidden from the public API.
class Process::LiveMemory {
public:
using Page = TaskHolder::LiveMemoryCache::Page;
using PageRef = TaskHolder::LiveMemoryCache::PageRef;
using PageRefPtr = TaskHolder::LiveMemoryCache::PageRefPtr;
using CacheIndex = fbl::TaggedWAVLTree<uint64_t, Page*, Process>;
explicit LiveMemory(TaskHolder::LiveMemoryCache& shared_cache) : shared_cache_(shared_cache) {}
fit::result<Error, Buffer<>> ReadLiveMemory(uint64_t vaddr, size_t size, ReadMemorySize size_mode,
const LiveHandle& handle,
TaskHolder::LiveMemoryCache& shared_cache);
TaskHolder::LiveMemoryCache& shared_cache() { return shared_cache_; }
CacheIndex& cache_index() { return cache_index_; }
private:
TaskHolder::LiveMemoryCache& shared_cache_;
CacheIndex cache_index_;
};
} // namespace zxdump
#endif // SRC_LIB_ZXDUMP_LIVE_MEMORY_CACHE_H_