blob: 5fc43f2a7d850e487cae1f339adb89211a6281f4 [file] [log] [blame]
// Copyright 2014 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_mach.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"
namespace base {
namespace {
// For Mach, have the DiscardableMemoryManager trigger userspace eviction when
// address space usage gets too high (e.g. 512 MBytes).
const size_t kMachMemoryLimit = 512 * 1024 * 1024;
// internal::DiscardableMemoryManager has an explicit constructor that takes
// a number of memory limit parameters. The LeakyLazyInstanceTraits doesn't
// handle the case. Thus, we need our own class here.
struct DiscardableMemoryManagerLazyInstanceTraits {
// Leaky as discardable memory clients can use this after the exit handler
// has been called.
static const bool kRegisterOnExit = false;
#ifndef NDEBUG
static const bool kAllowedToAccessOnNonjoinableThread = true;
#endif
static internal::DiscardableMemoryManager* New(void* instance) {
return new (instance) internal::DiscardableMemoryManager(
kMachMemoryLimit, kMachMemoryLimit, TimeDelta::Max());
}
static void Delete(internal::DiscardableMemoryManager* instance) {
instance->~DiscardableMemoryManager();
}
};
LazyInstance<internal::DiscardableMemoryManager,
DiscardableMemoryManagerLazyInstanceTraits>
g_manager = 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);
} // namespace
namespace internal {
DiscardableMemoryMach::DiscardableMemoryMach(size_t bytes)
: memory_(0, 0), bytes_(mach_vm_round_page(bytes)), is_locked_(false) {
g_manager.Pointer()->Register(this, bytes);
}
DiscardableMemoryMach::~DiscardableMemoryMach() {
if (is_locked_)
Unlock();
g_manager.Pointer()->Unregister(this);
}
// static
void DiscardableMemoryMach::PurgeForTesting() {
int state = 0;
vm_purgable_control(mach_task_self(), 0, VM_PURGABLE_PURGE_ALL, &state);
}
bool DiscardableMemoryMach::Initialize() {
return Lock() != DISCARDABLE_MEMORY_LOCK_STATUS_FAILED;
}
DiscardableMemoryLockStatus DiscardableMemoryMach::Lock() {
DCHECK(!is_locked_);
bool purged = false;
if (!g_manager.Pointer()->AcquireLock(this, &purged))
return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED;
is_locked_ = true;
return purged ? DISCARDABLE_MEMORY_LOCK_STATUS_PURGED
: DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS;
}
void DiscardableMemoryMach::Unlock() {
DCHECK(is_locked_);
g_manager.Pointer()->ReleaseLock(this);
is_locked_ = false;
}
void* DiscardableMemoryMach::Memory() const {
DCHECK(is_locked_);
return reinterpret_cast<void*>(memory_.address());
}
bool DiscardableMemoryMach::AllocateAndAcquireLock() {
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;
}
void DiscardableMemoryMach::ReleaseLock() {
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
}
void DiscardableMemoryMach::Purge() {
memory_.reset();
}
bool DiscardableMemoryMach::IsMemoryResident() const {
return true;
}
} // namespace internal
} // namespace base