| 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_shared_memory.h" | |
| 6 | |
| 7 #if defined(OS_POSIX) | |
| 8 #include <unistd.h> | |
| 9 #endif | |
| 10 | |
| 11 #include <algorithm> | |
| 12 | |
| 13 #include "base/atomicops.h" | |
| 14 #include "base/logging.h" | |
| 15 #include "base/numerics/safe_math.h" | |
| 16 #include "base/process/process_metrics.h" | |
| 17 | |
| 18 #if defined(OS_ANDROID) | |
| 19 #include "third_party/ashmem/ashmem.h" | |
| 20 #endif | |
| 21 | |
| 22 namespace base { | |
| 23 namespace { | |
| 24 | |
| 25 // Use a machine-sized pointer as atomic type. It will use the Atomic32 or | |
| 26 // Atomic64 routines, depending on the architecture. | |
| 27 typedef intptr_t AtomicType; | |
| 28 typedef uintptr_t UAtomicType; | |
| 29 | |
| 30 // Template specialization for timestamp serialization/deserialization. This | |
| 31 // is used to serialize timestamps using Unix time on systems where AtomicType | |
| 32 // does not have enough precision to contain a timestamp in the standard | |
| 33 // serialized format. | |
| 34 template <int> | |
| 35 Time TimeFromWireFormat(int64 value); | |
| 36 template <int> | |
| 37 int64 TimeToWireFormat(Time time); | |
| 38 | |
| 39 // Serialize to Unix time when using 4-byte wire format. | |
| 40 // Note: 19 January 2038, this will cease to work. | |
| 41 template <> | |
| 42 Time ALLOW_UNUSED_TYPE TimeFromWireFormat<4>(int64 value) { | |
| 43 return value ? Time::UnixEpoch() + TimeDelta::FromSeconds(value) : Time(); | |
| 44 } | |
| 45 template <> | |
| 46 int64 ALLOW_UNUSED_TYPE TimeToWireFormat<4>(Time time) { | |
| 47 return time > Time::UnixEpoch() ? (time - Time::UnixEpoch()).InSeconds() : 0; | |
| 48 } | |
| 49 | |
| 50 // Standard serialization format when using 8-byte wire format. | |
| 51 template <> | |
| 52 Time ALLOW_UNUSED_TYPE TimeFromWireFormat<8>(int64 value) { | |
| 53 return Time::FromInternalValue(value); | |
| 54 } | |
| 55 template <> | |
| 56 int64 ALLOW_UNUSED_TYPE TimeToWireFormat<8>(Time time) { | |
| 57 return time.ToInternalValue(); | |
| 58 } | |
| 59 | |
| 60 struct SharedState { | |
| 61 enum LockState { UNLOCKED = 0, LOCKED = 1 }; | |
| 62 | |
| 63 explicit SharedState(AtomicType ivalue) { value.i = ivalue; } | |
| 64 SharedState(LockState lock_state, Time timestamp) { | |
| 65 int64 wire_timestamp = TimeToWireFormat<sizeof(AtomicType)>(timestamp); | |
| 66 DCHECK_GE(wire_timestamp, 0); | |
| 67 DCHECK_EQ(lock_state & ~1, 0); | |
| 68 value.u = (static_cast<UAtomicType>(wire_timestamp) << 1) | lock_state; | |
| 69 } | |
| 70 | |
| 71 LockState GetLockState() const { return static_cast<LockState>(value.u & 1); } | |
| 72 | |
| 73 Time GetTimestamp() const { | |
| 74 return TimeFromWireFormat<sizeof(AtomicType)>(value.u >> 1); | |
| 75 } | |
| 76 | |
| 77 // Bit 1: Lock state. Bit is set when locked. | |
| 78 // Bit 2..sizeof(AtomicType)*8: Usage timestamp. NULL time when locked or | |
| 79 // purged. | |
| 80 union { | |
| 81 AtomicType i; | |
| 82 UAtomicType u; | |
| 83 } value; | |
| 84 }; | |
| 85 | |
| 86 // Shared state is stored at offset 0 in shared memory segments. | |
| 87 SharedState* SharedStateFromSharedMemory(const SharedMemory& shared_memory) { | |
| 88 DCHECK(shared_memory.memory()); | |
| 89 return static_cast<SharedState*>(shared_memory.memory()); | |
| 90 } | |
| 91 | |
| 92 // Round up |size| to a multiple of alignment, which must be a power of two. | |
| 93 size_t Align(size_t alignment, size_t size) { | |
| 94 DCHECK_EQ(alignment & (alignment - 1), 0u); | |
| 95 return (size + alignment - 1) & ~(alignment - 1); | |
| 96 } | |
| 97 | |
| 98 // Round up |size| to a multiple of page size. | |
| 99 size_t AlignToPageSize(size_t size) { | |
| 100 return Align(base::GetPageSize(), size); | |
| 101 } | |
| 102 | |
| 103 } // namespace | |
| 104 | |
| 105 DiscardableSharedMemory::DiscardableSharedMemory() | |
| 106 : mapped_size_(0), locked_page_count_(0) { | |
| 107 } | |
| 108 | |
| 109 DiscardableSharedMemory::DiscardableSharedMemory( | |
| 110 SharedMemoryHandle shared_memory_handle) | |
| 111 : shared_memory_(shared_memory_handle, false), | |
| 112 mapped_size_(0), | |
| 113 locked_page_count_(0) { | |
| 114 } | |
| 115 | |
| 116 DiscardableSharedMemory::~DiscardableSharedMemory() { | |
| 117 } | |
| 118 | |
| 119 bool DiscardableSharedMemory::CreateAndMap(size_t size) { | |
| 120 CheckedNumeric<size_t> checked_size = size; | |
| 121 checked_size += AlignToPageSize(sizeof(SharedState)); | |
| 122 if (!checked_size.IsValid()) | |
| 123 return false; | |
| 124 | |
| 125 if (!shared_memory_.CreateAndMapAnonymous(checked_size.ValueOrDie())) | |
| 126 return false; | |
| 127 | |
| 128 mapped_size_ = | |
| 129 shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState)); | |
| 130 | |
| 131 locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize(); | |
| 132 #if DCHECK_IS_ON() | |
| 133 for (size_t page = 0; page < locked_page_count_; ++page) | |
| 134 locked_pages_.insert(page); | |
| 135 #endif | |
| 136 | |
| 137 DCHECK(last_known_usage_.is_null()); | |
| 138 SharedState new_state(SharedState::LOCKED, Time()); | |
| 139 subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_)->value.i, | |
| 140 new_state.value.i); | |
| 141 return true; | |
| 142 } | |
| 143 | |
| 144 bool DiscardableSharedMemory::Map(size_t size) { | |
| 145 if (!shared_memory_.Map(AlignToPageSize(sizeof(SharedState)) + size)) | |
| 146 return false; | |
| 147 | |
| 148 mapped_size_ = | |
| 149 shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState)); | |
| 150 | |
| 151 locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize(); | |
| 152 #if DCHECK_IS_ON() | |
| 153 for (size_t page = 0; page < locked_page_count_; ++page) | |
| 154 locked_pages_.insert(page); | |
| 155 #endif | |
| 156 | |
| 157 return true; | |
| 158 } | |
| 159 | |
| 160 bool DiscardableSharedMemory::Unmap() { | |
| 161 if (!shared_memory_.Unmap()) | |
| 162 return false; | |
| 163 | |
| 164 mapped_size_ = 0; | |
| 165 return true; | |
| 166 } | |
| 167 | |
| 168 DiscardableSharedMemory::LockResult DiscardableSharedMemory::Lock( | |
| 169 size_t offset, size_t length) { | |
| 170 DCHECK_EQ(AlignToPageSize(offset), offset); | |
| 171 DCHECK_EQ(AlignToPageSize(length), length); | |
| 172 | |
| 173 // Calls to this function must be synchronized properly. | |
| 174 DFAKE_SCOPED_LOCK(thread_collision_warner_); | |
| 175 | |
| 176 DCHECK(shared_memory_.memory()); | |
| 177 | |
| 178 // We need to successfully acquire the platform independent lock before | |
| 179 // individual pages can be locked. | |
| 180 if (!locked_page_count_) { | |
| 181 // Return false when instance has been purged or not initialized properly | |
| 182 // by checking if |last_known_usage_| is NULL. | |
| 183 if (last_known_usage_.is_null()) | |
| 184 return FAILED; | |
| 185 | |
| 186 SharedState old_state(SharedState::UNLOCKED, last_known_usage_); | |
| 187 SharedState new_state(SharedState::LOCKED, Time()); | |
| 188 SharedState result(subtle::Acquire_CompareAndSwap( | |
| 189 &SharedStateFromSharedMemory(shared_memory_)->value.i, | |
| 190 old_state.value.i, | |
| 191 new_state.value.i)); | |
| 192 if (result.value.u != old_state.value.u) { | |
| 193 // Update |last_known_usage_| in case the above CAS failed because of | |
| 194 // an incorrect timestamp. | |
| 195 last_known_usage_ = result.GetTimestamp(); | |
| 196 return FAILED; | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 // Zero for length means "everything onward". | |
| 201 if (!length) | |
| 202 length = AlignToPageSize(mapped_size_) - offset; | |
| 203 | |
| 204 size_t start = offset / base::GetPageSize(); | |
| 205 size_t end = start + length / base::GetPageSize(); | |
| 206 DCHECK_LT(start, end); | |
| 207 DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize()); | |
| 208 | |
| 209 // Add pages to |locked_page_count_|. | |
| 210 // Note: Locking a page that is already locked is an error. | |
| 211 locked_page_count_ += end - start; | |
| 212 #if DCHECK_IS_ON() | |
| 213 // Detect incorrect usage by keeping track of exactly what pages are locked. | |
| 214 for (auto page = start; page < end; ++page) { | |
| 215 auto result = locked_pages_.insert(page); | |
| 216 DCHECK(result.second); | |
| 217 } | |
| 218 DCHECK_EQ(locked_pages_.size(), locked_page_count_); | |
| 219 #endif | |
| 220 | |
| 221 #if defined(OS_ANDROID) | |
| 222 SharedMemoryHandle handle = shared_memory_.handle(); | |
| 223 if (SharedMemory::IsHandleValid(handle)) { | |
| 224 if (ashmem_pin_region( | |
| 225 handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) { | |
| 226 return PURGED; | |
| 227 } | |
| 228 } | |
| 229 #endif | |
| 230 | |
| 231 return SUCCESS; | |
| 232 } | |
| 233 | |
| 234 void DiscardableSharedMemory::Unlock(size_t offset, size_t length) { | |
| 235 DCHECK_EQ(AlignToPageSize(offset), offset); | |
| 236 DCHECK_EQ(AlignToPageSize(length), length); | |
| 237 | |
| 238 // Calls to this function must be synchronized properly. | |
| 239 DFAKE_SCOPED_LOCK(thread_collision_warner_); | |
| 240 | |
| 241 // Zero for length means "everything onward". | |
| 242 if (!length) | |
| 243 length = AlignToPageSize(mapped_size_) - offset; | |
| 244 | |
| 245 DCHECK(shared_memory_.memory()); | |
| 246 | |
| 247 #if defined(OS_ANDROID) | |
| 248 SharedMemoryHandle handle = shared_memory_.handle(); | |
| 249 if (SharedMemory::IsHandleValid(handle)) { | |
| 250 if (ashmem_unpin_region( | |
| 251 handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) { | |
| 252 DPLOG(ERROR) << "ashmem_unpin_region() failed"; | |
| 253 } | |
| 254 } | |
| 255 #endif | |
| 256 | |
| 257 size_t start = offset / base::GetPageSize(); | |
| 258 size_t end = start + length / base::GetPageSize(); | |
| 259 DCHECK_LT(start, end); | |
| 260 DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize()); | |
| 261 | |
| 262 // Remove pages from |locked_page_count_|. | |
| 263 // Note: Unlocking a page that is not locked is an error. | |
| 264 DCHECK_GE(locked_page_count_, end - start); | |
| 265 locked_page_count_ -= end - start; | |
| 266 #if DCHECK_IS_ON() | |
| 267 // Detect incorrect usage by keeping track of exactly what pages are locked. | |
| 268 for (auto page = start; page < end; ++page) { | |
| 269 auto erased_count = locked_pages_.erase(page); | |
| 270 DCHECK_EQ(1u, erased_count); | |
| 271 } | |
| 272 DCHECK_EQ(locked_pages_.size(), locked_page_count_); | |
| 273 #endif | |
| 274 | |
| 275 // Early out and avoid releasing the platform independent lock if some pages | |
| 276 // are still locked. | |
| 277 if (locked_page_count_) | |
| 278 return; | |
| 279 | |
| 280 Time current_time = Now(); | |
| 281 DCHECK(!current_time.is_null()); | |
| 282 | |
| 283 SharedState old_state(SharedState::LOCKED, Time()); | |
| 284 SharedState new_state(SharedState::UNLOCKED, current_time); | |
| 285 // Note: timestamp cannot be NULL as that is a unique value used when | |
| 286 // locked or purged. | |
| 287 DCHECK(!new_state.GetTimestamp().is_null()); | |
| 288 // Timestamp precision should at least be accurate to the second. | |
| 289 DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(), | |
| 290 (current_time - Time::UnixEpoch()).InSeconds()); | |
| 291 SharedState result(subtle::Release_CompareAndSwap( | |
| 292 &SharedStateFromSharedMemory(shared_memory_)->value.i, | |
| 293 old_state.value.i, | |
| 294 new_state.value.i)); | |
| 295 | |
| 296 DCHECK_EQ(old_state.value.u, result.value.u); | |
| 297 | |
| 298 last_known_usage_ = current_time; | |
| 299 } | |
| 300 | |
| 301 void* DiscardableSharedMemory::memory() const { | |
| 302 return reinterpret_cast<uint8*>(shared_memory_.memory()) + | |
| 303 AlignToPageSize(sizeof(SharedState)); | |
| 304 } | |
| 305 | |
| 306 bool DiscardableSharedMemory::Purge(Time current_time) { | |
| 307 // Calls to this function must be synchronized properly. | |
| 308 DFAKE_SCOPED_LOCK(thread_collision_warner_); | |
| 309 | |
| 310 // Early out if not mapped. This can happen if the segment was previously | |
| 311 // unmapped using a call to Close(). | |
| 312 if (!shared_memory_.memory()) | |
| 313 return true; | |
| 314 | |
| 315 SharedState old_state(SharedState::UNLOCKED, last_known_usage_); | |
| 316 SharedState new_state(SharedState::UNLOCKED, Time()); | |
| 317 SharedState result(subtle::Acquire_CompareAndSwap( | |
| 318 &SharedStateFromSharedMemory(shared_memory_)->value.i, | |
| 319 old_state.value.i, | |
| 320 new_state.value.i)); | |
| 321 | |
| 322 // Update |last_known_usage_| to |current_time| if the memory is locked. This | |
| 323 // allows the caller to determine if purging failed because last known usage | |
| 324 // was incorrect or memory was locked. In the second case, the caller should | |
| 325 // most likely wait for some amount of time before attempting to purge the | |
| 326 // the memory again. | |
| 327 if (result.value.u != old_state.value.u) { | |
| 328 last_known_usage_ = result.GetLockState() == SharedState::LOCKED | |
| 329 ? current_time | |
| 330 : result.GetTimestamp(); | |
| 331 return false; | |
| 332 } | |
| 333 | |
| 334 last_known_usage_ = Time(); | |
| 335 return true; | |
| 336 } | |
| 337 | |
| 338 bool DiscardableSharedMemory::IsMemoryResident() const { | |
| 339 DCHECK(shared_memory_.memory()); | |
| 340 | |
| 341 SharedState result(subtle::NoBarrier_Load( | |
| 342 &SharedStateFromSharedMemory(shared_memory_)->value.i)); | |
| 343 | |
| 344 return result.GetLockState() == SharedState::LOCKED || | |
| 345 !result.GetTimestamp().is_null(); | |
| 346 } | |
| 347 | |
| 348 void DiscardableSharedMemory::Close() { | |
| 349 shared_memory_.Close(); | |
| 350 } | |
| 351 | |
| 352 #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING) | |
| 353 void DiscardableSharedMemory::Shrink() { | |
| 354 #if defined(OS_POSIX) | |
| 355 SharedMemoryHandle handle = shared_memory_.handle(); | |
| 356 if (!SharedMemory::IsHandleValid(handle)) | |
| 357 return; | |
| 358 | |
| 359 // Truncate shared memory to size of SharedState. | |
| 360 if (HANDLE_EINTR(ftruncate(SharedMemory::GetFdFromSharedMemoryHandle(handle), | |
| 361 AlignToPageSize(sizeof(SharedState)))) != 0) { | |
| 362 DPLOG(ERROR) << "ftruncate() failed"; | |
| 363 return; | |
| 364 } | |
| 365 mapped_size_ = 0; | |
| 366 #else | |
| 367 NOTIMPLEMENTED(); | |
| 368 #endif | |
| 369 } | |
| 370 #endif | |
| 371 | |
| 372 Time DiscardableSharedMemory::Now() const { | |
| 373 return Time::Now(); | |
| 374 } | |
| 375 | |
| 376 } // namespace base | |
| OLD | NEW |