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.
5 #include "content/child/child_discardable_shared_memory_manager.h"
7 #include "base/atomic_sequence_num.h"
9 #include "base/debug/crash_logging.h"
10 #include "base/memory/discardable_memory.h"
11 #include "base/memory/discardable_shared_memory.h"
12 #include "base/metrics/histogram.h"
13 #include "base/process/memory.h"
14 #include "base/process/process_metrics.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "base/trace_event/memory_dump_manager.h"
18 #include "base/trace_event/trace_event.h"
19 #include "content/common/child_process_messages.h"
24 // Default allocation size.
25 const size_t kAllocationSize
= 4 * 1024 * 1024;
27 // Global atomic to generate unique discardable shared memory IDs.
28 base::StaticAtomicSequenceNumber g_next_discardable_shared_memory_id
;
30 class DiscardableMemoryImpl
: public base::DiscardableMemory
{
32 DiscardableMemoryImpl(ChildDiscardableSharedMemoryManager
* manager
,
33 scoped_ptr
<DiscardableSharedMemoryHeap::Span
> span
)
34 : manager_(manager
), span_(span
.Pass()), is_locked_(true) {}
36 ~DiscardableMemoryImpl() override
{
38 manager_
->UnlockSpan(span_
.get());
40 manager_
->ReleaseSpan(span_
.Pass());
43 // Overridden from base::DiscardableMemory:
44 bool Lock() override
{
47 if (!manager_
->LockSpan(span_
.get()))
53 void Unlock() override
{
56 manager_
->UnlockSpan(span_
.get());
59 void* data() const override
{
61 return reinterpret_cast<void*>(span_
->start() * base::GetPageSize());
64 base::trace_event::MemoryAllocatorDump
* CreateMemoryAllocatorDump(
66 base::trace_event::ProcessMemoryDump
* pmd
) const override
{
67 return manager_
->CreateMemoryAllocatorDump(span_
.get(), name
, pmd
);
71 ChildDiscardableSharedMemoryManager
* const manager_
;
72 scoped_ptr
<DiscardableSharedMemoryHeap::Span
> span_
;
75 DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl
);
78 void SendDeletedDiscardableSharedMemoryMessage(
79 scoped_refptr
<ThreadSafeSender
> sender
,
80 DiscardableSharedMemoryId id
) {
81 sender
->Send(new ChildProcessHostMsg_DeletedDiscardableSharedMemory(id
));
86 ChildDiscardableSharedMemoryManager::ChildDiscardableSharedMemoryManager(
87 ThreadSafeSender
* sender
)
88 : heap_(base::GetPageSize()), sender_(sender
) {
89 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
90 this, base::ThreadTaskRunnerHandle::Get());
93 ChildDiscardableSharedMemoryManager::~ChildDiscardableSharedMemoryManager() {
94 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
96 // TODO(reveman): Determine if this DCHECK can be enabled. crbug.com/430533
97 // DCHECK_EQ(heap_.GetSize(), heap_.GetSizeOfFreeLists());
99 MemoryUsageChanged(0, 0);
102 scoped_ptr
<base::DiscardableMemory
>
103 ChildDiscardableSharedMemoryManager::AllocateLockedDiscardableMemory(
105 base::AutoLock
lock(lock_
);
109 UMA_HISTOGRAM_CUSTOM_COUNTS("Memory.DiscardableAllocationSize",
110 size
/ 1024, // In KB
112 4 * 1024 * 1024, // 4 GB
115 // Round up to multiple of page size.
116 size_t pages
= (size
+ base::GetPageSize() - 1) / base::GetPageSize();
118 // Default allocation size in pages.
119 size_t allocation_pages
= kAllocationSize
/ base::GetPageSize();
122 // When searching the free lists, allow a slack between required size and
123 // free span size that is less or equal to kAllocationSize. This is to
124 // avoid segments larger then kAllocationSize unless they are a perfect
125 // fit. The result is that large allocations can be reused without reducing
126 // the ability to discard memory.
127 if (pages
< allocation_pages
)
128 slack
= allocation_pages
- pages
;
130 size_t heap_size_prior_to_releasing_purged_memory
= heap_
.GetSize();
132 // Search free lists for suitable span.
133 scoped_ptr
<DiscardableSharedMemoryHeap::Span
> free_span
=
134 heap_
.SearchFreeLists(pages
, slack
);
135 if (!free_span
.get())
138 // Attempt to lock |free_span|. Delete span and search free lists again
139 // if locking failed.
140 if (free_span
->shared_memory()->Lock(
141 free_span
->start() * base::GetPageSize() -
142 reinterpret_cast<size_t>(free_span
->shared_memory()->memory()),
143 free_span
->length() * base::GetPageSize()) ==
144 base::DiscardableSharedMemory::FAILED
) {
145 DCHECK(!free_span
->shared_memory()->IsMemoryResident());
146 // We have to release purged memory before |free_span| can be destroyed.
147 heap_
.ReleasePurgedMemory();
148 DCHECK(!free_span
->shared_memory());
152 // Memory usage is guaranteed to have changed after having removed
153 // at least one span from the free lists.
154 MemoryUsageChanged(heap_
.GetSize(), heap_
.GetSizeOfFreeLists());
156 return make_scoped_ptr(new DiscardableMemoryImpl(this, free_span
.Pass()));
159 // Release purged memory to free up the address space before we attempt to
160 // allocate more memory.
161 heap_
.ReleasePurgedMemory();
163 // Make sure crash keys are up to date in case allocation fails.
164 if (heap_
.GetSize() != heap_size_prior_to_releasing_purged_memory
)
165 MemoryUsageChanged(heap_
.GetSize(), heap_
.GetSizeOfFreeLists());
167 size_t pages_to_allocate
=
168 std::max(kAllocationSize
/ base::GetPageSize(), pages
);
169 size_t allocation_size_in_bytes
= pages_to_allocate
* base::GetPageSize();
171 DiscardableSharedMemoryId new_id
=
172 g_next_discardable_shared_memory_id
.GetNext();
174 // Ask parent process to allocate a new discardable shared memory segment.
175 scoped_ptr
<base::DiscardableSharedMemory
> shared_memory(
176 AllocateLockedDiscardableSharedMemory(allocation_size_in_bytes
, new_id
));
178 // Create span for allocated memory.
179 scoped_ptr
<DiscardableSharedMemoryHeap::Span
> new_span(heap_
.Grow(
180 shared_memory
.Pass(), allocation_size_in_bytes
, new_id
,
181 base::Bind(&SendDeletedDiscardableSharedMemoryMessage
, sender_
, new_id
)));
183 // Unlock and insert any left over memory into free lists.
184 if (pages
< pages_to_allocate
) {
185 scoped_ptr
<DiscardableSharedMemoryHeap::Span
> leftover
=
186 heap_
.Split(new_span
.get(), pages
);
187 leftover
->shared_memory()->Unlock(
188 leftover
->start() * base::GetPageSize() -
189 reinterpret_cast<size_t>(leftover
->shared_memory()->memory()),
190 leftover
->length() * base::GetPageSize());
191 heap_
.MergeIntoFreeLists(leftover
.Pass());
194 MemoryUsageChanged(heap_
.GetSize(), heap_
.GetSizeOfFreeLists());
196 return make_scoped_ptr(new DiscardableMemoryImpl(this, new_span
.Pass()));
199 bool ChildDiscardableSharedMemoryManager::OnMemoryDump(
200 const base::trace_event::MemoryDumpArgs
& args
,
201 base::trace_event::ProcessMemoryDump
* pmd
) {
202 base::AutoLock
lock(lock_
);
203 return heap_
.OnMemoryDump(pmd
);
206 void ChildDiscardableSharedMemoryManager::ReleaseFreeMemory() {
207 base::AutoLock
lock(lock_
);
209 size_t heap_size_prior_to_releasing_memory
= heap_
.GetSize();
211 // Release both purged and free memory.
212 heap_
.ReleasePurgedMemory();
213 heap_
.ReleaseFreeMemory();
215 if (heap_
.GetSize() != heap_size_prior_to_releasing_memory
)
216 MemoryUsageChanged(heap_
.GetSize(), heap_
.GetSizeOfFreeLists());
219 bool ChildDiscardableSharedMemoryManager::LockSpan(
220 DiscardableSharedMemoryHeap::Span
* span
) {
221 base::AutoLock
lock(lock_
);
223 if (!span
->shared_memory())
226 size_t offset
= span
->start() * base::GetPageSize() -
227 reinterpret_cast<size_t>(span
->shared_memory()->memory());
228 size_t length
= span
->length() * base::GetPageSize();
230 switch (span
->shared_memory()->Lock(offset
, length
)) {
231 case base::DiscardableSharedMemory::SUCCESS
:
233 case base::DiscardableSharedMemory::PURGED
:
234 span
->shared_memory()->Unlock(offset
, length
);
236 case base::DiscardableSharedMemory::FAILED
:
244 void ChildDiscardableSharedMemoryManager::UnlockSpan(
245 DiscardableSharedMemoryHeap::Span
* span
) {
246 base::AutoLock
lock(lock_
);
248 DCHECK(span
->shared_memory());
249 size_t offset
= span
->start() * base::GetPageSize() -
250 reinterpret_cast<size_t>(span
->shared_memory()->memory());
251 size_t length
= span
->length() * base::GetPageSize();
253 return span
->shared_memory()->Unlock(offset
, length
);
256 void ChildDiscardableSharedMemoryManager::ReleaseSpan(
257 scoped_ptr
<DiscardableSharedMemoryHeap::Span
> span
) {
258 base::AutoLock
lock(lock_
);
260 // Delete span instead of merging it into free lists if memory is gone.
261 if (!span
->shared_memory())
264 heap_
.MergeIntoFreeLists(span
.Pass());
266 // Bytes of free memory changed.
267 MemoryUsageChanged(heap_
.GetSize(), heap_
.GetSizeOfFreeLists());
270 base::trace_event::MemoryAllocatorDump
*
271 ChildDiscardableSharedMemoryManager::CreateMemoryAllocatorDump(
272 DiscardableSharedMemoryHeap::Span
* span
,
274 base::trace_event::ProcessMemoryDump
* pmd
) const {
275 base::AutoLock
lock(lock_
);
276 return heap_
.CreateMemoryAllocatorDump(span
, name
, pmd
);
279 scoped_ptr
<base::DiscardableSharedMemory
>
280 ChildDiscardableSharedMemoryManager::AllocateLockedDiscardableSharedMemory(
282 DiscardableSharedMemoryId id
) {
283 TRACE_EVENT2("renderer",
284 "ChildDiscardableSharedMemoryManager::"
285 "AllocateLockedDiscardableSharedMemory",
286 "size", size
, "id", id
);
288 base::SharedMemoryHandle handle
= base::SharedMemory::NULLHandle();
290 new ChildProcessHostMsg_SyncAllocateLockedDiscardableSharedMemory(
292 scoped_ptr
<base::DiscardableSharedMemory
> memory(
293 new base::DiscardableSharedMemory(handle
));
294 if (!memory
->Map(size
))
295 base::TerminateBecauseOutOfMemory(size
);
296 return memory
.Pass();
299 void ChildDiscardableSharedMemoryManager::MemoryUsageChanged(
300 size_t new_bytes_total
,
301 size_t new_bytes_free
) const {
302 static const char kDiscardableMemoryAllocatedKey
[] =
303 "discardable-memory-allocated";
304 base::debug::SetCrashKeyValue(kDiscardableMemoryAllocatedKey
,
305 base::Uint64ToString(new_bytes_total
));
307 static const char kDiscardableMemoryFreeKey
[] = "discardable-memory-free";
308 base::debug::SetCrashKeyValue(kDiscardableMemoryFreeKey
,
309 base::Uint64ToString(new_bytes_free
));
312 } // namespace content