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 "base/memory/discardable_shared_memory.h"
13 #include "base/atomicops.h"
14 #include "base/bits.h"
15 #include "base/logging.h"
16 #include "base/numerics/safe_math.h"
17 #include "base/process/process_metrics.h"
19 #if defined(OS_ANDROID)
20 #include "third_party/ashmem/ashmem.h"
26 // Use a machine-sized pointer as atomic type. It will use the Atomic32 or
27 // Atomic64 routines, depending on the architecture.
28 typedef intptr_t AtomicType
;
29 typedef uintptr_t UAtomicType
;
31 // Template specialization for timestamp serialization/deserialization. This
32 // is used to serialize timestamps using Unix time on systems where AtomicType
33 // does not have enough precision to contain a timestamp in the standard
36 Time
TimeFromWireFormat(int64 value
);
38 int64
TimeToWireFormat(Time time
);
40 // Serialize to Unix time when using 4-byte wire format.
41 // Note: 19 January 2038, this will cease to work.
43 Time ALLOW_UNUSED_TYPE TimeFromWireFormat
<4>(int64 value
) {
44 return value
? Time::UnixEpoch() + TimeDelta::FromSeconds(value
) : Time();
47 int64 ALLOW_UNUSED_TYPE TimeToWireFormat
<4>(Time time
) {
48 return time
> Time::UnixEpoch() ? (time
- Time::UnixEpoch()).InSeconds() : 0;
51 // Standard serialization format when using 8-byte wire format.
53 Time ALLOW_UNUSED_TYPE TimeFromWireFormat
<8>(int64 value
) {
54 return Time::FromInternalValue(value
);
57 int64 ALLOW_UNUSED_TYPE TimeToWireFormat
<8>(Time time
) {
58 return time
.ToInternalValue();
62 enum LockState
{ UNLOCKED
= 0, LOCKED
= 1 };
64 explicit SharedState(AtomicType ivalue
) { value
.i
= ivalue
; }
65 SharedState(LockState lock_state
, Time timestamp
) {
66 int64 wire_timestamp
= TimeToWireFormat
<sizeof(AtomicType
)>(timestamp
);
67 DCHECK_GE(wire_timestamp
, 0);
68 DCHECK_EQ(lock_state
& ~1, 0);
69 value
.u
= (static_cast<UAtomicType
>(wire_timestamp
) << 1) | lock_state
;
72 LockState
GetLockState() const { return static_cast<LockState
>(value
.u
& 1); }
74 Time
GetTimestamp() const {
75 return TimeFromWireFormat
<sizeof(AtomicType
)>(value
.u
>> 1);
78 // Bit 1: Lock state. Bit is set when locked.
79 // Bit 2..sizeof(AtomicType)*8: Usage timestamp. NULL time when locked or
87 // Shared state is stored at offset 0 in shared memory segments.
88 SharedState
* SharedStateFromSharedMemory(const SharedMemory
& shared_memory
) {
89 DCHECK(shared_memory
.memory());
90 return static_cast<SharedState
*>(shared_memory
.memory());
93 // Round up |size| to a multiple of page size.
94 size_t AlignToPageSize(size_t size
) {
95 return bits::Align(size
, base::GetPageSize());
100 DiscardableSharedMemory::DiscardableSharedMemory()
101 : mapped_size_(0), locked_page_count_(0) {
104 DiscardableSharedMemory::DiscardableSharedMemory(
105 SharedMemoryHandle shared_memory_handle
)
106 : shared_memory_(shared_memory_handle
, false),
108 locked_page_count_(0) {
111 DiscardableSharedMemory::~DiscardableSharedMemory() {
114 bool DiscardableSharedMemory::CreateAndMap(size_t size
) {
115 CheckedNumeric
<size_t> checked_size
= size
;
116 checked_size
+= AlignToPageSize(sizeof(SharedState
));
117 if (!checked_size
.IsValid())
120 if (!shared_memory_
.CreateAndMapAnonymous(checked_size
.ValueOrDie()))
124 shared_memory_
.mapped_size() - AlignToPageSize(sizeof(SharedState
));
126 locked_page_count_
= AlignToPageSize(mapped_size_
) / base::GetPageSize();
128 for (size_t page
= 0; page
< locked_page_count_
; ++page
)
129 locked_pages_
.insert(page
);
132 DCHECK(last_known_usage_
.is_null());
133 SharedState
new_state(SharedState::LOCKED
, Time());
134 subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_
)->value
.i
,
139 bool DiscardableSharedMemory::Map(size_t size
) {
140 if (!shared_memory_
.Map(AlignToPageSize(sizeof(SharedState
)) + size
))
144 shared_memory_
.mapped_size() - AlignToPageSize(sizeof(SharedState
));
146 locked_page_count_
= AlignToPageSize(mapped_size_
) / base::GetPageSize();
148 for (size_t page
= 0; page
< locked_page_count_
; ++page
)
149 locked_pages_
.insert(page
);
155 bool DiscardableSharedMemory::Unmap() {
156 if (!shared_memory_
.Unmap())
163 DiscardableSharedMemory::LockResult
DiscardableSharedMemory::Lock(
164 size_t offset
, size_t length
) {
165 DCHECK_EQ(AlignToPageSize(offset
), offset
);
166 DCHECK_EQ(AlignToPageSize(length
), length
);
168 // Calls to this function must be synchronized properly.
169 DFAKE_SCOPED_LOCK(thread_collision_warner_
);
171 DCHECK(shared_memory_
.memory());
173 // We need to successfully acquire the platform independent lock before
174 // individual pages can be locked.
175 if (!locked_page_count_
) {
176 // Return false when instance has been purged or not initialized properly
177 // by checking if |last_known_usage_| is NULL.
178 if (last_known_usage_
.is_null())
181 SharedState
old_state(SharedState::UNLOCKED
, last_known_usage_
);
182 SharedState
new_state(SharedState::LOCKED
, Time());
183 SharedState
result(subtle::Acquire_CompareAndSwap(
184 &SharedStateFromSharedMemory(shared_memory_
)->value
.i
,
187 if (result
.value
.u
!= old_state
.value
.u
) {
188 // Update |last_known_usage_| in case the above CAS failed because of
189 // an incorrect timestamp.
190 last_known_usage_
= result
.GetTimestamp();
195 // Zero for length means "everything onward".
197 length
= AlignToPageSize(mapped_size_
) - offset
;
199 size_t start
= offset
/ base::GetPageSize();
200 size_t end
= start
+ length
/ base::GetPageSize();
201 DCHECK_LT(start
, end
);
202 DCHECK_LE(end
, AlignToPageSize(mapped_size_
) / base::GetPageSize());
204 // Add pages to |locked_page_count_|.
205 // Note: Locking a page that is already locked is an error.
206 locked_page_count_
+= end
- start
;
208 // Detect incorrect usage by keeping track of exactly what pages are locked.
209 for (auto page
= start
; page
< end
; ++page
) {
210 auto result
= locked_pages_
.insert(page
);
211 DCHECK(result
.second
);
213 DCHECK_EQ(locked_pages_
.size(), locked_page_count_
);
216 #if defined(OS_ANDROID)
217 SharedMemoryHandle handle
= shared_memory_
.handle();
218 if (SharedMemory::IsHandleValid(handle
)) {
219 if (ashmem_pin_region(
220 handle
.fd
, AlignToPageSize(sizeof(SharedState
)) + offset
, length
)) {
229 void DiscardableSharedMemory::Unlock(size_t offset
, size_t length
) {
230 DCHECK_EQ(AlignToPageSize(offset
), offset
);
231 DCHECK_EQ(AlignToPageSize(length
), length
);
233 // Calls to this function must be synchronized properly.
234 DFAKE_SCOPED_LOCK(thread_collision_warner_
);
236 // Zero for length means "everything onward".
238 length
= AlignToPageSize(mapped_size_
) - offset
;
240 DCHECK(shared_memory_
.memory());
242 #if defined(OS_ANDROID)
243 SharedMemoryHandle handle
= shared_memory_
.handle();
244 if (SharedMemory::IsHandleValid(handle
)) {
245 if (ashmem_unpin_region(
246 handle
.fd
, AlignToPageSize(sizeof(SharedState
)) + offset
, length
)) {
247 DPLOG(ERROR
) << "ashmem_unpin_region() failed";
252 size_t start
= offset
/ base::GetPageSize();
253 size_t end
= start
+ length
/ base::GetPageSize();
254 DCHECK_LT(start
, end
);
255 DCHECK_LE(end
, AlignToPageSize(mapped_size_
) / base::GetPageSize());
257 // Remove pages from |locked_page_count_|.
258 // Note: Unlocking a page that is not locked is an error.
259 DCHECK_GE(locked_page_count_
, end
- start
);
260 locked_page_count_
-= end
- start
;
262 // Detect incorrect usage by keeping track of exactly what pages are locked.
263 for (auto page
= start
; page
< end
; ++page
) {
264 auto erased_count
= locked_pages_
.erase(page
);
265 DCHECK_EQ(1u, erased_count
);
267 DCHECK_EQ(locked_pages_
.size(), locked_page_count_
);
270 // Early out and avoid releasing the platform independent lock if some pages
272 if (locked_page_count_
)
275 Time current_time
= Now();
276 DCHECK(!current_time
.is_null());
278 SharedState
old_state(SharedState::LOCKED
, Time());
279 SharedState
new_state(SharedState::UNLOCKED
, current_time
);
280 // Note: timestamp cannot be NULL as that is a unique value used when
282 DCHECK(!new_state
.GetTimestamp().is_null());
283 // Timestamp precision should at least be accurate to the second.
284 DCHECK_EQ((new_state
.GetTimestamp() - Time::UnixEpoch()).InSeconds(),
285 (current_time
- Time::UnixEpoch()).InSeconds());
286 SharedState
result(subtle::Release_CompareAndSwap(
287 &SharedStateFromSharedMemory(shared_memory_
)->value
.i
,
291 DCHECK_EQ(old_state
.value
.u
, result
.value
.u
);
293 last_known_usage_
= current_time
;
296 void* DiscardableSharedMemory::memory() const {
297 return reinterpret_cast<uint8
*>(shared_memory_
.memory()) +
298 AlignToPageSize(sizeof(SharedState
));
301 bool DiscardableSharedMemory::Purge(Time current_time
) {
302 // Calls to this function must be synchronized properly.
303 DFAKE_SCOPED_LOCK(thread_collision_warner_
);
305 // Early out if not mapped. This can happen if the segment was previously
306 // unmapped using a call to Close().
307 if (!shared_memory_
.memory())
310 SharedState
old_state(SharedState::UNLOCKED
, last_known_usage_
);
311 SharedState
new_state(SharedState::UNLOCKED
, Time());
312 SharedState
result(subtle::Acquire_CompareAndSwap(
313 &SharedStateFromSharedMemory(shared_memory_
)->value
.i
,
317 // Update |last_known_usage_| to |current_time| if the memory is locked. This
318 // allows the caller to determine if purging failed because last known usage
319 // was incorrect or memory was locked. In the second case, the caller should
320 // most likely wait for some amount of time before attempting to purge the
322 if (result
.value
.u
!= old_state
.value
.u
) {
323 last_known_usage_
= result
.GetLockState() == SharedState::LOCKED
325 : result
.GetTimestamp();
329 last_known_usage_
= Time();
333 bool DiscardableSharedMemory::IsMemoryResident() const {
334 DCHECK(shared_memory_
.memory());
336 SharedState
result(subtle::NoBarrier_Load(
337 &SharedStateFromSharedMemory(shared_memory_
)->value
.i
));
339 return result
.GetLockState() == SharedState::LOCKED
||
340 !result
.GetTimestamp().is_null();
343 void DiscardableSharedMemory::Close() {
344 shared_memory_
.Close();
347 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING)
348 void DiscardableSharedMemory::Shrink() {
349 #if defined(OS_POSIX)
350 SharedMemoryHandle handle
= shared_memory_
.handle();
351 if (!SharedMemory::IsHandleValid(handle
))
354 // Truncate shared memory to size of SharedState.
355 if (HANDLE_EINTR(ftruncate(SharedMemory::GetFdFromSharedMemoryHandle(handle
),
356 AlignToPageSize(sizeof(SharedState
)))) != 0) {
357 DPLOG(ERROR
) << "ftruncate() failed";
367 Time
DiscardableSharedMemory::Now() const {