diff options
author | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-23 20:27:51 +0000 |
---|---|---|
committer | avi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-23 20:27:51 +0000 |
commit | 345379a58a63d35db9bb0dfd4776141a0542c337 (patch) | |
tree | 3ff34fdb45a8347d5bf266549a97d38280c9863b | |
parent | 76f273e5fb4a1e158534c0ba77ceda8641ba6f7d (diff) | |
download | chromium_src-345379a58a63d35db9bb0dfd4776141a0542c337.zip chromium_src-345379a58a63d35db9bb0dfd4776141a0542c337.tar.gz chromium_src-345379a58a63d35db9bb0dfd4776141a0542c337.tar.bz2 |
Implement DiscardableMemory for Mac/iOS.
BUG=169597
TEST=unit tests, baby!
Review URL: https://chromiumcodereview.appspot.com/12047027
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@178368 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/base.gypi | 5 | ||||
-rw-r--r-- | base/memory/discardable_memory.cc | 66 | ||||
-rw-r--r-- | base/memory/discardable_memory.h | 70 | ||||
-rw-r--r-- | base/memory/discardable_memory_android.cc | 36 | ||||
-rw-r--r-- | base/memory/discardable_memory_mac.cc | 105 | ||||
-rw-r--r-- | base/memory/discardable_memory_unittest.cc | 33 |
6 files changed, 262 insertions, 53 deletions
diff --git a/base/base.gypi b/base/base.gypi index d19581c..17b7629 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -248,8 +248,10 @@ 'mach_ipc_mac.mm', 'memory/aligned_memory.cc', 'memory/aligned_memory.h', - 'memory/discardable_memory_android.cc', + 'memory/discardable_memory.cc', 'memory/discardable_memory.h', + 'memory/discardable_memory_android.cc', + 'memory/discardable_memory_mac.cc', 'memory/linked_ptr.h', 'memory/manual_constructor.h', 'memory/raw_scoped_refptr_mismatch_checker.h', @@ -653,6 +655,7 @@ ['include', '^mac/objc_property_releaser\\.'], ['include', '^mac/scoped_mach_port\\.'], ['include', '^mac/scoped_nsautorelease_pool\\.'], + ['include', '^memory/discardable_memory_mac\\.'], ['include', '^message_pump_mac\\.'], ['include', '^threading/platform_thread_mac\\.'], ['include', '^sys_string_conversions_mac\\.'], diff --git a/base/memory/discardable_memory.cc b/base/memory/discardable_memory.cc new file mode 100644 index 0000000..33f9e19 --- /dev/null +++ b/base/memory/discardable_memory.cc @@ -0,0 +1,66 @@ +// 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 "base/logging.h" + +namespace base { + +DiscardableMemory::DiscardableMemory() + : memory_(NULL), + size_(0), + is_locked_(false) +#if defined(OS_ANDROID) + , fd_(-1) +#endif // OS_ANDROID + { + DCHECK(Supported()); +} + +void* DiscardableMemory::Memory() const { + DCHECK(is_locked_); + return memory_; +} + +// Stub implementations for platforms that don't support discardable memory. + +#if !defined(OS_ANDROID) && !defined(OS_MACOSX) + +DiscardableMemory::~DiscardableMemory() { + NOTIMPLEMENTED(); +} + +// static +bool DiscardableMemory::Supported() { + return false; +} + +bool DiscardableMemory::InitializeAndLock(size_t size) { + NOTIMPLEMENTED(); + return false; +} + +LockDiscardableMemoryStatus DiscardableMemory::Lock() { + NOTIMPLEMENTED(); + return DISCARDABLE_MEMORY_FAILED; +} + +void DiscardableMemory::Unlock() { + NOTIMPLEMENTED(); +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return false; +} + +// static +void DiscardableMemory::PurgeForTesting() { + NOTIMPLEMENTED(); +} + +#endif // OS_* + +} // namespace base diff --git a/base/memory/discardable_memory.h b/base/memory/discardable_memory.h index 3b63894..900fd97 100644 --- a/base/memory/discardable_memory.h +++ b/base/memory/discardable_memory.h @@ -17,25 +17,32 @@ enum LockDiscardableMemoryStatus { DISCARDABLE_MEMORY_SUCCESS = 1 }; -// Platform abstraction for discardable memory. The DiscardableMemory should -// be mainly used for mobile devices where VM swap is not available, such as -// android. It is particularly helpful for caching large objects without -// worrying about OOM situation. -// Discardable memory allows user to lock(pin) and unlock(unpin) pages so that -// the allocated memory can be discarded by the kernel under memory pressure. -// From http://lwn.net/Articles/452035/: "Pinned pages (the default) behave -// like any anonymous memory. Unpinned pages are available to the kernel for -// eviction during VM pressure. When repinning the pages, the return value -// instructs user-space as to any eviction. In this manner, user-space processes -// may implement caching and similar resource management that efficiently -// integrates with kernel memory management." -// Compared to relying on OOM signals to clean up the memory, DiscardableMemory -// is much simpler as the OS will taking care of the LRU algorithm and there -// is no need to implement a separate cleanup() call. However, there is no -// guarantee which cached objects will be discarded. -// Because of memory alignment, the actual locked discardable memory could be -// larger than the requested memory size. It may not be very efficient for -// small size allocations. +// Platform abstraction for discardable memory. DiscardableMemory is used to +// cache large objects without worrying about blowing out memory, both on mobile +// devices where there is no swap, and desktop devices where unused free memory +// should be used to help the user experience. This is preferable to releasing +// memory in response to an OOM signal because it is simpler, though it has less +// flexibility as to which objects get discarded. +// +// Discardable memory has two states: locked and unlocked. While the memory is +// locked, it will not be discarded. Unlocking the memory allows the OS to +// reclaim it if needed. Locks do not nest. +// +// Notes: +// - The paging behavior of memory while it is locked is not specified. While +// mobile platforms will not swap it out, it may qualify for swapping +// on desktop platforms. It is not expected that this will matter, as the +// preferred pattern of usage for DiscardableMemory is to lock down the +// memory, use it as quickly as possible, and then unlock it. +// - Because of memory alignment, the amount of memory allocated can be +// larger than the requested memory size. It is not very efficient for +// small allocations. +// +// References: +// - Linux: http://lwn.net/Articles/452035/ +// - Mac: http://trac.webkit.org/browser/trunk/Source/WebCore/platform/mac/PurgeableBufferMac.cpp +// the comment starting with "vm_object_purgable_control" at +// http://www.opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/vm/vm_object.c class BASE_EXPORT DiscardableMemory { public: DiscardableMemory(); @@ -45,12 +52,7 @@ class BASE_EXPORT DiscardableMemory { ~DiscardableMemory(); // Check whether the system supports discardable memory. - static bool Supported() { -#if defined(OS_ANDROID) - return true; -#endif - return false; - } + static bool Supported(); // Initialize the DiscardableMemory object. On success, this function returns // true and the memory is locked. This should only be called once. @@ -73,18 +75,32 @@ class BASE_EXPORT DiscardableMemory { // this call returns NULL. void* Memory() const; + // Testing utility calls. + + // Check whether a purge of all discardable memory in the system is supported. + // Use only for testing! + static bool PurgeForTestingSupported(); + + // Purge all discardable memory in the system. This call has global effects + // across all running processes, so it should only be used for testing! + static void PurgeForTesting(); + private: +#if defined(OS_ANDROID) // Maps the discardable memory into the caller's address space. // Returns true on success, false otherwise. bool Map(); // Unmaps the discardable memory from the caller's address space. void Unmap(); +#endif // OS_ANDROID - int fd_; void* memory_; size_t size_; - bool is_pinned_; + bool is_locked_; +#if defined(OS_ANDROID) + int fd_; +#endif // OS_ANDROID DISALLOW_COPY_AND_ASSIGN(DiscardableMemory); }; diff --git a/base/memory/discardable_memory_android.cc b/base/memory/discardable_memory_android.cc index 37691b8d..376d4f0 100644 --- a/base/memory/discardable_memory_android.cc +++ b/base/memory/discardable_memory_android.cc @@ -13,16 +13,13 @@ namespace base { -DiscardableMemory::DiscardableMemory() - : fd_(-1), - memory_(NULL), - size_(0), - is_pinned_(false) { - DCHECK(Supported()); +// static +bool DiscardableMemory::Supported() { + return true; } DiscardableMemory::~DiscardableMemory() { - if (is_pinned_) + if (is_locked_) Unlock(); if (fd_ < 0) return; @@ -53,13 +50,13 @@ bool DiscardableMemory::InitializeAndLock(size_t size) { return false; } - is_pinned_ = true; + is_locked_ = true; return true; } LockDiscardableMemoryStatus DiscardableMemory::Lock() { DCHECK_NE(fd_, -1); - DCHECK(!is_pinned_); + DCHECK(!is_locked_); bool purged = false; if (ashmem_pin_region(fd_, 0, 0) == ASHMEM_WAS_PURGED) @@ -68,23 +65,18 @@ LockDiscardableMemoryStatus DiscardableMemory::Lock() { if (!Map()) return DISCARDABLE_MEMORY_FAILED; - is_pinned_ = true; + is_locked_ = true; return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS; } void DiscardableMemory::Unlock() { DCHECK_GE(fd_, 0); - DCHECK(is_pinned_); + DCHECK(is_locked_); Unmap(); if (ashmem_unpin_region(fd_, 0, 0)) DLOG(ERROR) << "Failed to unpin memory."; - is_pinned_ = false; -} - -void* DiscardableMemory::Memory() const { - DCHECK(is_pinned_); - return memory_; + is_locked_ = false; } bool DiscardableMemory::Map() { @@ -112,4 +104,14 @@ void DiscardableMemory::Unmap() { memory_ = NULL; } +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return false; +} + +// static +void DiscardableMemory::PurgeForTesting() { + NOTIMPLEMENTED(); +} + } // namespace base diff --git a/base/memory/discardable_memory_mac.cc b/base/memory/discardable_memory_mac.cc new file mode 100644 index 0000000..9d70d08 --- /dev/null +++ b/base/memory/discardable_memory_mac.cc @@ -0,0 +1,105 @@ +// 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/logging.h" + +namespace base { + +namespace { + +// 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 + +// static +bool DiscardableMemory::Supported() { + return true; +} + +DiscardableMemory::~DiscardableMemory() { + if (memory_) { + vm_deallocate(mach_task_self(), + reinterpret_cast<vm_address_t>(memory_), + size_); + } +} + +bool DiscardableMemory::InitializeAndLock(size_t size) { + DCHECK(!memory_); + size_ = size; + + vm_address_t buffer = 0; + kern_return_t ret = vm_allocate(mach_task_self(), + &buffer, + size, + VM_FLAGS_PURGABLE | + VM_FLAGS_ANYWHERE | + kDiscardableMemoryTag); + + if (ret != KERN_SUCCESS) { + DLOG(ERROR) << "vm_allocate() failed"; + return false; + } + + is_locked_ = true; + memory_ = reinterpret_cast<void*>(buffer); + return true; +} + +LockDiscardableMemoryStatus DiscardableMemory::Lock() { + DCHECK(!is_locked_); + + int state = VM_PURGABLE_NONVOLATILE; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast<vm_address_t>(memory_), + VM_PURGABLE_SET_STATE, + &state); + + if (ret != KERN_SUCCESS) + return DISCARDABLE_MEMORY_FAILED; + + is_locked_ = true; + return state & VM_PURGABLE_EMPTY ? DISCARDABLE_MEMORY_PURGED + : DISCARDABLE_MEMORY_SUCCESS; +} + +void DiscardableMemory::Unlock() { + DCHECK(is_locked_); + + int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT; + kern_return_t ret = vm_purgable_control( + mach_task_self(), + reinterpret_cast<vm_address_t>(memory_), + VM_PURGABLE_SET_STATE, + &state); + + if (ret != KERN_SUCCESS) + DLOG(ERROR) << "Failed to unlock memory."; + + is_locked_ = false; +} + +// static +bool DiscardableMemory::PurgeForTestingSupported() { + return true; +} + +// static +void DiscardableMemory::PurgeForTesting() { + int state = 0; + vm_purgable_control(mach_task_self(), + reinterpret_cast<vm_address_t>(0U), + VM_PURGABLE_PURGE_ALL, + &state); +} + +} // namespace base diff --git a/base/memory/discardable_memory_unittest.cc b/base/memory/discardable_memory_unittest.cc index 99888e6..60d3582 100644 --- a/base/memory/discardable_memory_unittest.cc +++ b/base/memory/discardable_memory_unittest.cc @@ -5,18 +5,14 @@ #include "base/memory/discardable_memory.h" #include "testing/gtest/include/gtest/gtest.h" -#if defined(OS_ANDROID) -#include "third_party/ashmem/ashmem.h" -#endif - namespace base { -#if defined(OS_ANDROID) +#if defined(OS_ANDROID) || defined(OS_MACOSX) // Test Lock() and Unlock() functionalities. TEST(DiscardableMemoryTest, LockAndUnLock) { ASSERT_TRUE(DiscardableMemory::Supported()); - size_t size = 1024; + const size_t size = 1024; DiscardableMemory memory; ASSERT_TRUE(memory.InitializeAndLock(size)); @@ -24,6 +20,8 @@ TEST(DiscardableMemoryTest, LockAndUnLock) { ASSERT_NE(static_cast<void*>(NULL), addr); memory.Unlock(); + // The system should have no reason to purge discardable blocks in this brief + // interval, though technically speaking this might flake. EXPECT_EQ(DISCARDABLE_MEMORY_SUCCESS, memory.Lock()); addr = memory.Memory(); ASSERT_NE(static_cast<void*>(NULL), addr); @@ -35,10 +33,29 @@ TEST(DiscardableMemoryTest, LockAndUnLock) { TEST(DiscardableMemoryTest, DeleteWhileLocked) { ASSERT_TRUE(DiscardableMemory::Supported()); - size_t size = 1024; + const size_t size = 1024; + + DiscardableMemory memory; + ASSERT_TRUE(memory.InitializeAndLock(size)); +} + +#if defined(OS_MACOSX) +// Test forced purging. +TEST(DiscardableMemoryTest, Purge) { + ASSERT_TRUE(DiscardableMemory::Supported()); + ASSERT_TRUE(DiscardableMemory::PurgeForTestingSupported()); + + const size_t size = 1024; + DiscardableMemory memory; ASSERT_TRUE(memory.InitializeAndLock(size)); + memory.Unlock(); + + DiscardableMemory::PurgeForTesting(); + EXPECT_EQ(DISCARDABLE_MEMORY_PURGED, memory.Lock()); } -#endif +#endif // OS_MACOSX + +#endif // OS_* } |