xref: /aosp_15_r20/external/cronet/base/memory/madv_free_discardable_memory_posix.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef BASE_MEMORY_MADV_FREE_DISCARDABLE_MEMORY_POSIX_H_
6 #define BASE_MEMORY_MADV_FREE_DISCARDABLE_MEMORY_POSIX_H_
7 
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <atomic>
12 #include <vector>
13 
14 #include "base/base_export.h"
15 #include "base/functional/callback.h"
16 #include "base/memory/discardable_memory.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/memory/raw_ptr_exclusion.h"
19 #include "base/sequence_checker.h"
20 #include "base/threading/thread_collision_warner.h"
21 #include "build/build_config.h"
22 
23 namespace base {
24 // Discardable memory backed by the MADV_FREE advice value, available since
25 // Linux 4.5.
26 //
27 // When unlocked, this implementation of discardable memory will
28 // apply the MADV_FREE advice value to all pages within the allocated range,
29 // causing pages to be discarded instead of swapped upon memory pressure.
30 // When pages are discarded, they become zero-fill-on-demand pages.
31 // Attempting to unlock an already-unlocked instance is undefined behaviour.
32 //
33 // When locked, all pages will be checked for eviction. If any page has
34 // been discarded, the entire allocated range is unmapped and the lock fails.
35 // After a failed lock, the instance remains unlocked but any further attempts
36 // to lock will fail. Additionally, the discardable memory instance is
37 // invalidated and access to memory obtained via data() is undefined behaviour.
38 // Attempting to lock an already-locked instance is undefined behaviour. If no
39 // page in the allocated range has been discarded, then lock succeeds and the
40 // allocated range of memory is available for use without any page fault,
41 // additional allocations, or memory zeroing.
42 //
43 // If DCHECK_IS_ON(), additional checks are added to ensure that the discardable
44 // memory instance is being used correctly. These checks are not present by
45 // default, as some incur a significant performance penalty or do not warrant
46 // crashing the process. These checks are:
47 // -    Do not allow lock while already locked or unlock while already unlocked
48 // -    Do not allow memory access via data() if instance is deallocated after
49 //      Lock() (although invalid memory can still be accessed through existing
50 //      pointers)
51 // -    After Unlock(), disallow read or write of memory pointed to by data()
52 //      with PROT_NONE until next Lock()
53 //
54 // Caveats:
55 // [1]: The smallest allocation unit is the size of a page, so it is
56 //      unsuitable for small allocations.
57 //
58 // [2]: The size of a discardable memory instance must be greater than 0 bytes.
59 //
60 class BASE_EXPORT MadvFreeDiscardableMemoryPosix : public DiscardableMemory {
61  public:
62   MadvFreeDiscardableMemoryPosix(size_t size_in_pages,
63                                  std::atomic<size_t>* allocator_byte_count);
64 
65   MadvFreeDiscardableMemoryPosix(const MadvFreeDiscardableMemoryPosix&) =
66       delete;
67   MadvFreeDiscardableMemoryPosix& operator=(
68       const MadvFreeDiscardableMemoryPosix&) = delete;
69 
70   ~MadvFreeDiscardableMemoryPosix() override;
71 
72   bool Lock() override;
73   void Unlock() override;
74   void* data() const override;
75 
76   bool IsLockedForTesting() const;
77   void DiscardForTesting() override;
78 
79   trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
80       const char* name,
81       trace_event::ProcessMemoryDump* pmd) const override;
82 
83  protected:
GetPageCount()84   size_t GetPageCount() const { return allocated_pages_; }
85 
86   bool IsValid() const;
87 
88   void SetKeepMemoryForTesting(bool keep_memory);
89 
90   // Force page discard by applying MADV_DONTNEED hint on a page.
91   // Has the same effect as if the page was naturally discarded during
92   // memory pressure due to MADV_FREE (i.e. zero-fill-on-demand pages for
93   // anonymous private mappings).
94   // Note that MADV_DONTNEED takes effect immediately for non-shared mappings.
95   void DiscardPage(size_t page_index);
96 
97  private:
98   bool LockPage(size_t page_index);
99   void UnlockPage(size_t page_index);
100 
101   bool Deallocate();
102 
103   // Gets whether this instance has been discarded (but not yet unmapped).
104   bool IsDiscarded() const;
105 
106   // Get whether all pages in this discardable memory instance are resident.
107   bool IsResident() const;
108 
109   const size_t size_in_bytes_;
110   const size_t allocated_pages_;
111 
112   // Pointer to allocator memory usage metric for updating upon allocation and
113   // destruction.
114   raw_ptr<std::atomic<size_t>> allocator_byte_count_;
115 
116   // Data comes from mmap() and we manage its poisioning.
117   // RAW_PTR_EXCLUSION: Never allocated by PartitionAlloc (always mmap'ed), so
118   // there is no benefit to using a raw_ptr, only cost.
119   RAW_PTR_EXCLUSION void* data_;
120   bool is_locked_ = true;
121 
122   // If true, MADV_FREE will not be set on Unlock().
123   bool keep_memory_for_testing_ = false;
124 
125   // Stores the first word of a page for use during locking.
126   std::vector<std::atomic<intptr_t>> page_first_word_;
127 
128   DFAKE_MUTEX(thread_collision_warner_);
129 };
130 
131 enum class MadvFreeSupport { kUnsupported, kSupported };
132 BASE_EXPORT MadvFreeSupport GetMadvFreeSupport();
133 
134 }  // namespace base
135 
136 #endif  // BASE_MEMORY_MADV_FREE_DISCARDABLE_MEMORY_POSIX_H_
137