Index: base/android/library_loader/library_prefetcher.cc |
diff --git a/base/android/library_loader/library_prefetcher.cc b/base/android/library_loader/library_prefetcher.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..84038a6bbafa58f387a374bf7aef10ae7e6c24b7 |
--- /dev/null |
+++ b/base/android/library_loader/library_prefetcher.cc |
@@ -0,0 +1,167 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/android/library_loader/library_prefetcher.h" |
+ |
+#include <stdlib.h> |
+#include <sys/resource.h> |
+#include <unistd.h> |
+#include <utility> |
+#include <vector> |
+ |
+namespace base { |
+namespace android { |
+ |
+FileLineIterator::FileLineIterator(const base::FilePath& path) { |
+ file_ = base::OpenFile(path, "r"); |
+} |
+ |
+FileLineIterator::~FileLineIterator() { |
+ if (file_) { |
+ base::CloseFile(file_); |
+ } |
+} |
+ |
+std::string FileLineIterator::NextLine() { |
+ if (!file_) { |
+ return std::string(); |
+ } |
+ char* line = fgets(line_buffer_, kMaxLineLength - 1, file_); |
pasko
2015/03/24 17:10:36
why so low level and complex with FILE* and fgets
Benoit L
2015/03/25 10:36:18
I would prefer to avoid an unbounded memory alloca
|
+ return line ? std::string(line) : std::string(); |
+} |
+ |
+ProcMapsIterator::ProcMapsIterator(FileLineIterator* line_iterator) |
+ : line_iterator_(line_iterator) { |
+} |
+ |
+bool ProcMapsIterator::Next(uint64_t* start, |
+ uint64_t* end, |
+ std::string* flags, |
+ uint64_t* offset, |
+ std::string* device, |
+ int64_t* inode, |
+ std::string* filename) { |
rmcilroy
2015/03/24 15:18:26
nit - this might be neater wrapping up the argumen
Benoit L
2015/03/25 10:36:19
Done.
|
+ std::string line = line_iterator_->NextLine(); |
+ if (line.size() == 0) { |
+ return false; |
+ } |
+ // Example: |
+ // b6e62000-b6e64000 r-xp 00000000 b3:15 1204 /system/lib/libstdc++.so |
+ // start-end flags offset device inode [filename] |
+ std::string line_str(line); |
+ std::vector<std::string> pieces; |
+ base::SplitStringAlongWhitespace(line_str, &pieces); |
+ size_t pieces_count = pieces.size(); |
+ // Filename is optional |
+ DCHECK((pieces_count == 5) || (pieces_count == 6)); |
rmcilroy
2015/03/24 15:18:26
Either do this as a CHECK or add an if to only to
Benoit L
2015/03/25 10:36:18
Done.
|
+ |
+ const char* start_end = pieces[0].c_str(); |
+ char* delimiter_ptr, *dummy; |
+ static_assert(sizeof(long long) == sizeof(int64_t), |
+ "long long is not int64_t"); // This way we can use strtoll(). |
pasko
2015/03/24 17:10:36
StringToInt64?
Benoit L
2015/03/25 10:36:19
Done.
|
+ *start = strtoull(start_end, &delimiter_ptr, 16); |
+ *end = strtoull(delimiter_ptr + 1, &dummy, 16); |
rmcilroy
2015/03/24 15:18:26
nit - I would prefer you just used base::SplitStri
Benoit L
2015/03/25 10:36:18
Done.
|
+ *flags = pieces[1]; |
+ *offset = strtoull(pieces[2].c_str(), &dummy, 16); |
+ *device = pieces[3]; |
+ *inode = strtoll(pieces[4].c_str(), &dummy, 10); |
+ if (pieces_count == 5) { |
+ *filename = std::string(); |
+ } else { |
+ *filename = pieces[5]; |
+ } |
+ return true; |
+} |
+ |
+namespace { |
+ |
+// Android defines the background priority to this value since at least 2009 |
+// (see Process.java). |
+const int kBackgroundPriority = 10; |
+// Valid for all the Android architectures. |
+const size_t kPageSize = 4096; |
+// We may load directly from the APK. |
+const int kSuffixesToMatchCount = 2; |
+const char* kSuffixesToMatch[] = {"libchrome.so", "base.apk"}; |
pasko
2015/03/24 17:10:36
why base.apk? is the chrome apk always renamed to
Benoit L
2015/03/25 10:36:19
According to the android source code (see PackageI
|
+ |
+bool IsReadableAndPrivate(const char* flags) { |
+ return flags && flags[0] == 'r' && flags[3] == 'p'; |
rmcilroy
2015/03/24 15:18:26
nit - add a check here that flags is long enough t
Benoit L
2015/03/25 10:36:18
Done.
|
+} |
+ |
+bool FilenameMatchesSuffix(const char* filename) { |
+ if (!filename) |
+ return false; |
+ const size_t filename_length = strlen(filename); |
+ for (int i = 0; i < kSuffixesToMatchCount; i++) { |
+ const size_t suffix_length = strlen(kSuffixesToMatch[i]); |
+ if (filename_length < suffix_length) |
+ continue; |
+ if (!strcmp(kSuffixesToMatch[i], |
+ filename + filename_length - suffix_length)) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+// For each range, reads a byte per page to force it into the page cache. |
+void Prefetch(const std::vector<std::pair<uint64_t, uint64_t>>& ranges) { |
+ for (const auto& range : ranges) { |
+ unsigned char* start_address = (unsigned char*) range.first; |
+ unsigned char* end_address = (unsigned char*) range.second; |
+ unsigned char dummy; |
+ for (unsigned char* addr = start_address; addr < end_address - sizeof(int); |
pasko
2015/03/24 17:10:36
s/ - sizeof(int)// ?
you can CHECK(!(start_addres
Benoit L
2015/03/25 10:36:18
Done.
|
+ addr += kPageSize) { |
+ // Volatile is required to prevent the compiler from eliminating this |
+ // loop. |
+ dummy ^= *((volatile unsigned char*) addr); |
+ } |
+ } |
+} |
+ |
+} // namespace |
+ |
+std::vector<std::pair<uint64_t, uint64_t>> FindRanges( |
+ FileLineIterator* file_line_iterator) { |
+ std::vector<std::pair<uint64_t, uint64_t>> ranges; |
+ ProcMapsIterator maps_iterator(file_line_iterator); |
+ uint64_t start, end, offset; |
+ int64_t inode; |
+ std::string flags, device, filename; |
+ |
+ while (maps_iterator.Next(&start, &end, &flags, &offset, &device, &inode, |
+ &filename)) { |
+ if (FilenameMatchesSuffix(filename.c_str()) && |
+ IsReadableAndPrivate(flags.c_str())) { |
+ ranges.push_back(std::make_pair(start, end)); |
pasko
2015/03/24 17:10:36
We could potentially have both libchrome.so and .a
Benoit L
2015/03/25 10:36:18
Well, we have several ranges in pretty much all ca
pasko
2015/03/25 12:40:53
Got it. As per offline discussion, on later system
rmcilroy
2015/03/25 15:12:18
It's due to packed relocations, but yes we will ne
|
+ } |
+ } |
+ return ranges; |
+} |
+ |
+// Forks a process prefetching the native library. This is done in a forked |
+// process for the following reasons: |
+// - Isolating the main process from mistakes in the parsing. If the parsing |
+// returns an incorrect address, only the forked process will crash. |
rmcilroy
2015/03/24 15:18:26
The parsing is done on the main process though.
Benoit L
2015/03/25 10:36:18
Acknowledged.
|
+// - Not inflating the memory used by the main process uselessly, which could |
+// increase its likelihood to be killed. |
+// - Have the pages clean and not referenced, to make them easy for the kernel |
+// to evict, minimizing disruption caused by this prefetching. |
rmcilroy
2015/03/24 15:18:26
Does forking actually helps this - the pages shoul
Benoit L
2015/03/25 10:36:18
You are right, removed this comment.
Done.
|
+// The forked process has background priority and, since it is not declared to |
+// the android runtime, can be killed at any time, which is not an issue here. |
+void ForkAndPrefetchNativeLibrary() { |
+ // Looking for ranges is done before the fork, to avoid syscalls and/or |
+ // memory allocations in the forked process, that can be problematic. |
+ FileLineIterator file_line_iterator(base::FilePath("/proc/self/maps")); |
+ std::vector<std::pair<uint64_t, uint64_t>> ranges = |
+ FindRanges(&file_line_iterator); |
+ pid_t pid = fork(); |
+ if (pid == 0) { |
+ setpriority(PRIO_PROCESS, 0, kBackgroundPriority); |
+ Prefetch(ranges); |
+ exit(0); |
+ } |
+} |
+ |
+} // namespace android |
+} // namespace base |