165 lines
4.0 KiB
C++
165 lines
4.0 KiB
C++
|
// 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
|