Chromium Code Reviews| 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..4792cf010e7d47cb1d651b1429bc8695050eea08 |
| --- /dev/null |
| +++ b/base/android/library_loader/library_prefetcher.cc |
| @@ -0,0 +1,189 @@ |
| +// 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 <stdio.h> |
| +#include <sys/resource.h> |
| +#include <sys/wait.h> |
| +#include <unistd.h> |
| +#include <utility> |
| +#include <vector> |
| + |
| +#include "base/logging.h" |
| +#include "base/posix/eintr_wrapper.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/strings/string_util.h" |
| + |
| +namespace base { |
| +namespace android { |
| + |
| +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"}; |
| + |
| +bool IsReadableAndPrivate(const std::string& flags) { |
| + return flags.size() == 4 && flags[0] == 'r' && flags[3] == 'p'; |
| +} |
| + |
| +bool FilenameMatchesSuffix(const std::string& filename) { |
| + for (int i = 0; i < kSuffixesToMatchCount; i++) { |
| + if (EndsWith(filename, kSuffixesToMatch[i], true)) { |
| + return true; |
| + } |
| + } |
| + return false; |
| +} |
| + |
| +// For each range, reads a byte per page to force it into the page cache. |
| +// Heap allocations, syscalls and library functions are not allowed in this |
| +// function. |
| +void Prefetch(const std::vector<std::pair<uint64_t, uint64_t>>& ranges) { |
| + for (const auto& range : ranges) { |
| + const uint64_t page_mask = kPageSize - 1; |
| + CHECK(!(range.first & page_mask) && !(range.second & page_mask)); |
| + unsigned char* start_address = (unsigned char*)range.first; |
|
pasko
2015/03/31 12:01:43
C-style casts are discouraged, please use the leas
Benoit L
2015/03/31 14:00:01
Done.
|
| + unsigned char* end_address = (unsigned char*)range.second; |
| + unsigned char dummy; |
| + for (unsigned char* addr = start_address; addr < end_address; |
| + addr += kPageSize) { |
| + // Volatile is required to prevent the compiler from eliminating this |
| + // loop. |
| + dummy ^= *((volatile unsigned char*)addr); |
| + } |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +// static |
| +bool NativeLibraryPrefetcher::ParseMapping(const std::string& line, |
| + Mapping* mapping) { |
| + // Example: |
| + // b6e62000-b6e64000 r-xp 00000000 b3:15 1204 /system/lib/libstdc++.so |
| + // start-end flags offset device inode [filename] |
| + std::vector<std::string> pieces; |
| + base::SplitStringAlongWhitespace(line, &pieces); |
| + size_t pieces_count = pieces.size(); |
| + // Filename is optional, so at least 5 fields. |
| + if (pieces_count < 5) { |
| + return false; |
| + } |
| + |
| + std::vector<std::string> start_end; |
| + base::SplitString(pieces[0], '-', &start_end); |
| + if (start_end.size() != 2) { |
| + return false; |
| + } |
| + bool ok = base::HexStringToUInt64(start_end[0], &mapping->start); |
| + ok &= base::HexStringToUInt64(start_end[1], &mapping->end); |
| + mapping->flags = pieces[1]; |
| + // Discards fields 3, 4, and 5 starting from 1 (ie offset, device and inode). |
|
pasko
2015/03/31 12:01:43
We use this form 'Discards' and 'Truncates' when w
Benoit L
2015/03/31 14:00:01
Done.
|
| + if (!ok) { |
| + return false; |
| + } |
| + if (pieces_count == 5) { |
| + mapping->filename = std::string(); |
| + } else { |
| + // Truncates filenames containing spaces, but these are not the droids |
| + // you're looking for. |
| + mapping->filename = pieces[5]; |
| + } |
| + return true; |
| +} |
| + |
| +// static |
| +bool NativeLibraryPrefetcher::IsGoodToPrefetch(const Mapping& mapping) { |
| + return FilenameMatchesSuffix(mapping.filename) && |
| + IsReadableAndPrivate(mapping.flags); // Code mappings are private. |
|
pasko
2015/03/31 12:01:43
nit:
s/Code/Code and data/
?
Benoit L
2015/03/31 14:00:01
Done.
|
| +} |
| + |
| +// static |
| +bool NativeLibraryPrefetcher::FindRanges( |
| + std::vector<std::pair<uint64_t, uint64_t>>* ranges) { |
| + base::ScopedFILE file(base::OpenFile(base::FilePath("/proc/self/maps"), "r")); |
| + if (!file.get()) { |
| + return false; |
| + } |
| + std::vector<Mapping> mappings; |
| + const size_t kMaxLineLength = 4096; |
| + char line_buffer[kMaxLineLength]; |
| + while (char* line_str = fgets(line_buffer, kMaxLineLength - 1, file.get())) { |
| + std::string line(line_str); |
| + Mapping mapping; |
| + if (!ParseMapping(line, &mapping)) { |
| + continue; |
| + } |
| + if (IsGoodToPrefetch(mapping)) { |
| + mappings.push_back(mapping); |
| + } |
| + } |
| + |
| + // If libchrome.so is mapped, keep only libchrome's ranges. |
|
pasko
2015/03/31 12:01:43
please move it to another function and test separa
Benoit L
2015/03/31 14:00:01
Done.
|
| + const std::string& libchrome_suffix = kSuffixesToMatch[0]; |
| + bool has_libchrome_mapping = false; |
| + for (const Mapping& mapping : mappings) { |
| + if (EndsWith(mapping.filename, libchrome_suffix, true)) { |
| + has_libchrome_mapping = true; |
| + break; |
| + } |
| + } |
| + for (const Mapping& mapping : mappings) { |
| + if (has_libchrome_mapping |
| + && !EndsWith(mapping.filename, libchrome_suffix, true)) { |
| + continue; |
| + } |
| + ranges->push_back(std::make_pair(mapping.start, mapping.end)); |
| + } |
| + return true; |
| +} |
| + |
| +// Forks and waits for 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. |
| +// - Not inflating the memory used by the main process uselessly, which could |
| +// increase its likelihood to be killed. |
| +// 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. |
| +// static |
| +bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() { |
| + // Looking for ranges is done before the fork, to avoid syscalls and/or memory |
| + // allocations in the forked process. The child process inherits the lock |
| + // state of all the parent thread locks. It cannot rely on being able to |
| + // acquire any lock (unless special care is taken in a pre-fork handler), |
| + // including being able to call malloc(). |
| + std::vector<std::pair<uint64_t, uint64_t>> ranges; |
| + if (!FindRanges(&ranges)) { |
| + return false; |
| + } |
| + pid_t pid = fork(); |
| + if (pid == 0) { |
| + setpriority(PRIO_PROCESS, 0, kBackgroundPriority); |
| + Prefetch(ranges); |
| + _exit(0); // Don't call the atexit() handlers. |
| + } else { |
| + if (pid < 0) { |
| + return false; |
| + } |
| + int status; |
| + const pid_t result = HANDLE_EINTR(waitpid(pid, &status, 0)); |
| + if (result == pid) { |
| + if (WIFEXITED(status)) { |
| + return WEXITSTATUS(status) == 0; |
| + } |
| + } |
| + return false; |
| + } |
| +} |
| + |
| +} // namespace android |
| +} // namespace base |