| Index: base/memory/discardable_shared_memory.cc
|
| diff --git a/base/memory/discardable_shared_memory.cc b/base/memory/discardable_shared_memory.cc
|
| index 851f1add97f0326bd1ce28bb43c228a5b5aafe77..eb9e55236218a4af9861753a8af93445aedc6ef4 100644
|
| --- a/base/memory/discardable_shared_memory.cc
|
| +++ b/base/memory/discardable_shared_memory.cc
|
| @@ -13,6 +13,11 @@
|
| #include "base/atomicops.h"
|
| #include "base/logging.h"
|
| #include "base/numerics/safe_math.h"
|
| +#include "base/process/process_metrics.h"
|
| +
|
| +#if defined(OS_ANDROID)
|
| +#include "third_party/ashmem/ashmem.h"
|
| +#endif
|
|
|
| namespace base {
|
| namespace {
|
| @@ -59,7 +64,7 @@ struct SharedState {
|
| SharedState(LockState lock_state, Time timestamp) {
|
| int64 wire_timestamp = TimeToWireFormat<sizeof(AtomicType)>(timestamp);
|
| DCHECK_GE(wire_timestamp, 0);
|
| - DCHECK((lock_state & ~1) == 0);
|
| + DCHECK_EQ(lock_state & ~1, 0);
|
| value.u = (static_cast<UAtomicType>(wire_timestamp) << 1) | lock_state;
|
| }
|
|
|
| @@ -84,14 +89,28 @@ SharedState* SharedStateFromSharedMemory(const SharedMemory& shared_memory) {
|
| return static_cast<SharedState*>(shared_memory.memory());
|
| }
|
|
|
| +// Round up |size| to a multiple of alignment, which must be a power of two.
|
| +size_t Align(size_t alignment, size_t size) {
|
| + DCHECK_EQ(alignment & (alignment - 1), 0u);
|
| + return (size + alignment - 1) & ~(alignment - 1);
|
| +}
|
| +
|
| +// Round up |size| to a multiple of page size.
|
| +size_t AlignToPageSize(size_t size) {
|
| + return Align(base::GetPageSize(), size);
|
| +}
|
| +
|
| } // namespace
|
|
|
| -DiscardableSharedMemory::DiscardableSharedMemory() {
|
| +DiscardableSharedMemory::DiscardableSharedMemory()
|
| + : mapped_size_(0), locked_page_count_(0) {
|
| }
|
|
|
| DiscardableSharedMemory::DiscardableSharedMemory(
|
| SharedMemoryHandle shared_memory_handle)
|
| - : shared_memory_(shared_memory_handle, false) {
|
| + : shared_memory_(shared_memory_handle, false),
|
| + mapped_size_(0),
|
| + locked_page_count_(0) {
|
| }
|
|
|
| DiscardableSharedMemory::~DiscardableSharedMemory() {
|
| @@ -99,13 +118,22 @@ DiscardableSharedMemory::~DiscardableSharedMemory() {
|
|
|
| bool DiscardableSharedMemory::CreateAndMap(size_t size) {
|
| CheckedNumeric<size_t> checked_size = size;
|
| - checked_size += sizeof(SharedState);
|
| + checked_size += AlignToPageSize(sizeof(SharedState));
|
| if (!checked_size.IsValid())
|
| return false;
|
|
|
| if (!shared_memory_.CreateAndMapAnonymous(checked_size.ValueOrDie()))
|
| return false;
|
|
|
| + mapped_size_ =
|
| + shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState));
|
| +
|
| + locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize();
|
| +#if DCHECK_IS_ON
|
| + for (size_t page = 0; page < locked_page_count_; ++page)
|
| + locked_pages_.insert(page);
|
| +#endif
|
| +
|
| DCHECK(last_known_usage_.is_null());
|
| SharedState new_state(SharedState::LOCKED, Time());
|
| subtle::Release_Store(&SharedStateFromSharedMemory(shared_memory_)->value.i,
|
| @@ -114,35 +142,130 @@ bool DiscardableSharedMemory::CreateAndMap(size_t size) {
|
| }
|
|
|
| bool DiscardableSharedMemory::Map(size_t size) {
|
| - return shared_memory_.Map(sizeof(SharedState) + size);
|
| + if (!shared_memory_.Map(AlignToPageSize(sizeof(SharedState)) + size))
|
| + return false;
|
| +
|
| + mapped_size_ =
|
| + shared_memory_.mapped_size() - AlignToPageSize(sizeof(SharedState));
|
| +
|
| + locked_page_count_ = AlignToPageSize(mapped_size_) / base::GetPageSize();
|
| +#if DCHECK_IS_ON
|
| + for (size_t page = 0; page < locked_page_count_; ++page)
|
| + locked_pages_.insert(page);
|
| +#endif
|
| +
|
| + return true;
|
| }
|
|
|
| -bool DiscardableSharedMemory::Lock() {
|
| - DCHECK(shared_memory_.memory());
|
| +bool DiscardableSharedMemory::Lock(size_t offset, size_t length) {
|
| + DCHECK_EQ(AlignToPageSize(offset), offset);
|
| + DCHECK_EQ(AlignToPageSize(length), length);
|
| +
|
| + // Calls to this function must synchronized properly.
|
| + DFAKE_SCOPED_LOCK(thread_collision_warner_);
|
|
|
| // Return false when instance has been purged or not initialized properly by
|
| // checking if |last_known_usage_| is NULL.
|
| if (last_known_usage_.is_null())
|
| return false;
|
|
|
| - SharedState old_state(SharedState::UNLOCKED, last_known_usage_);
|
| - SharedState new_state(SharedState::LOCKED, Time());
|
| - SharedState result(subtle::Acquire_CompareAndSwap(
|
| - &SharedStateFromSharedMemory(shared_memory_)->value.i,
|
| - old_state.value.i,
|
| - new_state.value.i));
|
| - if (result.value.u == old_state.value.u)
|
| - return true;
|
| + DCHECK(shared_memory_.memory());
|
| +
|
| + // We need to successfully acquire the platform independent lock before
|
| + // individual pages can be locked.
|
| + if (!locked_page_count_) {
|
| + SharedState old_state(SharedState::UNLOCKED, last_known_usage_);
|
| + SharedState new_state(SharedState::LOCKED, Time());
|
| + SharedState result(subtle::Acquire_CompareAndSwap(
|
| + &SharedStateFromSharedMemory(shared_memory_)->value.i,
|
| + old_state.value.i,
|
| + new_state.value.i));
|
| + if (result.value.u != old_state.value.u) {
|
| + // Update |last_known_usage_| in case the above CAS failed because of
|
| + // an incorrect timestamp.
|
| + last_known_usage_ = result.GetTimestamp();
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + // Zero for length means "everything onward".
|
| + if (!length)
|
| + length = AlignToPageSize(mapped_size_) - offset;
|
| +
|
| + size_t start = offset / base::GetPageSize();
|
| + size_t end = start + length / base::GetPageSize();
|
| + DCHECK_LT(start, end);
|
| + DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize());
|
| +
|
| + // Add pages to |locked_page_count_|.
|
| + // Note: Locking a page that is already locked is an error.
|
| + locked_page_count_ += end - start;
|
| +#if DCHECK_IS_ON
|
| + // Detect incorrect usage by keeping track of exactly what pages are locked.
|
| + for (auto page = start; page < end; ++page) {
|
| + auto result = locked_pages_.insert(page);
|
| + DCHECK(result.second);
|
| + }
|
| + DCHECK_EQ(locked_pages_.size(), locked_page_count_);
|
| +#endif
|
|
|
| - // Update |last_known_usage_| in case the above CAS failed because of
|
| - // an incorrect timestamp.
|
| - last_known_usage_ = result.GetTimestamp();
|
| - return false;
|
| +#if defined(OS_ANDROID)
|
| + SharedMemoryHandle handle = shared_memory_.handle();
|
| + DCHECK(SharedMemory::IsHandleValid(handle));
|
| + if (ashmem_pin_region(
|
| + handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) {
|
| + return false;
|
| + }
|
| +#endif
|
| +
|
| + return true;
|
| }
|
|
|
| -void DiscardableSharedMemory::Unlock() {
|
| +void DiscardableSharedMemory::Unlock(size_t offset, size_t length) {
|
| + DCHECK_EQ(AlignToPageSize(offset), offset);
|
| + DCHECK_EQ(AlignToPageSize(length), length);
|
| +
|
| + // Calls to this function must synchronized properly.
|
| + DFAKE_SCOPED_LOCK(thread_collision_warner_);
|
| +
|
| + // Zero for length means "everything onward".
|
| + if (!length)
|
| + length = AlignToPageSize(mapped_size_) - offset;
|
| +
|
| DCHECK(shared_memory_.memory());
|
|
|
| +#if defined(OS_ANDROID)
|
| + SharedMemoryHandle handle = shared_memory_.handle();
|
| + DCHECK(SharedMemory::IsHandleValid(handle));
|
| + if (ashmem_unpin_region(
|
| + handle.fd, AlignToPageSize(sizeof(SharedState)) + offset, length)) {
|
| + DPLOG(ERROR) << "ashmem_unpin_region() failed";
|
| + }
|
| +#endif
|
| +
|
| + size_t start = offset / base::GetPageSize();
|
| + size_t end = start + length / base::GetPageSize();
|
| + DCHECK_LT(start, end);
|
| + DCHECK_LE(end, AlignToPageSize(mapped_size_) / base::GetPageSize());
|
| +
|
| + // Remove pages from |locked_page_count_|.
|
| + // Note: Unlocking a page that is not locked is an error.
|
| + DCHECK_GE(locked_page_count_, end - start);
|
| + locked_page_count_ -= end - start;
|
| +#if DCHECK_IS_ON
|
| + // Detect incorrect usage by keeping track of exactly what pages are locked.
|
| + for (auto page = start; page < end; ++page) {
|
| + auto erased_count = locked_pages_.erase(page);
|
| + DCHECK_EQ(1u, erased_count);
|
| + }
|
| + DCHECK_EQ(locked_pages_.size(), locked_page_count_);
|
| +#endif
|
| +
|
| + // Early out and avoid releasing the platform independent lock if some pages
|
| + // are still locked.
|
| + if (locked_page_count_)
|
| + return;
|
| +
|
| Time current_time = Now();
|
| DCHECK(!current_time.is_null());
|
|
|
| @@ -151,7 +274,7 @@ void DiscardableSharedMemory::Unlock() {
|
| // Note: timestamp cannot be NULL as that is a unique value used when
|
| // locked or purged.
|
| DCHECK(!new_state.GetTimestamp().is_null());
|
| - // Timestamps precision should at least be accurate to the second.
|
| + // Timestamp precision should at least be accurate to the second.
|
| DCHECK_EQ((new_state.GetTimestamp() - Time::UnixEpoch()).InSeconds(),
|
| (current_time - Time::UnixEpoch()).InSeconds());
|
| SharedState result(subtle::Release_CompareAndSwap(
|
| @@ -165,10 +288,14 @@ void DiscardableSharedMemory::Unlock() {
|
| }
|
|
|
| void* DiscardableSharedMemory::memory() const {
|
| - return SharedStateFromSharedMemory(shared_memory_) + 1;
|
| + return reinterpret_cast<uint8*>(shared_memory_.memory()) +
|
| + AlignToPageSize(sizeof(SharedState));
|
| }
|
|
|
| bool DiscardableSharedMemory::Purge(Time current_time) {
|
| + // Calls to this function must synchronized properly.
|
| + DFAKE_SCOPED_LOCK(thread_collision_warner_);
|
| +
|
| // Early out if not mapped. This can happen if the segment was previously
|
| // unmapped using a call to Close().
|
| if (!shared_memory_.memory())
|
|
|