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..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 |