OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "base/memory/discardable_memory_mach.h" | |
6 | |
7 #include <mach/mach.h> | |
8 | |
9 #include "base/basictypes.h" | |
10 #include "base/compiler_specific.h" | |
11 #include "base/lazy_instance.h" | |
12 #include "base/logging.h" | |
13 #include "base/mac/mach_logging.h" | |
14 | |
15 namespace base { | |
16 namespace { | |
17 | |
18 // For Mach, have the DiscardableMemoryManager trigger userspace eviction when | |
19 // address space usage gets too high (e.g. 512 MBytes). | |
20 const size_t kMachMemoryLimit = 512 * 1024 * 1024; | |
21 | |
22 // internal::DiscardableMemoryManager has an explicit constructor that takes | |
23 // a number of memory limit parameters. The LeakyLazyInstanceTraits doesn't | |
24 // handle the case. Thus, we need our own class here. | |
25 struct DiscardableMemoryManagerLazyInstanceTraits { | |
26 // Leaky as discardable memory clients can use this after the exit handler | |
27 // has been called. | |
28 static const bool kRegisterOnExit = false; | |
29 #ifndef NDEBUG | |
30 static const bool kAllowedToAccessOnNonjoinableThread = true; | |
31 #endif | |
32 | |
33 static internal::DiscardableMemoryManager* New(void* instance) { | |
34 return new (instance) internal::DiscardableMemoryManager( | |
35 kMachMemoryLimit, kMachMemoryLimit, TimeDelta::Max()); | |
36 } | |
37 static void Delete(internal::DiscardableMemoryManager* instance) { | |
38 instance->~DiscardableMemoryManager(); | |
39 } | |
40 }; | |
41 | |
42 LazyInstance<internal::DiscardableMemoryManager, | |
43 DiscardableMemoryManagerLazyInstanceTraits> | |
44 g_manager = LAZY_INSTANCE_INITIALIZER; | |
45 | |
46 // The VM subsystem allows tagging of memory and 240-255 is reserved for | |
47 // application use (see mach/vm_statistics.h). Pick 252 (after chromium's atomic | |
48 // weight of ~52). | |
49 const int kDiscardableMemoryTag = VM_MAKE_TAG(252); | |
50 | |
51 } // namespace | |
52 | |
53 namespace internal { | |
54 | |
55 DiscardableMemoryMach::DiscardableMemoryMach(size_t bytes) | |
56 : memory_(0, 0), bytes_(mach_vm_round_page(bytes)), is_locked_(false) { | |
57 g_manager.Pointer()->Register(this, bytes); | |
58 } | |
59 | |
60 DiscardableMemoryMach::~DiscardableMemoryMach() { | |
61 if (is_locked_) | |
62 Unlock(); | |
63 g_manager.Pointer()->Unregister(this); | |
64 } | |
65 | |
66 bool DiscardableMemoryMach::Initialize() { | |
67 return Lock() != DISCARDABLE_MEMORY_LOCK_STATUS_FAILED; | |
68 } | |
69 | |
70 DiscardableMemoryLockStatus DiscardableMemoryMach::Lock() { | |
71 DCHECK(!is_locked_); | |
72 | |
73 bool purged = false; | |
74 if (!g_manager.Pointer()->AcquireLock(this, &purged)) | |
75 return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED; | |
76 | |
77 is_locked_ = true; | |
78 return purged ? DISCARDABLE_MEMORY_LOCK_STATUS_PURGED | |
79 : DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS; | |
80 } | |
81 | |
82 void DiscardableMemoryMach::Unlock() { | |
83 DCHECK(is_locked_); | |
84 g_manager.Pointer()->ReleaseLock(this); | |
85 is_locked_ = false; | |
86 } | |
87 | |
88 void* DiscardableMemoryMach::Memory() const { | |
89 DCHECK(is_locked_); | |
90 return reinterpret_cast<void*>(memory_.address()); | |
91 } | |
92 | |
93 bool DiscardableMemoryMach::AllocateAndAcquireLock() { | |
94 kern_return_t ret; | |
95 bool persistent; | |
96 if (!memory_.size()) { | |
97 vm_address_t address = 0; | |
98 ret = vm_allocate( | |
99 mach_task_self(), | |
100 &address, | |
101 bytes_, | |
102 VM_FLAGS_ANYWHERE | VM_FLAGS_PURGABLE | kDiscardableMemoryTag); | |
103 MACH_CHECK(ret == KERN_SUCCESS, ret) << "vm_allocate"; | |
104 memory_.reset(address, bytes_); | |
105 | |
106 // When making a fresh allocation, it's impossible for |persistent| to | |
107 // be true. | |
108 persistent = false; | |
109 } else { | |
110 // |persistent| will be reset to false below if appropriate, but when | |
111 // reusing an existing allocation, it's possible for it to be true. | |
112 persistent = true; | |
113 | |
114 #if !defined(NDEBUG) | |
115 ret = vm_protect(mach_task_self(), | |
116 memory_.address(), | |
117 memory_.size(), | |
118 FALSE, | |
119 VM_PROT_DEFAULT); | |
120 MACH_DCHECK(ret == KERN_SUCCESS, ret) << "vm_protect"; | |
121 #endif | |
122 } | |
123 | |
124 int state = VM_PURGABLE_NONVOLATILE; | |
125 ret = vm_purgable_control( | |
126 mach_task_self(), memory_.address(), VM_PURGABLE_SET_STATE, &state); | |
127 MACH_CHECK(ret == KERN_SUCCESS, ret) << "vm_purgable_control"; | |
128 if (state & VM_PURGABLE_EMPTY) | |
129 persistent = false; | |
130 | |
131 return persistent; | |
132 } | |
133 | |
134 void DiscardableMemoryMach::ReleaseLock() { | |
135 int state = VM_PURGABLE_VOLATILE | VM_VOLATILE_GROUP_DEFAULT; | |
136 kern_return_t ret = vm_purgable_control( | |
137 mach_task_self(), memory_.address(), VM_PURGABLE_SET_STATE, &state); | |
138 MACH_CHECK(ret == KERN_SUCCESS, ret) << "vm_purgable_control"; | |
139 | |
140 #if !defined(NDEBUG) | |
141 ret = vm_protect( | |
142 mach_task_self(), memory_.address(), memory_.size(), FALSE, VM_PROT_NONE); | |
143 MACH_DCHECK(ret == KERN_SUCCESS, ret) << "vm_protect"; | |
144 #endif | |
145 } | |
146 | |
147 void DiscardableMemoryMach::Purge() { | |
148 memory_.reset(); | |
149 } | |
150 | |
151 } // namespace internal | |
152 } // namespace base | |
OLD | NEW |