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
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214

base / memory / discardable_shared_memory.h [blame]

// Copyright 2014 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_DISCARDABLE_SHARED_MEMORY_H_
#define BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_

#include <stddef.h>

#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/dcheck_is_on.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/threading/thread_collision_warner.h"
#include "base/time/time.h"
#include "build/build_config.h"

#if DCHECK_IS_ON()
#include <set>
#endif

// Linux (including Android) support the MADV_REMOVE argument with madvise()
// which has the behavior of reliably causing zero-fill-on-demand pages to
// be returned after a call. Here we define
// DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE on Linux
// and Android to indicate that this type of behavior can be expected on
// those platforms. Note that madvise() will still be used on other POSIX
// platforms but doesn't provide the zero-fill-on-demand pages guarantee.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
#define DISCARDABLE_SHARED_MEMORY_ZERO_FILL_ON_DEMAND_PAGES_AFTER_PURGE
#endif

namespace base {

namespace trace_event {
class MemoryAllocatorDump;
class ProcessMemoryDump;
}  // namespace trace_event

// Platform abstraction for discardable shared memory.
//
// This class is not thread-safe. Clients are responsible for synchronizing
// access to an instance of this class.
class BASE_EXPORT DiscardableSharedMemory {
 public:
  enum LockResult { SUCCESS, PURGED, FAILED };

  DiscardableSharedMemory();

  // Create a new DiscardableSharedMemory object from an existing, open shared
  // memory file. Memory must be locked.
  explicit DiscardableSharedMemory(UnsafeSharedMemoryRegion region);

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

  // Closes any open files.
  virtual ~DiscardableSharedMemory();

  // Creates and maps a locked DiscardableSharedMemory object with |size|.
  // Returns true on success and false on failure.
  bool CreateAndMap(size_t size);

  // Maps the locked discardable memory into the caller's address space.
  // Returns true on success, false otherwise.
  bool Map(size_t size);

  // Unmaps the discardable shared memory from the caller's address space.
  // Unmapping won't unlock previously locked range.
  // Returns true if successful; returns false on error or if the memory is
  // not mapped.
  bool Unmap();

  // The actual size of the mapped memory (may be larger than requested). It is
  // 0 when the memory has not been mapped via `Map()`.
  size_t mapped_size() const {
    if (shared_memory_mapping_.IsValid()) {
      return mapped_memory().size();
    } else {
      return 0u;
    }
  }

  // Returns a duplicated shared memory region for this DiscardableSharedMemory
  // object.
  UnsafeSharedMemoryRegion DuplicateRegion() const {
    return shared_memory_region_.Duplicate();
  }

  // Returns an ID for the shared memory region. This is ID of the mapped region
  // consistent across all processes and is valid as long as the region is not
  // unmapped.
  const UnguessableToken& mapped_id() const LIFETIME_BOUND {
    return shared_memory_mapping_.guid();
  }

  // Locks a range of memory so that it will not be purged by the system.
  // The range of memory must be unlocked. The result of trying to lock an
  // already locked range is undefined. |offset| and |length| must both be
  // a multiple of the page size as returned by GetPageSize().
  // Passing 0 for |length| means "everything onward".
  // Returns SUCCESS if range was successfully locked and the memory is still
  // resident, PURGED if range was successfully locked but has been purged
  // since last time it was locked and FAILED if range could not be locked.
  // Locking can fail for two reasons; object might have been purged, our
  // last known usage timestamp might be out of date. Last known usage time
  // is updated to the actual last usage timestamp if memory is still resident
  // or 0 if not.
  LockResult Lock(size_t offset, size_t length);

  // Unlock a previously successfully locked range of memory. The range of
  // memory must be locked. The result of trying to unlock a not
  // previously locked range is undefined.
  // |offset| and |length| must both be a multiple of the page size as returned
  // by GetPageSize().
  // Passing 0 for |length| means "everything onward".
  void Unlock(size_t offset, size_t length);

  // Gets a span over the opened discardable memory space. Discardable memory
  // must have been mapped via Map().
  //
  // This gives the logical memory region, matching the size of what was
  // requested. The actual mapped memory may be larger due to system alignment
  // requirements. See `SharedMemoryMapping::size()` vs
  // `SharedMemoryMapping::mapped_size()`.
  span<uint8_t> memory();
  span<const uint8_t> memory() const;

  // Returns the last known usage time for DiscardableSharedMemory object. This
  // may be earlier than the "true" usage time when memory has been used by a
  // different process. Returns NULL time if purged.
  Time last_known_usage() const { return last_known_usage_; }

  // This returns true and sets |last_known_usage_| to 0 if
  // DiscardableSharedMemory object was successfully purged. Purging can fail
  // for two reasons; object might be locked or our last known usage timestamp
  // might be out of date. Last known usage time is updated to |current_time|
  // if locked or the actual last usage timestamp if unlocked. It is often
  // necessary to call this function twice for the object to successfully be
  // purged. First call, updates |last_known_usage_|. Second call, successfully
  // purges the object using the updated |last_known_usage_|.
  // Note: there is no guarantee that multiple calls to this function will
  // successfully purge object. DiscardableSharedMemory object might be locked
  // or another thread/process might be able to lock and unlock it in between
  // each call.
  bool Purge(Time current_time);

  // Returns true if memory is still resident.
  bool IsMemoryResident() const;

  // Returns true if memory is locked.
  bool IsMemoryLocked() const;

  // Closes the open discardable memory segment.
  // It is safe to call Close repeatedly.
  void Close();

  // For tracing: Creates ownership edge to the underlying shared memory dump
  // which is cross process in the given |pmd|. |local_segment_dump| is the dump
  // associated with the local discardable shared memory segment and |is_owned|
  // is true when the current process owns the segment and the effective memory
  // is assigned to the current process.
  void CreateSharedMemoryOwnershipEdge(
      trace_event::MemoryAllocatorDump* local_segment_dump,
      trace_event::ProcessMemoryDump* pmd,
      bool is_owned) const;

#if BUILDFLAG(IS_ANDROID)
  // Returns true if the Ashmem device is supported on this system.
  // Only use this for unit-testing.
  static bool IsAshmemDeviceSupportedForTesting();
#endif

 private:
  // Returns the full mapped memory region after the internal bookkeeping
  // header. This may be larger than the region exposed through `memory()` due
  // to platform alignment requirements. Discardable memory must have been
  // mapped via Map().
  span<uint8_t> mapped_memory();
  span<const uint8_t> mapped_memory() const;

  // LockPages/UnlockPages are platform-native discardable page management
  // helper functions. Both expect |offset| to be specified relative to the
  // base address at which |memory| is mapped, and that |offset| and |length|
  // are page-aligned by the caller.
  // Returns SUCCESS on platforms which do not support discardable pages.
  static LockResult LockPages(const UnsafeSharedMemoryRegion& region,
                              size_t offset,
                              size_t length);
  // UnlockPages() is a no-op on platforms not supporting discardable pages.
  static void UnlockPages(const UnsafeSharedMemoryRegion& region,
                          size_t offset,
                          size_t length);

  // Virtual for tests.
  virtual Time Now() const;

  UnsafeSharedMemoryRegion shared_memory_region_;
  WritableSharedMemoryMapping shared_memory_mapping_;
  size_t locked_page_count_ = 0u;
#if DCHECK_IS_ON()
  std::set<size_t> locked_pages_;
#endif
  // Implementation is not thread-safe but still usable if clients are
  // synchronized somehow. Use a collision warner to detect incorrect usage.
  DFAKE_MUTEX(thread_collision_warner_);
  Time last_known_usage_;
};

}  // namespace base

#endif  // BASE_MEMORY_DISCARDABLE_SHARED_MEMORY_H_