blob: fa6a23145c389bbfe7d420c3a7a6f68b4ccfb8a8 [file] [log] [blame]
// 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 <mach/mach.h>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_vm.h"
#include "base/memory/discardable_memory_emulated.h"
#include "base/memory/discardable_memory_malloc.h"
#include "base/memory/discardable_memory_manager.h"
#include "base/memory/scoped_ptr.h"
namespace base {
namespace {
// For Mac, have the DiscardableMemoryManager trigger userspace eviction when
// address space usage gets too high (e.g. 512 MBytes).
const size_t kMacMemoryLimit = 512 * 1024 * 1024;
struct SharedState {
SharedState() : manager(kMacMemoryLimit, kMacMemoryLimit, TimeDelta::Max()) {}
internal::DiscardableMemoryManager manager;
};
LazyInstance<SharedState>::Leaky g_shared_state = LAZY_INSTANCE_INITIALIZER;
// The VM subsystem allows tagging of memory and 240-255 is reserved for
// application use (see mach/vm_statistics.h). Pick 252 (after chromium's atomic
// weight of ~52).
const int kDiscardableMemoryTag = VM_MAKE_TAG(252);
class DiscardableMemoryMac
: public DiscardableMemory,
public internal::DiscardableMemoryManagerAllocation {
public:
explicit DiscardableMemoryMac(size_t bytes)
: memory_(0, 0),
bytes_(mach_vm_round_page(bytes)),
is_locked_(false) {
g_shared_state.Pointer()->manager.Register(this, bytes);
}
bool Initialize() { return Lock() != DISCARDABLE_MEMORY_LOCK_STATUS_FAILED; }
virtual ~DiscardableMemoryMac() {
if (is_locked_)
Unlock();
g_shared_state.Pointer()->manager.Unregister(this);
}
// Overridden from DiscardableMemory:
virtual DiscardableMemoryLockStatus Lock() OVERRIDE {
DCHECK(!is_locked_);
bool purged = false;
if (!g_shared_state.Pointer()->manager.AcquireLock(this, &purged))
return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED;
is_locked_ = true;
return purged ? DISCARDABLE_MEMORY_LOCK_STATUS_PURGED
: DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS;
}
virtual void Unlock() OVERRIDE {
DCHECK(is_locked_);
g_shared_state.Pointer()->manager.ReleaseLock(this);
is_locked_ = false;
}
virtual void* Memory() const OVERRIDE {
DCHECK(is_locked_);
return reinterpret_cast<void*>(memory_.address());
}
// Overridden from internal::DiscardableMemoryManagerAllocation:
virtual bool AllocateAndAcquireLock() OVERRIDE {
kern_return_t ret;
bool persistent;
if (!memory_.size()) {
vm_address_t address = 0;
ret = vm_allocate(
mach_task_self(),
&address,
bytes_,
VM_FLAGS_ANYWHERE | VM_FLAGS_PURGABLE | kDiscardableMemoryTag);
MACH_CHECK(ret == KERN_SUCCESS, ret) << "vm_allocate";
memory_.reset(address, bytes_);
// When making a fresh allocation, it's impossible for |persistent| to
// be true.
persistent = false;
} else {
// |persistent| will be reset to false below if appropriate, but when
// reusing an existing allocation, it's possible for it to be true.
persistent = true;
#if !defined(NDEBUG)
ret = vm_protect(mach_task_self(),
memory_.address(),
memory_.size(),
FALSE,
VM_PROT_DEFAULT);
MACH_DCHECK(ret == KERN_SUCCESS, ret) << "vm_protect";
#endif
}
int state = VM_PURGABLE_NONVOLATILE;
ret = vm_purgable_control(mach_task_self(),
memory_.address(),
VM_PURGABLE_SET_STATE,
&state);
MACH_CHECK(ret == KERN_SUCCESS, ret) << "vm_purgable_control";
if (state & VM_PURGABLE_EMPTY)
persistent = false;
return persistent;
}
virtual void ReleaseLock() OVERRIDE {
int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT;
kern_return_t ret = vm_purgable_control(mach_task_self(),
memory_.address(),
VM_PURGABLE_SET_STATE,
&state);
MACH_CHECK(ret == KERN_SUCCESS, ret) << "vm_purgable_control";
#if !defined(NDEBUG)
ret = vm_protect(mach_task_self(),
memory_.address(),
memory_.size(),
FALSE,
VM_PROT_NONE);
MACH_DCHECK(ret == KERN_SUCCESS, ret) << "vm_protect";
#endif
}
virtual void Purge() OVERRIDE {
memory_.reset();
}
private:
mac::ScopedMachVM memory_;
const size_t bytes_;
bool is_locked_;
DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryMac);
};
} // namespace
// static
bool DiscardableMemory::ReduceMemoryUsage() {
return internal::DiscardableMemoryEmulated::ReduceMemoryUsage();
}
// static
void DiscardableMemory::GetSupportedTypes(
std::vector<DiscardableMemoryType>* types) {
const DiscardableMemoryType supported_types[] = {
DISCARDABLE_MEMORY_TYPE_MAC,
DISCARDABLE_MEMORY_TYPE_EMULATED,
DISCARDABLE_MEMORY_TYPE_MALLOC
};
types->assign(supported_types, supported_types + arraysize(supported_types));
}
// static
scoped_ptr<DiscardableMemory> DiscardableMemory::CreateLockedMemoryWithType(
DiscardableMemoryType type, size_t size) {
switch (type) {
case DISCARDABLE_MEMORY_TYPE_NONE:
case DISCARDABLE_MEMORY_TYPE_ASHMEM:
return scoped_ptr<DiscardableMemory>();
case DISCARDABLE_MEMORY_TYPE_MAC: {
scoped_ptr<DiscardableMemoryMac> memory(new DiscardableMemoryMac(size));
if (!memory->Initialize())
return scoped_ptr<DiscardableMemory>();
return memory.PassAs<DiscardableMemory>();
}
case DISCARDABLE_MEMORY_TYPE_EMULATED: {
scoped_ptr<internal::DiscardableMemoryEmulated> memory(
new internal::DiscardableMemoryEmulated(size));
if (!memory->Initialize())
return scoped_ptr<DiscardableMemory>();
return memory.PassAs<DiscardableMemory>();
}
case DISCARDABLE_MEMORY_TYPE_MALLOC: {
scoped_ptr<internal::DiscardableMemoryMalloc> memory(
new internal::DiscardableMemoryMalloc(size));
if (!memory->Initialize())
return scoped_ptr<DiscardableMemory>();
return memory.PassAs<DiscardableMemory>();
}
}
NOTREACHED();
return scoped_ptr<DiscardableMemory>();
}
// static
void DiscardableMemory::PurgeForTesting() {
int state = 0;
vm_purgable_control(mach_task_self(), 0, VM_PURGABLE_PURGE_ALL, &state);
internal::DiscardableMemoryEmulated::PurgeForTesting();
}
} // namespace base