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