Merge "Purge linker block allocators before leaving linker" am: e930e10935
am: 14355943ed

Change-Id: Icee442e23f0720477f056d0cfda5d836325c7d3f
diff --git a/linker/linker.cpp b/linker/linker.cpp
index d0c740b..428dd25 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -536,6 +536,10 @@
     allocator_.free(ptr);
   }
 
+  static void purge() {
+    allocator_.purge();
+  }
+
  private:
   static LinkerBlockAllocator allocator_;
 };
@@ -553,6 +557,10 @@
   static void free(T* ptr) {
     SizeBasedAllocator<sizeof(T)>::free(ptr);
   }
+
+  static void purge() {
+    SizeBasedAllocator<sizeof(T)>::purge();
+  }
 };
 
 class LoadTask {
@@ -2074,6 +2082,8 @@
          ns == nullptr ? "(null)" : ns->get_name(),
          ns);
 
+  auto purge_guard = android::base::make_scope_guard([&]() { purge_unused_memory(); });
+
   auto failure_guard = android::base::make_scope_guard(
       [&]() { LD_LOG(kLogDlopen, "... dlopen failed: %s", linker_get_error_buffer()); });
 
@@ -4069,3 +4079,17 @@
   }
   return it->second;
 }
+
+void purge_unused_memory() {
+  // For now, we only purge the memory used by LoadTask because we know those
+  // are temporary objects.
+  //
+  // Purging other LinkerBlockAllocator hardly yields much because they hold
+  // information about namespaces and opened libraries, which are not freed
+  // when the control leaves the linker.
+  //
+  // Purging BionicAllocator may give us a few dirty pages back, but those pages
+  // would be already zeroed out, so they compress easily in ZRAM.  Therefore,
+  // it is not worth munmap()'ing those pages.
+  TypeBasedAllocator<LoadTask>::purge();
+}
diff --git a/linker/linker.h b/linker/linker.h
index 91d3ddf..964c266 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -186,3 +186,5 @@
 
 void increment_dso_handle_reference_counter(void* dso_handle);
 void decrement_dso_handle_reference_counter(void* dso_handle);
+
+void purge_unused_memory();
diff --git a/linker/linker_block_allocator.cpp b/linker/linker_block_allocator.cpp
index d72cad3..fdb4c85 100644
--- a/linker/linker_block_allocator.cpp
+++ b/linker/linker_block_allocator.cpp
@@ -55,7 +55,8 @@
   : block_size_(
       round_up(block_size < sizeof(FreeBlockInfo) ? sizeof(FreeBlockInfo) : block_size, 16)),
     page_list_(nullptr),
-    free_block_list_(nullptr)
+    free_block_list_(nullptr),
+    allocated_(0)
 {}
 
 void* LinkerBlockAllocator::alloc() {
@@ -76,6 +77,8 @@
 
   memset(block_info, 0, block_size_);
 
+  ++allocated_;
+
   return block_info;
 }
 
@@ -104,6 +107,8 @@
   block_info->num_free_blocks = 1;
 
   free_block_list_ = block_info;
+
+  --allocated_;
 }
 
 void LinkerBlockAllocator::protect_all(int prot) {
@@ -154,3 +159,18 @@
 
   abort();
 }
+
+void LinkerBlockAllocator::purge() {
+  if (allocated_) {
+    return;
+  }
+
+  LinkerBlockAllocatorPage* page = page_list_;
+  while (page) {
+    LinkerBlockAllocatorPage* next = page->next;
+    munmap(page, kAllocateSize);
+    page = next;
+  }
+  page_list_ = nullptr;
+  free_block_list_ = nullptr;
+}
diff --git a/linker/linker_block_allocator.h b/linker/linker_block_allocator.h
index 0c54b93..8ae4094 100644
--- a/linker/linker_block_allocator.h
+++ b/linker/linker_block_allocator.h
@@ -50,6 +50,9 @@
   void free(void* block);
   void protect_all(int prot);
 
+  // Purge all pages if all previously allocated blocks have been freed.
+  void purge();
+
  private:
   void create_new_page();
   LinkerBlockAllocatorPage* find_page(void* block);
@@ -57,6 +60,7 @@
   size_t block_size_;
   LinkerBlockAllocatorPage* page_list_;
   void* free_block_list_;
+  size_t allocated_;
 
   DISALLOW_COPY_AND_ASSIGN(LinkerBlockAllocator);
 };
diff --git a/linker/linker_main.cpp b/linker/linker_main.cpp
index b0c27dc..7486cd7 100644
--- a/linker/linker_main.cpp
+++ b/linker/linker_main.cpp
@@ -503,6 +503,10 @@
   fflush(stdout);
 #endif
 
+  // We are about to hand control over to the executable loaded.  We don't want
+  // to leave dirty pages behind unnecessarily.
+  purge_unused_memory();
+
   ElfW(Addr) entry = exe_info.entry_point;
   TRACE("[ Ready to execute \"%s\" @ %p ]", si->get_realpath(), reinterpret_cast<void*>(entry));
   return entry;