summaryrefslogtreecommitdiffstats
path: root/base/memory/discardable_memory_android.cc
blob: 73a25ae419e897b7bf2f3f903edd931a1599be27 (plain)
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
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/memory/discardable_memory.h"

#include <sys/mman.h>
#include <unistd.h>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/synchronization/lock.h"
#include "third_party/ashmem/ashmem.h"

namespace {

base::LazyInstance<base::Lock>::Leaky g_discardable_memory_lock =
    LAZY_INSTANCE_INITIALIZER;

// Total number of discardable memory in the process.
int g_num_discardable_memory = 0;

// Upper limit on the number of discardable memory to avoid hitting file
// descriptor limit.
const int kDiscardableMemoryNumLimit = 128;

}

namespace base {

// static
bool DiscardableMemory::Supported() {
  return true;
}

DiscardableMemory::~DiscardableMemory() {
  if (is_locked_)
    Unlock();
  // If fd_ is smaller than 0, initialization must have failed and
  // g_num_discardable_memory is not incremented by the caller.
  if (fd_ < 0)
    return;
  HANDLE_EINTR(close(fd_));
  fd_ = -1;
  ReleaseFileDescriptor();
}

bool DiscardableMemory::ReserveFileDescriptor() {
  base::AutoLock lock(g_discardable_memory_lock.Get());
  if (g_num_discardable_memory < kDiscardableMemoryNumLimit) {
    ++g_num_discardable_memory;
    return true;
  }
  return false;
}

void DiscardableMemory::ReleaseFileDescriptor() {
  base::AutoLock lock(g_discardable_memory_lock.Get());
  --g_num_discardable_memory;
  DCHECK_LE(0, g_num_discardable_memory);
}

bool DiscardableMemory::InitializeAndLock(size_t size) {
  // When this function returns true, fd_ should be larger or equal than 0
  // and g_num_discardable_memory is incremented by 1. Otherwise, fd_
  // is less than 0 and g_num_discardable_memory is not incremented by
  // the caller.
  DCHECK_EQ(fd_, -1);
  DCHECK(!memory_);
  if (!ReserveFileDescriptor())
    return false;

  size_ = size;
  fd_ = ashmem_create_region("", size);

  if (fd_ < 0) {
    DLOG(ERROR) << "ashmem_create_region() failed";
    ReleaseFileDescriptor();
    return false;
  }

  int err = ashmem_set_prot_region(fd_, PROT_READ | PROT_WRITE);
  if (err < 0) {
    DLOG(ERROR) << "Error " << err << " when setting protection of ashmem";
    HANDLE_EINTR(close(fd_));
    fd_ = -1;
    ReleaseFileDescriptor();
    return false;
  }

  if (!Map()) {
    // Close the file descriptor in case of any initialization errors.
    HANDLE_EINTR(close(fd_));
    fd_ = -1;
    ReleaseFileDescriptor();
    return false;
  }

  is_locked_ = true;
  return true;
}

LockDiscardableMemoryStatus DiscardableMemory::Lock() {
  DCHECK_NE(fd_, -1);
  DCHECK(!is_locked_);

  bool purged = false;
  if (ashmem_pin_region(fd_, 0, 0) == ASHMEM_WAS_PURGED)
    purged = true;

  if (!Map())
    return DISCARDABLE_MEMORY_FAILED;

  is_locked_ = true;
  return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS;
}

void DiscardableMemory::Unlock() {
  DCHECK_GE(fd_, 0);
  DCHECK(is_locked_);

  Unmap();
  if (ashmem_unpin_region(fd_, 0, 0))
    DLOG(ERROR) << "Failed to unpin memory.";
  is_locked_ = false;
}

bool DiscardableMemory::Map() {
  DCHECK(!memory_);
  // There is a problem using MAP_PRIVATE here. As we are constantly calling
  // Lock() and Unlock(), data could get lost if they are not written to the
  // underlying file when Unlock() gets called.
  memory_ = mmap(NULL, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
  if (memory_ == (void*)-1) {
    DPLOG(ERROR) << "Failed to map memory.";
    memory_ = NULL;
    if (ashmem_unpin_region(fd_, 0, 0))
      DLOG(ERROR) << "Failed to unpin memory.";
    return false;
  }
  return true;
}

void DiscardableMemory::Unmap() {
  DCHECK(memory_);

  if (-1 == munmap(memory_, size_))
    DPLOG(ERROR) << "Failed to unmap memory.";

  memory_ = NULL;
}

// static
bool DiscardableMemory::PurgeForTestingSupported() {
  return false;
}

// static
void DiscardableMemory::PurgeForTesting() {
  NOTIMPLEMENTED();
}

}  // namespace base