Make android_os_Process_readProcFile allocate less
Make android_os_Process_readProcFile read small proc files into a stack-allocated buffer and fall back to the heap only for larger files. Avoid pointless zeroing of memory that we're going to use for read() buffers. Read proc files atomically instead of piece-by-piece. Use Android RAII helpers instead of explicit resource management. Bug: 140439982 Test: booted, works Change-Id: Ifc02a4bbab0cfd968aff841d7ce0af846db613eb
This commit is contained in:
@@ -28,7 +28,11 @@
|
||||
#include <meminfo/sysmeminfo.h>
|
||||
#include <processgroup/processgroup.h>
|
||||
#include <processgroup/sched_policy.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -58,8 +62,18 @@ using namespace android;
|
||||
|
||||
static const bool kDebugPolicy = false;
|
||||
static const bool kDebugProc = false;
|
||||
// When reading `proc` files, how many bytes to read at a time
|
||||
static const int kReadSize = 4096;
|
||||
|
||||
// Stack reservation for reading small proc files. Most callers of
|
||||
// readProcFile() are reading files under this threshold, e.g.,
|
||||
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
|
||||
// bytes, so use 1024 for the stack to provide a bit of slack.
|
||||
static const ssize_t kProcReadStackBufferSize = 1024;
|
||||
|
||||
// The other files we read from proc tend to be a bit larger (e.g.,
|
||||
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
|
||||
// retry with a relatively large heap-allocated buffer. We double
|
||||
// this size and retry until the whole file fits.
|
||||
static const ssize_t kProcReadMinHeapBufferSize = 4096;
|
||||
|
||||
#if GUARD_THREAD_PRIORITY
|
||||
Mutex gKeyCreateMutex;
|
||||
@@ -1004,9 +1018,9 @@ jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz,
|
||||
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
int fd = open(file8, O_RDONLY | O_CLOEXEC);
|
||||
|
||||
if (fd < 0) {
|
||||
::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
|
||||
if (!fd.ok()) {
|
||||
if (kDebugProc) {
|
||||
ALOGW("Unable to open process file: %s\n", file8);
|
||||
}
|
||||
@@ -1015,35 +1029,52 @@ jboolean android_os_Process_readProcFile(JNIEnv* env, jobject clazz,
|
||||
}
|
||||
env->ReleaseStringUTFChars(file, file8);
|
||||
|
||||
std::vector<char> fileBuffer(kReadSize);
|
||||
int numBytesRead = 0;
|
||||
while (true) {
|
||||
// Resize buffer to make space for contents. This might be more than we need, but once we've
|
||||
// read we resize back down
|
||||
fileBuffer.resize(numBytesRead + kReadSize, 0);
|
||||
// Read in contents
|
||||
int len = TEMP_FAILURE_RETRY(read(fd, fileBuffer.data() + numBytesRead, kReadSize));
|
||||
numBytesRead += len;
|
||||
if (len < 0) {
|
||||
// If `len` is negative, an error occurred on read
|
||||
// Most proc files we read are small, so we only go through the
|
||||
// loop once and use the stack buffer. We allocate a buffer big
|
||||
// enough for the whole file.
|
||||
|
||||
char readBufferStack[kProcReadStackBufferSize];
|
||||
std::unique_ptr<char[]> readBufferHeap;
|
||||
char* readBuffer = &readBufferStack[0];
|
||||
ssize_t readBufferSize = kProcReadStackBufferSize;
|
||||
ssize_t numberBytesRead;
|
||||
for (;;) {
|
||||
// By using pread, we can avoid an lseek to rewind the FD
|
||||
// before retry, saving a system call.
|
||||
numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
|
||||
if (numberBytesRead < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (numberBytesRead < 0) {
|
||||
if (kDebugProc) {
|
||||
ALOGW("Unable to open process file: %s fd=%d\n", file8, fd);
|
||||
ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
|
||||
}
|
||||
close(fd);
|
||||
return JNI_FALSE;
|
||||
} else if (len == 0) {
|
||||
// If nothing read, we're done
|
||||
}
|
||||
if (numberBytesRead < readBufferSize) {
|
||||
break;
|
||||
}
|
||||
if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
|
||||
if (kDebugProc) {
|
||||
ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
|
||||
}
|
||||
return JNI_FALSE;
|
||||
}
|
||||
readBufferSize = std::max(readBufferSize * 2,
|
||||
kProcReadMinHeapBufferSize);
|
||||
readBufferHeap.reset(); // Free address space before getting more.
|
||||
readBufferHeap = std::make_unique<char[]>(readBufferSize);
|
||||
if (!readBufferHeap) {
|
||||
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
readBuffer = readBufferHeap.get();
|
||||
}
|
||||
// Resize back down to the amount we read
|
||||
fileBuffer.resize(numBytesRead);
|
||||
// Terminate buffer with null byte
|
||||
fileBuffer.push_back('\0');
|
||||
close(fd);
|
||||
|
||||
return android_os_Process_parseProcLineArray(env, clazz, fileBuffer.data(), 0, numBytesRead,
|
||||
format, outStrings, outLongs, outFloats);
|
||||
// parseProcLineArray below modifies the buffer while parsing!
|
||||
return android_os_Process_parseProcLineArray(
|
||||
env, clazz, readBuffer, 0, numberBytesRead,
|
||||
format, outStrings, outLongs, outFloats);
|
||||
}
|
||||
|
||||
void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
|
||||
|
||||
Reference in New Issue
Block a user