summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-23 20:27:51 +0000
committeravi@chromium.org <avi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-23 20:27:51 +0000
commit345379a58a63d35db9bb0dfd4776141a0542c337 (patch)
tree3ff34fdb45a8347d5bf266549a97d38280c9863b
parent76f273e5fb4a1e158534c0ba77ceda8641ba6f7d (diff)
downloadchromium_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.gypi5
-rw-r--r--base/memory/discardable_memory.cc66
-rw-r--r--base/memory/discardable_memory.h70
-rw-r--r--base/memory/discardable_memory_android.cc36
-rw-r--r--base/memory/discardable_memory_mac.cc105
-rw-r--r--base/memory/discardable_memory_unittest.cc33
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_*
}