Home | History | Annotate | Download | only in memory
      1 // Copyright (c) 2013 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_memory.h"
      6 
      7 #include <sys/mman.h>
      8 #include <unistd.h>
      9 
     10 #include "base/lazy_instance.h"
     11 #include "base/logging.h"
     12 #include "base/posix/eintr_wrapper.h"
     13 #include "base/synchronization/lock.h"
     14 #include "third_party/ashmem/ashmem.h"
     15 
     16 namespace {
     17 
     18 base::LazyInstance<base::Lock>::Leaky g_discardable_memory_lock =
     19     LAZY_INSTANCE_INITIALIZER;
     20 
     21 // Total number of discardable memory in the process.
     22 int g_num_discardable_memory = 0;
     23 
     24 // Upper limit on the number of discardable memory to avoid hitting file
     25 // descriptor limit.
     26 const int kDiscardableMemoryNumLimit = 128;
     27 
     28 }
     29 
     30 namespace base {
     31 
     32 // static
     33 bool DiscardableMemory::Supported() {
     34   return true;
     35 }
     36 
     37 DiscardableMemory::~DiscardableMemory() {
     38   if (is_locked_)
     39     Unlock();
     40   // If fd_ is smaller than 0, initialization must have failed and
     41   // g_num_discardable_memory is not incremented by the caller.
     42   if (fd_ < 0)
     43     return;
     44   HANDLE_EINTR(close(fd_));
     45   fd_ = -1;
     46   ReleaseFileDescriptor();
     47 }
     48 
     49 bool DiscardableMemory::ReserveFileDescriptor() {
     50   base::AutoLock lock(g_discardable_memory_lock.Get());
     51   if (g_num_discardable_memory < kDiscardableMemoryNumLimit) {
     52     ++g_num_discardable_memory;
     53     return true;
     54   }
     55   return false;
     56 }
     57 
     58 void DiscardableMemory::ReleaseFileDescriptor() {
     59   base::AutoLock lock(g_discardable_memory_lock.Get());
     60   --g_num_discardable_memory;
     61   DCHECK_LE(0, g_num_discardable_memory);
     62 }
     63 
     64 bool DiscardableMemory::InitializeAndLock(size_t size) {
     65   // When this function returns true, fd_ should be larger or equal than 0
     66   // and g_num_discardable_memory is incremented by 1. Otherwise, fd_
     67   // is less than 0 and g_num_discardable_memory is not incremented by
     68   // the caller.
     69   DCHECK_EQ(fd_, -1);
     70   DCHECK(!memory_);
     71   if (!ReserveFileDescriptor())
     72     return false;
     73 
     74   size_ = size;
     75   fd_ = ashmem_create_region("", size);
     76 
     77   if (fd_ < 0) {
     78     DLOG(ERROR) << "ashmem_create_region() failed";
     79     ReleaseFileDescriptor();
     80     return false;
     81   }
     82 
     83   int err = ashmem_set_prot_region(fd_, PROT_READ | PROT_WRITE);
     84   if (err < 0) {
     85     DLOG(ERROR) << "Error " << err << " when setting protection of ashmem";
     86     HANDLE_EINTR(close(fd_));
     87     fd_ = -1;
     88     ReleaseFileDescriptor();
     89     return false;
     90   }
     91 
     92   if (!Map()) {
     93     // Close the file descriptor in case of any initialization errors.
     94     HANDLE_EINTR(close(fd_));
     95     fd_ = -1;
     96     ReleaseFileDescriptor();
     97     return false;
     98   }
     99 
    100   is_locked_ = true;
    101   return true;
    102 }
    103 
    104 LockDiscardableMemoryStatus DiscardableMemory::Lock() {
    105   DCHECK_NE(fd_, -1);
    106   DCHECK(!is_locked_);
    107 
    108   bool purged = false;
    109   if (ashmem_pin_region(fd_, 0, 0) == ASHMEM_WAS_PURGED)
    110     purged = true;
    111 
    112   if (!Map())
    113     return DISCARDABLE_MEMORY_FAILED;
    114 
    115   is_locked_ = true;
    116   return purged ? DISCARDABLE_MEMORY_PURGED : DISCARDABLE_MEMORY_SUCCESS;
    117 }
    118 
    119 void DiscardableMemory::Unlock() {
    120   DCHECK_GE(fd_, 0);
    121   DCHECK(is_locked_);
    122 
    123   Unmap();
    124   if (ashmem_unpin_region(fd_, 0, 0))
    125     DLOG(ERROR) << "Failed to unpin memory.";
    126   is_locked_ = false;
    127 }
    128 
    129 bool DiscardableMemory::Map() {
    130   DCHECK(!memory_);
    131   // There is a problem using MAP_PRIVATE here. As we are constantly calling
    132   // Lock() and Unlock(), data could get lost if they are not written to the
    133   // underlying file when Unlock() gets called.
    134   memory_ = mmap(NULL, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
    135   if (memory_ == (void*)-1) {
    136     DPLOG(ERROR) << "Failed to map memory.";
    137     memory_ = NULL;
    138     if (ashmem_unpin_region(fd_, 0, 0))
    139       DLOG(ERROR) << "Failed to unpin memory.";
    140     return false;
    141   }
    142   return true;
    143 }
    144 
    145 void DiscardableMemory::Unmap() {
    146   DCHECK(memory_);
    147 
    148   if (-1 == munmap(memory_, size_))
    149     DPLOG(ERROR) << "Failed to unmap memory.";
    150 
    151   memory_ = NULL;
    152 }
    153 
    154 // static
    155 bool DiscardableMemory::PurgeForTestingSupported() {
    156   return false;
    157 }
    158 
    159 // static
    160 void DiscardableMemory::PurgeForTesting() {
    161   NOTIMPLEMENTED();
    162 }
    163 
    164 }  // namespace base
    165