Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/memory/discardable_shared_memory.h" | 5 #include "base/memory/discardable_shared_memory.h" |
| 6 | 6 |
| 7 #if defined(OS_POSIX) | 7 #if defined(OS_POSIX) |
| 8 #include <unistd.h> | 8 #include <unistd.h> |
| 9 #endif | 9 #endif |
| 10 | 10 |
| 11 #include <algorithm> | 11 #include <algorithm> |
| 12 | 12 |
| 13 #include "base/atomicops.h" | 13 #include "base/atomicops.h" |
| 14 #include "base/logging.h" | 14 #include "base/logging.h" |
| 15 #include "base/numerics/safe_math.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 | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
We need to deal with one of the implementations in
reveman
2014/12/17 16:34:05
We can introduce discardable_shared_memory_posix/a
Avi (use Gerrit)
2014/12/17 16:58:17
Acknowledged.
| |
| 16 | 21 |
| 17 namespace base { | 22 namespace base { |
| 18 namespace { | 23 namespace { |
| 19 | 24 |
| 20 // Use a machine-sized pointer as atomic type. It will use the Atomic32 or | 25 // Use a machine-sized pointer as atomic type. It will use the Atomic32 or |
| 21 // Atomic64 routines, depending on the architecture. | 26 // Atomic64 routines, depending on the architecture. |
| 22 typedef intptr_t AtomicType; | 27 typedef intptr_t AtomicType; |
| 23 typedef uintptr_t UAtomicType; | 28 typedef uintptr_t UAtomicType; |
| 24 | 29 |
| 25 // Template specialization for timestamp serialization/deserialization. This | 30 // Template specialization for timestamp serialization/deserialization. This |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 77 UAtomicType u; | 82 UAtomicType u; |
| 78 } value; | 83 } value; |
| 79 }; | 84 }; |
| 80 | 85 |
| 81 // Shared state is stored at offset 0 in shared memory segments. | 86 // Shared state is stored at offset 0 in shared memory segments. |
| 82 SharedState* SharedStateFromSharedMemory(const SharedMemory& shared_memory) { | 87 SharedState* SharedStateFromSharedMemory(const SharedMemory& shared_memory) { |
| 83 DCHECK(shared_memory.memory()); | 88 DCHECK(shared_memory.memory()); |
| 84 return static_cast<SharedState*>(shared_memory.memory()); | 89 return static_cast<SharedState*>(shared_memory.memory()); |
| 85 } | 90 } |
| 86 | 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); | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
DCHECK_EQ(0u, ...)
reveman
2014/12/17 16:34:06
Done. But out curiosity, why? I used to write code
Avi (use Gerrit)
2014/12/17 16:58:17
The printout, mostly. When it fails, it explicitly
reveman
2014/12/17 18:38:49
I don't think it does for DCHECK_*. It will just s
| |
| 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 | |
| 87 } // namespace | 103 } // namespace |
| 88 | 104 |
| 89 DiscardableSharedMemory::DiscardableSharedMemory() { | 105 DiscardableSharedMemory::DiscardableSharedMemory() |
| 106 : mapped_size_(0), locked_page_count_(0) { | |
| 90 } | 107 } |
| 91 | 108 |
| 92 DiscardableSharedMemory::DiscardableSharedMemory( | 109 DiscardableSharedMemory::DiscardableSharedMemory( |
| 93 SharedMemoryHandle shared_memory_handle) | 110 SharedMemoryHandle shared_memory_handle) |
| 94 : shared_memory_(shared_memory_handle, false) { | 111 : shared_memory_(shared_memory_handle, false), |
| 112 mapped_size_(0), | |
| 113 locked_page_count_(0) { | |
| 95 } | 114 } |
| 96 | 115 |
| 97 DiscardableSharedMemory::~DiscardableSharedMemory() { | 116 DiscardableSharedMemory::~DiscardableSharedMemory() { |
| 98 } | 117 } |
| 99 | 118 |
| 100 bool DiscardableSharedMemory::CreateAndMap(size_t size) { | 119 bool DiscardableSharedMemory::CreateAndMap(size_t size) { |
| 101 CheckedNumeric<size_t> checked_size = size; | 120 CheckedNumeric<size_t> checked_size = size; |
| 102 checked_size += sizeof(SharedState); | 121 checked_size += AlignToPageSize(sizeof(SharedState)); |
| 103 if (!checked_size.IsValid()) | 122 if (!checked_size.IsValid()) |
| 104 return false; | 123 return false; |
| 105 | 124 |
| 106 if (!shared_memory_.CreateAndMapAnonymous(checked_size.ValueOrDie())) | 125 if (!shared_memory_.CreateAndMapAnonymous(checked_size.ValueOrDie())) |
| 107 return false; | 126 return false; |
| 108 | 127 |
| 128 mapped_size_ = | |
| 129 shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState)); | |
| 130 | |
| 131 locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize(); | |
| 132 #ifndef NDEBUG | |
| 133 for (size_t page = 0; page < locked_page_count_; ++page) | |
| 134 locked_pages_.insert(page); | |
| 135 #endif | |
| 136 | |
| 109 DCHECK(last_known_usage_.is_null()); | 137 DCHECK(last_known_usage_.is_null()); |
| 110 SharedState new_state(SharedState::LOCKED, Time()); | 138 SharedState new_state(SharedState::LOCKED, Time()); |
| 111 subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_)->value.i, | 139 subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_)->value.i, |
| 112 new_state.value.i); | 140 new_state.value.i); |
| 113 return true; | 141 return true; |
| 114 } | 142 } |
| 115 | 143 |
| 116 bool DiscardableSharedMemory::Map(size_t size) { | 144 bool DiscardableSharedMemory::Map(size_t size) { |
| 117 return shared_memory_.Map(sizeof(SharedState) + 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 #ifndef NDEBUG | |
| 153 for (size_t page = 0; page < locked_page_count_; ++page) | |
| 154 locked_pages_.insert(page); | |
| 155 #endif | |
| 156 | |
| 157 return true; | |
| 118 } | 158 } |
| 119 | 159 |
| 120 bool DiscardableSharedMemory::Lock() { | 160 bool DiscardableSharedMemory::Lock(size_t offset, size_t length) { |
| 121 DCHECK(shared_memory_.memory()); | 161 DCHECK_EQ(AlignToPageSize(offset), offset); |
| 162 DCHECK_EQ(AlignToPageSize(length), length); | |
| 122 | 163 |
| 123 // Return false when instance has been purged or not initialized properly by | 164 // Return false when instance has been purged or not initialized properly by |
| 124 // checking if |last_known_usage_| is NULL. | 165 // checking if |last_known_usage_| is NULL. |
| 125 if (last_known_usage_.is_null()) | 166 if (last_known_usage_.is_null()) |
| 126 return false; | 167 return false; |
| 127 | 168 |
| 128 SharedState old_state(SharedState::UNLOCKED, last_known_usage_); | 169 DCHECK(shared_memory_.memory()); |
| 129 SharedState new_state(SharedState::LOCKED, Time()); | |
| 130 SharedState result(subtle::Acquire_CompareAndSwap( | |
| 131 &SharedStateFromSharedMemory(shared_memory_)->value.i, | |
| 132 old_state.value.i, | |
| 133 new_state.value.i)); | |
| 134 if (result.value.u == old_state.value.u) | |
| 135 return true; | |
| 136 | 170 |
| 137 // Update |last_known_usage_| in case the above CAS failed because of | 171 // We need to successfully acquire the platform independent lock before |
| 138 // an incorrect timestamp. | 172 // individual pages can be locked. |
| 139 last_known_usage_ = result.GetTimestamp(); | 173 if (!locked_page_count_) { |
|
Avi (use Gerrit)
2014/12/16 23:20:29
I have a bad gut feeling about this.
It used to b
reveman
2014/12/17 16:34:05
Multiple calls on the same instance? This class is
Avi (use Gerrit)
2014/12/17 16:58:17
If this isn't thread-safe, can you at least do a t
reveman
2014/12/17 18:38:48
Added a ThreadCollisionWarner.
| |
| 140 return false; | 174 SharedState old_state(SharedState::UNLOCKED, last_known_usage_); |
| 175 SharedState new_state(SharedState::LOCKED, Time()); | |
| 176 SharedState result(subtle::Acquire_CompareAndSwap( | |
| 177 &SharedStateFromSharedMemory(shared_memory_)->value.i, | |
| 178 old_state.value.i, | |
| 179 new_state.value.i)); | |
| 180 if (result.value.u != old_state.value.u) { | |
| 181 // Update |last_known_usage_| in case the above CAS failed because of | |
| 182 // an incorrect timestamp. | |
| 183 last_known_usage_ = result.GetTimestamp(); | |
| 184 return false; | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 // Zero for length means "everything onward". | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
What? Why is this not in the .h file? That's too i
reveman
2014/12/17 16:34:05
Sorry, I meant to also add a comment about this to
Avi (use Gerrit)
2014/12/17 16:58:17
Acknowledged.
| |
| 189 if (!length) | |
| 190 length = AlignToPageSize(mapped_size_) - offset; | |
| 191 | |
| 192 size_t start = offset / base::GetPageSize(); | |
| 193 size_t end = start + length / base::GetPageSize(); | |
| 194 DCHECK_LT(start, AlignToPageSize(mapped_size_)); | |
| 195 DCHECK_LE(end, AlignToPageSize(mapped_size_)); | |
| 196 | |
| 197 // Add pages to |locked_page_count_|. | |
| 198 // Note: Locking a page that is already locked is an error. | |
| 199 locked_page_count_ += end - start; | |
| 200 #ifndef NDEBUG | |
| 201 // Detect incorrect usage by keeping track of exactly what pages are locked. | |
| 202 for (size_t page = start; page < end; ++page) { | |
|
Avi (use Gerrit)
2014/12/16 23:20:29
auto page
reveman
2014/12/17 16:34:05
Done.
| |
| 203 DCHECK(locked_pages_.find(page) == locked_pages_.end()); | |
| 204 locked_pages_.insert(page); | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
A smidge faster to sub in the two lines:
auto res
reveman
2014/12/17 16:34:05
Done.
| |
| 205 } | |
| 206 DCHECK_EQ(locked_pages_.size(), locked_page_count_); | |
| 207 #endif | |
| 208 | |
| 209 #if defined(OS_ANDROID) | |
| 210 SharedMemoryHandle handle = shared_memory_.handle(); | |
| 211 DCHECK(SharedMemory::IsHandleValid(handle)); | |
| 212 if (ashmem_pin_region( | |
| 213 handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) { | |
| 214 return false; | |
| 215 } | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
Is there any way we can not deal with ashmem here
reveman
2014/12/17 16:34:05
I think it's better to include android/posix speci
| |
| 216 #endif | |
| 217 | |
| 218 return true; | |
| 141 } | 219 } |
| 142 | 220 |
| 143 void DiscardableSharedMemory::Unlock() { | 221 void DiscardableSharedMemory::Unlock(size_t offset, size_t length) { |
| 222 DCHECK_EQ(AlignToPageSize(offset), offset); | |
| 223 DCHECK_EQ(AlignToPageSize(length), length); | |
| 224 | |
| 225 // Zero for length means "everything onward". | |
| 226 if (!length) | |
| 227 length = AlignToPageSize(mapped_size_) - offset; | |
| 228 | |
| 144 DCHECK(shared_memory_.memory()); | 229 DCHECK(shared_memory_.memory()); |
| 145 | 230 |
| 231 #if defined(OS_ANDROID) | |
| 232 SharedMemoryHandle handle = shared_memory_.handle(); | |
| 233 DCHECK(SharedMemory::IsHandleValid(handle)); | |
| 234 if (ashmem_unpin_region( | |
| 235 handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) { | |
| 236 DPLOG(ERROR) << "ashmem_unpin_region() failed"; | |
| 237 } | |
| 238 #endif | |
| 239 | |
| 240 size_t start = offset / base::GetPageSize(); | |
| 241 size_t end = start + length / base::GetPageSize(); | |
| 242 DCHECK_LT(start, AlignToPageSize(mapped_size_)); | |
| 243 DCHECK_LE(end, AlignToPageSize(mapped_size_)); | |
| 244 | |
| 245 // Remove pages from |locked_page_count_|. | |
| 246 // Note: Unlocking a page that is not locked is an error. | |
| 247 DCHECK_GE(locked_page_count_, end - start); | |
| 248 locked_page_count_ -= end - start; | |
| 249 #ifndef NDEBUG | |
| 250 // Detect incorrect usage by keeping track of exactly what pages are locked. | |
| 251 for (uintptr_t page = start; page < end; ++page) { | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
auto page
Might as well use auto in for loop decl
reveman
2014/12/17 16:34:06
Done.
| |
| 252 DCHECK(locked_pages_.find(page) != locked_pages_.end()); | |
| 253 locked_pages_.erase(page); | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
A smidge faster to sub in the two lines:
auto era
reveman
2014/12/17 16:34:05
Done.
| |
| 254 } | |
| 255 DCHECK_EQ(locked_pages_.size(), locked_page_count_); | |
| 256 #endif | |
| 257 | |
| 258 // Early out and avoid releasing the platform independent lock if some pages | |
| 259 // are still locked. | |
| 260 if (locked_page_count_) | |
|
Avi (use Gerrit)
2014/12/16 23:20:30
Same freakout over concurrency. Unlock used to use
reveman
2014/12/17 16:34:06
Same reply. Instances of this class are not thread
| |
| 261 return; | |
| 262 | |
| 146 Time current_time = Now(); | 263 Time current_time = Now(); |
| 147 DCHECK(!current_time.is_null()); | 264 DCHECK(!current_time.is_null()); |
| 148 | 265 |
| 149 SharedState old_state(SharedState::LOCKED, Time()); | 266 SharedState old_state(SharedState::LOCKED, Time()); |
| 150 SharedState new_state(SharedState::UNLOCKED, current_time); | 267 SharedState new_state(SharedState::UNLOCKED, current_time); |
| 151 // Note: timestamp cannot be NULL as that is a unique value used when | 268 // Note: timestamp cannot be NULL as that is a unique value used when |
| 152 // locked or purged. | 269 // locked or purged. |
| 153 DCHECK(!new_state.GetTimestamp().is_null()); | 270 DCHECK(!new_state.GetTimestamp().is_null()); |
| 154 // Timestamps precision should at least be accurate to the second. | 271 // Timestamp precision should at least be accurate to the second. |
| 155 DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(), | 272 DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(), |
| 156 (current_time - Time::UnixEpoch()).InSeconds()); | 273 (current_time - Time::UnixEpoch()).InSeconds()); |
| 157 SharedState result(subtle::Release_CompareAndSwap( | 274 SharedState result(subtle::Release_CompareAndSwap( |
| 158 &SharedStateFromSharedMemory(shared_memory_)->value.i, | 275 &SharedStateFromSharedMemory(shared_memory_)->value.i, |
| 159 old_state.value.i, | 276 old_state.value.i, |
| 160 new_state.value.i)); | 277 new_state.value.i)); |
| 161 | 278 |
| 162 DCHECK_EQ(old_state.value.u, result.value.u); | 279 DCHECK_EQ(old_state.value.u, result.value.u); |
| 163 | 280 |
| 164 last_known_usage_ = current_time; | 281 last_known_usage_ = current_time; |
| 165 } | 282 } |
| 166 | 283 |
| 167 void* DiscardableSharedMemory::memory() const { | 284 void* DiscardableSharedMemory::memory() const { |
| 168 return SharedStateFromSharedMemory(shared_memory_) + 1; | 285 return reinterpret_cast<uint8*>(shared_memory_.memory()) + |
| 286 AlignToPageSize(sizeof(SharedState)); | |
| 169 } | 287 } |
| 170 | 288 |
| 171 bool DiscardableSharedMemory::Purge(Time current_time) { | 289 bool DiscardableSharedMemory::Purge(Time current_time) { |
| 172 // Early out if not mapped. This can happen if the segment was previously | 290 // Early out if not mapped. This can happen if the segment was previously |
| 173 // unmapped using a call to Close(). | 291 // unmapped using a call to Close(). |
| 174 if (!shared_memory_.memory()) | 292 if (!shared_memory_.memory()) |
| 175 return true; | 293 return true; |
| 176 | 294 |
| 177 SharedState old_state(SharedState::UNLOCKED, last_known_usage_); | 295 SharedState old_state(SharedState::UNLOCKED, last_known_usage_); |
| 178 SharedState new_state(SharedState::UNLOCKED, Time()); | 296 SharedState new_state(SharedState::UNLOCKED, Time()); |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 226 void DiscardableSharedMemory::Close() { | 344 void DiscardableSharedMemory::Close() { |
| 227 shared_memory_.Unmap(); | 345 shared_memory_.Unmap(); |
| 228 shared_memory_.Close(); | 346 shared_memory_.Close(); |
| 229 } | 347 } |
| 230 | 348 |
| 231 Time DiscardableSharedMemory::Now() const { | 349 Time DiscardableSharedMemory::Now() const { |
| 232 return Time::Now(); | 350 return Time::Now(); |
| 233 } | 351 } |
| 234 | 352 |
| 235 } // namespace base | 353 } // namespace base |
| OLD | NEW |