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