| // 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_ |