Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(10)

Side by Side Diff: base/memory/persistent_memory_allocator.cc

Issue 1410213004: Create "persistent memory allocator" for persisting and sharing objects. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: use 'volatile' for shared memory; use std::atomic for member flag Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015 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/persistent_memory_allocator.h"
6
7 #include <assert.h>
8 #include <algorithm>
9
10 #include "base/logging.h"
11 #include "base/metrics/histogram_macros.h"
12
13 // All integer constants in this file are signed because Atomic32 is signed
14 // and keeping all others consistent with this avoids a lot of unnecessary
15 // casting to avoid signed/unsigned operations just to avoid compiler errors.
16 // This means an occasonal cast of a constant from sizeof() to "int" but
17 // is far simpler than the alternative. Only the external interface uses
18 // size_t for simplicity to the caller.
19
20 namespace {
21
22 // Required range of memory segment sizes. It has to fit in a signed 32-bit
23 // number and should be a power of 2 in order to accomodate almost any page
24 // size.
25 const int32_t kSegmentMinSize = 1 << 10; // 1 KiB
26 const int32_t kSegmentMaxSize = 1 << 30; // 1 GiB
27
28 // All allocations and data-structures must be aligned to this byte boundary.
29 // Alignment as large as the physical bus between CPU and RAM is _required_
30 // for some architectures, is simply more efficient on other CPUs, and
31 // generally a Good Idea(tm) for all platforms as it reduces/eliminates the
32 // chance that a type will span cache lines. Alignment mustn't be less
33 // than 8 to ensure proper alignment for all types. The rest is a balance
34 // between reducing spans across multiple cache lines and wasted space spent
35 // padding out allocations. An alignment of 16 would ensure that the block
36 // header structure always sits in a single cache line. An average of about
37 // 1/2 this value will be wasted with every allocation.
38 const int32_t kAllocAlignment = 8;
39
40 // A constant (random) value placed in the shared metadata to identify
41 // an already initialized memory segment.
42 const int32_t kGlobalCookie = 0x408305DC;
43
44 // The current version of the metadata. If updates are made that change
45 // the metadata, the version number can be queried to operate in a backward-
46 // compatible manner until the memory segment is completely re-initalized.
47 const int32_t kGlobalVersion = 1;
48
49 // Constant values placed in the block headers to indicate its state.
50 const int32_t kBlockCookieFree = 0;
51 const int32_t kBlockCookieQueue = 1;
52 const int32_t kBlockCookieWasted = -1;
53 const int32_t kBlockCookieAllocated = 0xC8799269;
54
55 // TODO(bcwhite): When acceptable, consider moving flags to std::atomic<char>
56 // types rather than combined bitfield.
57
58 // Flags stored in the flags_ field of the SharedMetaData structure below.
59 enum : int32_t {
60 kFlagCorrupt = 1 << 0,
61 kFlagFull = 1 << 1
62 };
63
64 bool CheckFlag(volatile base::subtle::Atomic32* flags, int flag) {
65 base::subtle::Atomic32 loaded_flags = base::subtle::Acquire_Load(flags);
66 return (loaded_flags & flag) != 0;
67 }
68
69 void SetFlag(volatile base::subtle::Atomic32* flags, int flag) {
70 for (;;) {
71 base::subtle::Atomic32 loaded_flags = base::subtle::Acquire_Load(flags);
72 base::subtle::Atomic32 new_flags =
73 (loaded_flags & ~flag) | flag;
74 if (base::subtle::Release_CompareAndSwap(
75 flags, loaded_flags, new_flags) == loaded_flags) {
76 break;
77 }
78 }
79 }
80
81 } // namespace
82
83 namespace base {
84
85 // The block-header is placed at the top of every allocation within the
86 // segment to describe the data that follows it.
87 struct PersistentMemoryAllocator::BlockHeader {
88 int32_t size; // Number of bytes in this block, including header.
89 int32_t cookie; // Constant value indicating completed allocation.
90 uint32_t type_id; // A number provided by caller indicating data type.
91 subtle::Atomic32 next; // Pointer to the next block when iterating.
92 };
93
94 // The shared metadata exists once at the top of the memory segment to
95 // describe the state of the allocator to all processes.
96 struct PersistentMemoryAllocator::SharedMetadata {
97 int32_t cookie; // Some value that indicates complete initialization.
98 int32_t size; // Total size of memory segment.
99 int32_t page_size; // Paging size within memory segment.
100 int32_t version; // Version code so upgrades don't break.
101 subtle::Atomic32 freeptr; // Offset/ref to first free space in the segment.
102 subtle::Atomic32 flags; // Bitfield of information flags.
103 int32_t name; // Reference to stored name string.
104
105 // The "iterable" queue is an M&S Queue as described here, append-only:
106 // https://www.research.ibm.com/people/m/michael/podc-1996.pdf
107 subtle::Atomic32 tailptr; // Last block available for iteration.
108 BlockHeader queue; // Empty block for linked-list head/tail. (must be last)
109 };
110
111 // The "queue" block header is used to detect "last node" so that zero/null
112 // can be used to indicate that it hasn't been added at all. It is part of
113 // the SharedMetadata structure which itself is always located at offset zero.
114 const PersistentMemoryAllocator::Reference
115 PersistentMemoryAllocator::kReferenceQueue =
116 offsetof(SharedMetadata, queue);
117 const PersistentMemoryAllocator::Reference
118 PersistentMemoryAllocator::kReferenceNull = 0;
119
120 PersistentMemoryAllocator::PersistentMemoryAllocator(void* base,
121 size_t size,
122 size_t page_size,
123 const std::string& name)
124 : mem_base_(static_cast<char*>(base)),
125 mem_size_(static_cast<int32_t>(size)),
126 mem_page_(static_cast<int32_t>((page_size ? page_size : size))),
127 corrupt_(0),
128 allocs_histogram_(nullptr),
129 used_histogram_(nullptr) {
130 static_assert(sizeof(BlockHeader) % kAllocAlignment == 0,
131 "BlockHeader is not a multiple of kAllocAlignment");
132 static_assert(sizeof(SharedMetadata) % kAllocAlignment == 0,
133 "SharedMetadata is not a multiple of kAllocAlignment");
134
135 CHECK(base && reinterpret_cast<uintptr_t>(base) % kAllocAlignment == 0);
136 CHECK(size >= kSegmentMinSize && size <= kSegmentMaxSize &&
137 size % kAllocAlignment == 0);
138 CHECK(page_size == 0 || size % page_size == 0);
139
140 if (shared_meta()->cookie != kGlobalCookie) {
141 // This block is only executed when a completely new memory segment is
142 // being initialized. It's unshared and single-threaded...
143 volatile BlockHeader* const first_block =
144 reinterpret_cast<volatile BlockHeader*>(mem_base_ +
145 sizeof(SharedMetadata));
146 if (shared_meta()->cookie != 0 ||
147 shared_meta()->size != 0 ||
148 shared_meta()->version != 0 ||
149 subtle::NoBarrier_Load(&shared_meta()->freeptr) != 0 ||
150 subtle::NoBarrier_Load(&shared_meta()->flags) != 0 ||
151 shared_meta()->name != 0 ||
152 shared_meta()->tailptr != 0 ||
153 shared_meta()->queue.cookie != 0 ||
154 subtle::NoBarrier_Load(&shared_meta()->queue.next) != 0 ||
155 first_block->size != 0 ||
156 first_block->cookie != 0 ||
157 first_block->type_id != 0 ||
158 first_block->next != 0) {
159 // ...or something malicious has been playing with the metadata.
160 NOTREACHED();
161 SetCorrupt();
162 }
163
164 // This is still safe to do even if corruption has been detected.
165 shared_meta()->cookie = kGlobalCookie;
166 shared_meta()->size = mem_size_;
167 shared_meta()->page_size = mem_page_;
168 shared_meta()->version = kGlobalVersion;
169 subtle::NoBarrier_Store(&shared_meta()->freeptr, sizeof(SharedMetadata));
170
171 // Set up the queue of iterable allocations.
172 shared_meta()->queue.size = sizeof(BlockHeader);
173 shared_meta()->queue.cookie = kBlockCookieQueue;
174 subtle::NoBarrier_Store(&shared_meta()->queue.next, kReferenceQueue);
175 subtle::NoBarrier_Store(&shared_meta()->tailptr, kReferenceQueue);
176
177 // Allocate space for the name so other processes can learn it.
178 if (!name.empty()) {
179 const size_t name_length = name.length() + 1;
180 shared_meta()->name = Allocate(name_length, 0);
181 char* name_cstr = GetAsObject<char>(shared_meta()->name, 0);
182 if (name_cstr)
183 strcpy(name_cstr, name.c_str());
184 }
185 } else {
186 // The allocator is attaching to a previously initialized segment of
187 // memory. Make sure the embedded data matches what has been passed.
188 if (shared_meta()->size != mem_size_ ||
189 shared_meta()->page_size != mem_page_) {
190 NOTREACHED();
191 SetCorrupt();
192 }
193 }
194
195 // Metrics are created here so there is no recursion from Allocate
196 // trying to update a histogram that needs to be created and in turn
197 // calls Allocate again.
198 // Some metrics are only active on the primary owner.
199 if (!name.empty()) {
200 used_histogram_ = Histogram::FactoryGet(
201 name + ".UsedKiB", 1, 256 << 10, 100, HistogramBase::kNoFlags);
202 }
203
204 // Other metrics are active on all users of the memory segment.
205 Reference name_ref = shared_meta()->name;
206 char* name_cstr = GetAsObject<char>(name_ref, 0);
207 if (name_cstr) {
208 size_t name_length = GetAllocSize(name_ref);
209 while (name_length > 0 && name_cstr[name_length - 1] != '\0')
210 --name_length;
211 if (name_length > 0) {
212 std::string shared_name(name_cstr, name_length);
213 allocs_histogram_ = Histogram::FactoryGet(
214 shared_name + ".Allocs", 1, 10000, 50, HistogramBase::kNoFlags);
215 }
216 }
217 }
218
219 PersistentMemoryAllocator::~PersistentMemoryAllocator() {
220 // It's strictly forbidden to do any memory access here in case there is
221 // some issue with the underlying memory segment. The "Local" allocator
222 // makes use of this to allow deletion of the segment on the heap from
223 // within its destructor.
224 }
225
226 size_t PersistentMemoryAllocator::GetAllocSize(Reference ref) {
227 volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
228 if (!block)
229 return 0;
230 int32_t size = block->size;
231 // Header was verified by GetBlock() but a malicious actor could change
232 // the value between there and here. Check it again.
233 if (size <= (int)sizeof(BlockHeader) || ref + size >= mem_size_)
234 return 0;
235 return static_cast<size_t>(size - sizeof(BlockHeader));
236 }
237
238 uint32_t PersistentMemoryAllocator::GetType(Reference ref) {
239 volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
240 if (!block)
241 return 0;
242 return block->type_id;
243 }
244
245 void PersistentMemoryAllocator::SetType(Reference ref, uint32_t type_id) {
246 volatile BlockHeader* const block = GetBlock(ref, 0, 0, false, false);
247 if (!block)
248 return;
249 block->type_id = type_id;
250 }
251
252 int32_t PersistentMemoryAllocator::Allocate(size_t usize, uint32_t type_id) {
253 // Validate usize to ensure it won't overflow when used as signed 32-bit.
254 if (usize > (size_t)kSegmentMaxSize - sizeof(BlockHeader)) {
255 NOTREACHED();
256 return kReferenceNull;
257 }
258
259 // Round up the requested size, plus header, to the next allocation alignment.
260 int32_t size = static_cast<int32_t>(usize + sizeof(BlockHeader));
261 size = (size + (kAllocAlignment - 1)) & ~(kAllocAlignment - 1);
262 if (size <= (int)sizeof(BlockHeader) || size > mem_page_) {
263 NOTREACHED();
264 return kReferenceNull;
265 }
266
267 // Allocation is lockless so we do all our caculation and then, if saving
268 // indicates a change has occurred since we started, scrap everything and
269 // start over.
270 for (;;) {
271 if (IsCorrupt())
272 return kReferenceNull;
273
274 // Get the current start of unallocated memory. Other threads may
275 // update this at any time and cause us to retry these operations.
276 const int32_t freeptr = subtle::NoBarrier_Load(&shared_meta()->freeptr);
277 if (freeptr + size > mem_size_) {
278 SetFlag(&shared_meta()->flags, kFlagFull);
279 return kReferenceNull;
280 }
281
282 // Get pointer to the "free" block. It doesn't even have a header; pass
283 // -sizeof(header) so accouting for that will yield an expected size of
284 // zero which is what will be stored at that location. If something
285 // has been allocated since the load of freeptr above, it is still safe
286 // as nothing will be written to that location until after the CAS below.
287 volatile BlockHeader* const block = GetBlock(freeptr, 0, 0, false, true);
288 if (!block) {
289 SetCorrupt();
290 return kReferenceNull;
291 }
292
293 // An allocation cannot cross page boundaries. If it would, create a
294 // "wasted" block and begin again at the top of the next page. This
295 // area could just be left empty but we fill in the block header just
296 // for completeness sake.
297 const int32_t page_free = mem_page_ - freeptr % mem_page_;
298 if (size > page_free) {
299 if (page_free <= (int)sizeof(BlockHeader)) {
300 SetCorrupt();
301 return kReferenceNull;
302 }
303 const int32_t new_freeptr = freeptr + page_free;
304 if (subtle::NoBarrier_CompareAndSwap(
305 &shared_meta()->freeptr, freeptr, new_freeptr) == freeptr) {
306 block->size = page_free;
307 block->cookie = kBlockCookieWasted;
308 }
309 continue;
310 }
311
312 // Don't leave a slice at the end of a page too small for anything. This
313 // can result in an allocation up to two alignment-sizes greater than the
314 // minimum required by requested-size + header + alignment.
315 if (page_free - size < (int)(sizeof(BlockHeader) + kAllocAlignment))
316 size = page_free;
317
318 const int32_t new_freeptr = freeptr + size;
319 if (new_freeptr > mem_size_) {
320 SetCorrupt();
321 return kReferenceNull;
322 }
323
324 if (subtle::NoBarrier_CompareAndSwap(
325 &shared_meta()->freeptr, freeptr, new_freeptr) != freeptr) {
326 // Another thread must have completed an allocation while we were working.
327 // Try again.
328 continue;
329 }
330
331 // Record this allocation in usage stats (if active). This is safe
332 // to call at this point because the allocation is complete.
333 if (allocs_histogram_)
334 allocs_histogram_->Add(static_cast<HistogramBase::Sample>(usize));
335
336 // Given that all memory was zeroed before ever being given to an instance
337 // of this class and given that we only allocate in a monotomic fashion
338 // going forward, it must be that the newly allocated block is completely
339 // full of zeros. If we find anything in the block header that is NOT a
340 // zero then something must have previously run amuck through memory,
341 // writing beyond the allocated space and into unallocated space.
342 if (block->size != 0 ||
343 block->cookie != kBlockCookieFree ||
344 block->type_id != 0 ||
345 subtle::NoBarrier_Load(&block->next) != 0) {
346 SetCorrupt();
347 return kReferenceNull;
348 }
349
350 block->size = size;
351 block->cookie = kBlockCookieAllocated;
352 block->type_id = type_id;
353 return freeptr;
354 }
355 }
356
357 void PersistentMemoryAllocator::GetMemoryInfo(MemoryInfo* meminfo) {
358 int32_t remaining =
359 mem_size_ - subtle::NoBarrier_Load(&shared_meta()->freeptr);
360 meminfo->total = mem_size_;
361 meminfo->free = IsCorrupt() ? 0 : remaining - sizeof(BlockHeader);
362 }
363
364 void PersistentMemoryAllocator::MakeIterable(Reference ref) {
365 if (IsCorrupt())
366 return;
367 volatile BlockHeader* block = GetBlock(ref, 0, 0, false, false);
368 if (!block) // invalid reference
369 return;
370 if (subtle::Acquire_Load(&block->next) != 0) // previously set iterable
371 return;
372 subtle::Release_Store(&block->next, kReferenceQueue); // will be tail block
373
374 // Try to add this block to the tail of the queue. May take multiple tries.
375 int32_t tail;
376 for (;;) {
377 // Acquire the current tail-pointer released by previous call to this
378 // method and validate it.
379 tail = subtle::Acquire_Load(&shared_meta()->tailptr);
380 block = GetBlock(tail, 0, 0, true, false);
381 if (!block) {
382 SetCorrupt();
383 return;
384 }
385
386 // Try to insert the block at the tail of the queue. The tail node always
387 // has an existing value of kReferenceQueue; if that is not the value
388 // returned, another thread has acted in the meantime.
389 int32_t next = subtle::Release_CompareAndSwap(
390 &block->next, kReferenceQueue, ref);
391 if (next == kReferenceQueue) {
392 // Update the tail pointer to the new offset. If the "else" clause did
393 // not exist, then this could be a simple Release_Store to set the new
394 // value but because it does, it's possible that other threads could add
395 // one or more nodes at the tail before reaching this point. We don't
396 // have to check the return value because it either operates correctly
397 // or the exact same operation has already been done (by the "else"
398 // clause).
399 subtle::Release_CompareAndSwap(&shared_meta()->tailptr, tail, ref);
400 return;
401 } else {
402 // In the unlikely case that a thread crashed or was killed between the
403 // update of "next" and the update of "tailptr", it is necessary to
404 // perform the operation that would have been done. There's no explicit
405 // check for crash/kill which means that this operation may also happen
406 // even when the other thread is in perfect working order which is what
407 // necessitates the CompareAndSwap above.
408 subtle::Release_CompareAndSwap(&shared_meta()->tailptr, tail, next);
409 }
410 }
411 }
412
413 void PersistentMemoryAllocator::CreateIterator(Iterator* state) {
414 state->last = kReferenceQueue;
415 state->niter = 0;
416 }
417
418 int32_t PersistentMemoryAllocator::GetNextIterable(Iterator* state,
419 uint32_t* type_id) {
420 volatile BlockHeader* block = GetBlock(state->last, 0, 0, true, false);
421 if (!block) // invalid iterator state
422 return kReferenceNull;
423
424 // The compiler and CPU can freely reorder all memory accesses on which
425 // there are no dependencies. It could, for example, move the load of
426 // "freeptr" above this point because there are no explicit dependencies
427 // between it and "next". If it did, however, then another block could
428 // be queued after that but before the following load meaning there is
429 // one more queued block than the future "detect loop by having more
430 // blocks that could fit before freeptr" will allow.
431 //
432 // By "acquiring" the "next" value here, it's synchronized to the enqueue
433 // of the node which in turn is synchronized to the allocation (which sets
434 // freeptr). Thus, the scenario above cannot happen.
435 int32_t next = subtle::Acquire_Load(&block->next);
436 block = GetBlock(next, 0, 0, false, false);
437 if (!block) // no next allocation in queue
438 return kReferenceNull;
439
440 // Memory corruption could cause a loop in the list. We need to detect
441 // that so as to not cause an infinite loop in the caller. We do this
442 // simply by making sure we don't iterate more than the absolute maximum
443 // number of allocations that could have been made. Callers are likely
444 // to loop multiple times before it is detected but at least it stops.
445 int32_t freeptr = std::min(subtle::Acquire_Load(&shared_meta()->freeptr),
446 mem_size_);
447 if (state->niter > freeptr / (sizeof(BlockHeader) + kAllocAlignment)) {
448 SetCorrupt();
449 return kReferenceNull;
450 }
451
452 state->last = next;
453 state->niter++;
454 *type_id = block->type_id;
455
456 return next;
457 }
458
459 // The "corrupted" state is held both locally and globally (shared). The
460 // shared flag can't be trusted since a malicious actor could overwrite it.
461 // The local version is immune to foreign actors. Thus, if seen shared,
462 // copy it locally and, once known, always restore it globally.
463 void PersistentMemoryAllocator::SetCorrupt() {
464 CHECK(corrupt_.is_lock_free());
JF 2015/11/20 20:43:31 It would be useful to explain why this is here (mu
bcwhite 2015/11/23 16:48:22 Done.
465 LOG(ERROR) << "Corruption detected in shared-memory segment.";
466 corrupt_.store(true);
467 SetFlag(&shared_meta()->flags, kFlagCorrupt);
468 }
469
470 bool PersistentMemoryAllocator::IsCorrupt() {
471 if (corrupt_.load() || CheckFlag(&shared_meta()->flags, kFlagCorrupt)) {
472 SetCorrupt(); // Make sure all indicators are set.
473 return true;
474 }
475 return false;
476 }
477
478 bool PersistentMemoryAllocator::IsFull() {
479 return CheckFlag(&shared_meta()->flags, kFlagFull);
480 }
481
482 // Dereference a block |ref| and ensure that it's valid for the desired
483 // |type_id| and |size|. |special| indicates that we may try to access block
484 // headers not available to callers but still accessed by this module. By
485 // having internal dereferences go through this same function, the allocator
486 // is hardened against corruption.
487 volatile PersistentMemoryAllocator::BlockHeader*
488 PersistentMemoryAllocator::GetBlock(Reference ref, uint32_t type_id,
489 int32_t size, bool queue_ok, bool free_ok) {
490 // Validation of parameters.
491 if (ref % kAllocAlignment != 0)
492 return nullptr;
493 if (ref < (int)(queue_ok ? kReferenceQueue : sizeof(SharedMetadata)))
494 return nullptr;
495 size += sizeof(BlockHeader);
496 if (ref + size > mem_size_)
497 return nullptr;
498
499 // Validation of referenced block-header.
500 if (!free_ok) {
501 int32_t freeptr = subtle::NoBarrier_Load(&shared_meta()->freeptr);
502 if (ref + size > freeptr)
503 return nullptr;
504 volatile BlockHeader* const block =
505 reinterpret_cast<volatile BlockHeader*>(mem_base_ + ref);
506 if (block->size < size)
507 return nullptr;
508 if (ref != kReferenceQueue && block->cookie != kBlockCookieAllocated)
509 return nullptr;
510 if (type_id != 0 && block->type_id != type_id)
511 return nullptr;
512 }
513
514 // Return pointer to block data.
515 return reinterpret_cast<volatile BlockHeader*>(mem_base_ + ref);
516 }
517
518 volatile void* PersistentMemoryAllocator::GetBlockData(Reference ref,
519 uint32_t type_id,
520 int32_t size) {
521 DCHECK(size > 0);
522 volatile BlockHeader* block = GetBlock(ref, type_id, size, false, false);
523 if (!block)
524 return nullptr;
525 return reinterpret_cast<volatile char*>(block) + sizeof(BlockHeader);
526 }
527
528 void PersistentMemoryAllocator::UpdateStaticHistograms() {
529 if (used_histogram_) {
530 MemoryInfo meminfo;
531 GetMemoryInfo(&meminfo);
532 HistogramBase::Sample usedkb = static_cast<HistogramBase::Sample>(
533 (meminfo.total - meminfo.free) >> 10);
534 used_histogram_->Add(usedkb);
535 }
536 }
537
538 //----- LocalPersistentMemoryAllocator -----------------------------------------
539
540 LocalPersistentMemoryAllocator::LocalPersistentMemoryAllocator(
541 size_t size,
542 const std::string& name)
543 : PersistentMemoryAllocator(memset(new char[size], 0, size),
544 size, 0, name) {}
545
546 LocalPersistentMemoryAllocator::~LocalPersistentMemoryAllocator() {
547 delete mem_base_;
548 }
549
550 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698