1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136

base / memory / madv_free_discardable_memory_posix.h [blame]

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_MEMORY_MADV_FREE_DISCARDABLE_MEMORY_POSIX_H_
#define BASE_MEMORY_MADV_FREE_DISCARDABLE_MEMORY_POSIX_H_

#include <stddef.h>
#include <stdint.h>

#include <atomic>
#include <vector>

#include "base/base_export.h"
#include "base/functional/callback.h"
#include "base/memory/discardable_memory.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/sequence_checker.h"
#include "base/threading/thread_collision_warner.h"
#include "build/build_config.h"

namespace base {
// Discardable memory backed by the MADV_FREE advice value, available since
// Linux 4.5.
//
// When unlocked, this implementation of discardable memory will
// apply the MADV_FREE advice value to all pages within the allocated range,
// causing pages to be discarded instead of swapped upon memory pressure.
// When pages are discarded, they become zero-fill-on-demand pages.
// Attempting to unlock an already-unlocked instance is undefined behaviour.
//
// When locked, all pages will be checked for eviction. If any page has
// been discarded, the entire allocated range is unmapped and the lock fails.
// After a failed lock, the instance remains unlocked but any further attempts
// to lock will fail. Additionally, the discardable memory instance is
// invalidated and access to memory obtained via data() is undefined behaviour.
// Attempting to lock an already-locked instance is undefined behaviour. If no
// page in the allocated range has been discarded, then lock succeeds and the
// allocated range of memory is available for use without any page fault,
// additional allocations, or memory zeroing.
//
// If DCHECK_IS_ON(), additional checks are added to ensure that the discardable
// memory instance is being used correctly. These checks are not present by
// default, as some incur a significant performance penalty or do not warrant
// crashing the process. These checks are:
// -    Do not allow lock while already locked or unlock while already unlocked
// -    Do not allow memory access via data() if instance is deallocated after
//      Lock() (although invalid memory can still be accessed through existing
//      pointers)
// -    After Unlock(), disallow read or write of memory pointed to by data()
//      with PROT_NONE until next Lock()
//
// Caveats:
// [1]: The smallest allocation unit is the size of a page, so it is
//      unsuitable for small allocations.
//
// [2]: The size of a discardable memory instance must be greater than 0 bytes.
//
class BASE_EXPORT MadvFreeDiscardableMemoryPosix : public DiscardableMemory {
 public:
  MadvFreeDiscardableMemoryPosix(size_t size_in_pages,
                                 std::atomic<size_t>* allocator_byte_count);

  MadvFreeDiscardableMemoryPosix(const MadvFreeDiscardableMemoryPosix&) =
      delete;
  MadvFreeDiscardableMemoryPosix& operator=(
      const MadvFreeDiscardableMemoryPosix&) = delete;

  ~MadvFreeDiscardableMemoryPosix() override;

  bool Lock() override;
  void Unlock() override;
  void* data() const override;

  bool IsLockedForTesting() const;
  void DiscardForTesting() override;

  trace_event::MemoryAllocatorDump* CreateMemoryAllocatorDump(
      const char* name,
      trace_event::ProcessMemoryDump* pmd) const override;

 protected:
  size_t GetPageCount() const { return allocated_pages_; }

  bool IsValid() const;

  void SetKeepMemoryForTesting(bool keep_memory);

  // Force page discard by applying MADV_DONTNEED hint on a page.
  // Has the same effect as if the page was naturally discarded during
  // memory pressure due to MADV_FREE (i.e. zero-fill-on-demand pages for
  // anonymous private mappings).
  // Note that MADV_DONTNEED takes effect immediately for non-shared mappings.
  void DiscardPage(size_t page_index);

 private:
  bool LockPage(size_t page_index);
  void UnlockPage(size_t page_index);

  bool Deallocate();

  // Gets whether this instance has been discarded (but not yet unmapped).
  bool IsDiscarded() const;

  // Get whether all pages in this discardable memory instance are resident.
  bool IsResident() const;

  const size_t size_in_bytes_;
  const size_t allocated_pages_;

  // Pointer to allocator memory usage metric for updating upon allocation and
  // destruction.
  raw_ptr<std::atomic<size_t>> allocator_byte_count_;

  // Data comes from mmap() and we manage its poisioning.
  // RAW_PTR_EXCLUSION: Never allocated by PartitionAlloc (always mmap'ed), so
  // there is no benefit to using a raw_ptr, only cost.
  RAW_PTR_EXCLUSION void* data_;
  bool is_locked_ = true;

  // If true, MADV_FREE will not be set on Unlock().
  bool keep_memory_for_testing_ = false;

  // Stores the first word of a page for use during locking.
  std::vector<std::atomic<intptr_t>> page_first_word_;

  DFAKE_MUTEX(thread_collision_warner_);
};

enum class MadvFreeSupport { kUnsupported, kSupported };
BASE_EXPORT MadvFreeSupport GetMadvFreeSupport();

}  // namespace base

#endif  // BASE_MEMORY_MADV_FREE_DISCARDABLE_MEMORY_POSIX_H_