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

Side by Side Diff: base/memory/shared_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: addressed review comments by Alexander 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/shared_memory_allocator.h"
6
7 #include <assert.h>
8
9 #include "base/atomicops.h"
10 #include "base/logging.h"
11
12 namespace {
13
14 // All allocations and data-structures must be aligned to this byte boundary.
15 // It shouldn't be less than 8 so that 64-bit values can be read in a single
16 // RAM bus access. 16 was chosen so that the block header would always fall
17 // within a single cache line.
18 const int32_t kAllocAlignment = 16;
19
20 // A constant (random) value placed in the shared metadata to identify
21 // an already initialized memory segment.
22 const int32_t kGlobalCookie = 0x408305DC;
23
24 // The current version of the metadata. If updates are made that change
25 // the metadata, the version number can be queried to operate in a backward-
26 // compatible manner until the memory segment is completely re-initalized.
27 const int32_t kGlobalVersion = 1;
28
29 // Constant values placed in the block headers to indicate its state.
30 const int32_t kBlockCookieFree = 0;
31 const int32_t kBlockCookieQueue = 1;
32 const int32_t kBlockCookieWasted = -1;
33 const int32_t kBlockCookieAllocated = 0xC8799269;
34
35 } // namespace
36
37 namespace base {
38
39 // The block-header is placed at the top of every allocation within the
40 // segment to describe the data that follows it.
41 struct SharedMemoryAllocator::BlockHeader {
42 int32_t size; // Number of bytes in this block, including header.
43 int32_t cookie; // Constant value indicating completed allocation.
44 int32_t type; // A number provided by caller indicating data type.
45 subtle::Atomic32 next; // Pointer to the next block when iterating
46 };
47
48 // The shared metadata exists once at the top of the memory segment to
49 // describe the state of the allocator to all processes.
50 struct SharedMemoryAllocator::SharedMetadata {
51 int32_t cookie; // Some value that indicates complete initialization.
52 int32_t size; // Total size of memory segment.
53 int32_t version; // Version code so upgrades don't break.
54 subtle::Atomic32 freeptr; // Offset to first free space in the segment.
55 char corrupted; // Flag indicating that corruption has been detected.
56 char full; // Flag indicating alloc failed because segment is full.
57 char flags[2]; // Future flags. (exact padding to int boundary)
58 int32_t reserved[2]; // Padding to ensure size is multiple of alignment.
59
60 // The "iterable" queue is an M&S Queue as described here, append-only:
61 // https://www.research.ibm.com/people/m/michael/podc-1996.pdf
62 subtle::Atomic32 tailptr; // Last block available for iteration.
63 BlockHeader queue; // Empty block for linked-list head/tail. (must be last)
64 };
65
66 // The "queue" block header is used to detect "last node" so that zero/null
67 // can be used to indicate that it hasn't been added at all. It is part of
68 // the SharedMetadata structure which itself is always located at offset zero.
69 // This can't be a constant because SharedMetadata is a private definition.
70 #define OFFSET_QUEUE offsetof(SharedMetadata, queue)
71 #define OFFSET_NULL 0 // the equivalest NULL value for an offset
72
73 SharedMemoryAllocator::SharedMemoryAllocator(void* base, int32_t size,
74 int32_t page_size)
75 : shared_meta_(static_cast<SharedMetadata*>(base)),
76 mem_base_(static_cast<char*>(base)),
77 mem_size_(size),
78 mem_page_(page_size ? page_size : size),
79 last_seen_(0),
80 corrupted_(false) {
81 static_assert(sizeof(BlockHeader) % kAllocAlignment == 0,
82 "BlockHeader is not a multiple of kAllocAlignment");
83 static_assert(sizeof(SharedMetadata) % kAllocAlignment == 0,
84 "SharedMetadata is not a multiple of kAllocAlignment");
85
86 DCHECK(base && reinterpret_cast<uintptr_t>(base) % kAllocAlignment == 0);
87 DCHECK(size >= 1 << 10 && size <= 1 << 20 && // 1 KiB <= size <= 1 MiB
88 size % kAllocAlignment == 0);
89 DCHECK(page_size >= 0 && (page_size == 0 || size % page_size == 0));
90
91 if (shared_meta_->cookie != kGlobalCookie) {
92 // This block is only executed when a completely new memory segment is
93 // being initialized. It's unshared and single-threaded...
chrisha 2015/10/30 14:36:46 Maybe it's worth having two separate constructors.
bcwhite 2015/10/30 17:27:39 Maybe. That seems a reasonable idea though it mig
94 const BlockHeader* first_block = reinterpret_cast<BlockHeader*>(
95 mem_base_ + sizeof(SharedMetadata));
96 if (shared_meta_->cookie != 0 ||
97 shared_meta_->size != 0 ||
98 shared_meta_->version != 0 ||
99 subtle::NoBarrier_Load(&shared_meta_->freeptr) != 0 ||
100 shared_meta_->corrupted != 0 ||
101 shared_meta_->full != 0 ||
102 shared_meta_->tailptr != 0 ||
103 shared_meta_->queue.cookie != 0 ||
104 subtle::NoBarrier_Load(&shared_meta_->queue.next) != 0 ||
105 first_block->size != 0 ||
106 first_block->cookie != 0 ||
107 first_block->type != 0 ||
108 first_block->next != 0) {
109 // ...or something malicious has been playing with the metadata.
110 SetCorrupted();
111 }
112
113 // This is still safe to do even if corruption has been detected.
114 shared_meta_->cookie = kGlobalCookie;
115 shared_meta_->size = size;
116 shared_meta_->version = kGlobalVersion;
117 subtle::NoBarrier_Store(&shared_meta_->freeptr, sizeof(SharedMetadata));
118
119 // Set up the queue of iterable allocations.
120 shared_meta_->queue.size = sizeof(BlockHeader);
121 shared_meta_->queue.cookie = kBlockCookieQueue;
122 subtle::NoBarrier_Store(&shared_meta_->queue.next, OFFSET_QUEUE);
123 subtle::NoBarrier_Store(&shared_meta_->tailptr, OFFSET_QUEUE);
124 }
125 }
126
127 SharedMemoryAllocator::~SharedMemoryAllocator() {
128 }
129
130 int32_t SharedMemoryAllocator::Allocate(int32_t size, int32_t type) {
131 if (size < 0) {
132 NOTREACHED();
133 return OFFSET_NULL;
134 }
135
136 // Round up the requested size, plus header, to the next allocation alignment.
137 size += sizeof(BlockHeader);
138 size = (size + (kAllocAlignment - 1)) & ~(kAllocAlignment - 1);
139 if (size > mem_page_)
140 return OFFSET_NULL;
141
142 // Allocation is lockless so we do all our caculation and then, if saving
143 // indicates a change has occurred since we started, scrap everything and
144 // start over.
145 for (;;) {
146 if (IsCorrupted())
147 return OFFSET_NULL;
148
149 int32_t freeptr = subtle::Acquire_Load(&shared_meta_->freeptr);
150 if (freeptr + size > mem_size_) {
151 shared_meta_->full = true;
152 return OFFSET_NULL;
153 }
154 BlockHeader* block = GetBlock(freeptr, 0, 0, true);
155 if (!block) {
156 SetCorrupted();
157 return OFFSET_NULL;
158 }
159
160 // An allocation cannot cross page boundaries. If it would, create a
161 // "wasted" block and begin again at the top of the next page.
162 int32_t page_free = mem_page_ - freeptr % mem_page_;
163 if (size > page_free) {
164 int32_t new_freeptr = freeptr + page_free;
165 if (subtle::Release_CompareAndSwap(
166 &shared_meta_->freeptr, freeptr, new_freeptr) == freeptr) {
167 block->size = page_free;
168 block->cookie = kBlockCookieWasted;
169 }
170 continue;
171 }
172
173 // Don't leave a slice at the end of a page too small for anything. This
174 // can result in an allocation up to two alignment-sizes greater than the
175 // minimum required by requested-size + header + alignment.
176 if (page_free - size < (int)(sizeof(BlockHeader) + kAllocAlignment))
177 size = page_free;
178
179 int32_t new_freeptr = freeptr + size;
180 if (new_freeptr > mem_size_) {
181 SetCorrupted();
182 return OFFSET_NULL;
183 }
184
185 if (subtle::Release_CompareAndSwap(
186 &shared_meta_->freeptr, freeptr, new_freeptr) != freeptr) {
187 // Another thread must have completed an allocation while we were working.
188 // Try again.
189 continue;
190 }
191
192 // Given that all memory was zeroed before ever being given to an instance
193 // of this class and given that we only allocate in a monotomic fashion
194 // going forward, it must be that the newly allocated block is completely
195 // full of zeros. If we find anything in the block header that is NOT a
196 // zero then something must have previously run amuck through memory,
197 // writing beyond the allocated space and into unallocated space.
198 if (block->size != 0 ||
199 block->cookie != kBlockCookieFree ||
200 block->type != 0 ||
201 subtle::NoBarrier_Load(&block->next) != 0) {
202 SetCorrupted();
203 return OFFSET_NULL;
204 }
205
206 block->size = size;
207 block->cookie = kBlockCookieAllocated;
208 block->type = type;
209 return freeptr;
210 }
211 }
212
213 void SharedMemoryAllocator::GetMemoryInfo(MemoryInfo* meminfo) {
214 int32_t remaining =
215 mem_size_ - subtle::NoBarrier_Load(&shared_meta_->freeptr);
216 meminfo->total = mem_size_;
217 meminfo->free = IsCorrupted() ? 0 : remaining - sizeof(BlockHeader);
218 }
219
220 void SharedMemoryAllocator::MakeIterable(int32_t offset) {
221 if (IsCorrupted())
222 return;
223 BlockHeader* block = GetBlock(offset, 0, 0, false);
224 if (!block) // invalid offset
225 return;
226 if (subtle::NoBarrier_Load(&block->next) != 0) // previously set iterable
227 return;
228 subtle::NoBarrier_Store(&block->next, OFFSET_QUEUE); // will be tail block
229
230 // Try to add this block to the tail of the queue. May take multiple tries.
231 int32_t tail;
232 for (;;) {
233 tail = subtle::Acquire_Load(&shared_meta_->tailptr);
234 block = GetBlock(tail, 0, 0, true);
235 if (!block) {
236 SetCorrupted();
237 return;
238 }
239 int32_t next = subtle::NoBarrier_Load(&block->next);
240
241 // Ensure that the tail pointer didn't change while reading next. Only
242 // the read of the tail pointer is atomic but we need to read both the
243 // tail pointer and the next pointer from it in an atomic fashion. The
244 // way to do this is to read both non-atomically and then verify after
245 // the second read that the first read is still valid/unchanged.
246 if (tail == subtle::Release_Load(&shared_meta_->tailptr)) {
247 // Check if the found block is truely the last in the queue (i.e. it
248 // points back to the "queue" node).
249 if (next == OFFSET_QUEUE) {
250 // Yes. Try to append the passed block after the current tail block.
251 if (subtle::Release_CompareAndSwap(
252 &block->next, OFFSET_QUEUE, offset) == OFFSET_QUEUE) {
253 // Success! The block is enqueued; need to update the tail pointer.
254 break;
255 }
256 } else {
257 // No. Another thread has stopped between the block-next update
258 // and the tail-pointer update. Try to update tailptr past the
259 // found block. That other thread may complete it first or it
260 // may have crashed. Be fail-safe.
261 subtle::Release_CompareAndSwap(&shared_meta_->tailptr, tail, next);
262 }
263 }
264 }
265
266 // Block has been enqueued. Now update the tail-pointer past it. This
267 // could fail if another thread has already completed the operation as
268 // part of being fail-safe.
269 subtle::Release_CompareAndSwap(&shared_meta_->tailptr, tail, offset);
270 }
271
272 int32_t SharedMemoryAllocator::GetFirstIterable(Iterator* state,
273 int32_t* type) {
274 state->last = OFFSET_QUEUE;
275 return GetNextIterable(state, type);
276 }
277
278 int32_t SharedMemoryAllocator::GetNextIterable(Iterator* state, int32_t* type) {
279 const BlockHeader* block = GetBlock(state->last, 0, 0, true);
280 if (!block) // invalid iterator state
281 return OFFSET_NULL;
282 int32_t next = subtle::NoBarrier_Load(&block->next);
283 block = GetBlock(next, 0, 0, false);
284 if (!block) // no next allocation in queue
285 return OFFSET_NULL;
286
287 state->last = next;
288 *type = block->type;
289 return next;
290 }
291
292 void SharedMemoryAllocator::SetCorrupted() {
293 LOG(ERROR) << "Corruption detected in shared-memory segment.";
294 corrupted_ = true;
295 shared_meta_->corrupted = true;
296 }
297
298 bool SharedMemoryAllocator::IsCorrupted() {
299 if (corrupted_ || shared_meta_->corrupted) {
300 SetCorrupted(); // Make sure all indicators are set.
301 return true;
302 }
303 return false;
304 }
305
306 bool SharedMemoryAllocator::IsFull() {
307 return shared_meta_->full != 0;
308 }
309
310 // Dereference a block |offset| and ensure that it's valid for the desired
311 // |type| and |size|. |special| indicates that we may try to access block
312 // headers not available to callers but still accessed by this module. By
313 // having internal dereferences go through this same function, the allocator
314 // is hardened against corruption.
315 SharedMemoryAllocator::BlockHeader* SharedMemoryAllocator::GetBlock(
316 int32_t offset, int32_t type, int32_t size, bool special) {
317 // Validation of parameters.
318 if (offset % kAllocAlignment != 0)
319 return nullptr;
320 if (offset < (int)(special ? OFFSET_QUEUE : sizeof(SharedMetadata)))
321 return nullptr;
322 size += sizeof(BlockHeader);
323 if (offset + size > mem_size_)
324 return nullptr;
325 int32_t freeptr = subtle::NoBarrier_Load(&shared_meta_->freeptr);
326 if (offset + size > freeptr + (int)(special ? sizeof(BlockHeader) : 0))
327 return nullptr;
328
329 // Validation of referenced block-header.
330 const BlockHeader* block = reinterpret_cast<BlockHeader*>(mem_base_ + offset);
331 if (offset != freeptr && block->size < size)
332 return nullptr;
333 if (!special && block->cookie != kBlockCookieAllocated)
334 return nullptr;
335 if (type != 0 && block->type != type)
336 return nullptr;
337
338 // Return pointer to block data.
339 return reinterpret_cast<BlockHeader*>(mem_base_ + offset);
340 }
341
342 void* SharedMemoryAllocator::GetBlockData(int32_t offset, int32_t type,
343 int32_t size, bool special) {
344 DCHECK(size > 0);
345 BlockHeader* block = GetBlock(offset, type, size, special);
346 if (!block)
347 return nullptr;
348 return reinterpret_cast<char*>(block) + sizeof(BlockHeader);
349 }
350
351 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698