// 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