1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "CachedAppOptimizer"
18 //#define LOG_NDEBUG 0
19
20 #include <android-base/file.h>
21 #include <android-base/logging.h>
22 #include <android-base/stringprintf.h>
23 #include <android-base/unique_fd.h>
24 #include <android_runtime/AndroidRuntime.h>
25 #include <binder/IPCThreadState.h>
26 #include <cutils/compiler.h>
27 #include <dirent.h>
28 #include <jni.h>
29 #include <linux/errno.h>
30 #include <log/log.h>
31 #include <meminfo/procmeminfo.h>
32 #include <nativehelper/JNIHelp.h>
33 #include <processgroup/processgroup.h>
34 #include <stddef.h>
35 #include <stdio.h>
36 #include <sys/mman.h>
37 #include <sys/pidfd.h>
38 #include <sys/stat.h>
39 #include <sys/syscall.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42
43 #include <algorithm>
44
45 using android::base::StringPrintf;
46 using android::base::WriteStringToFile;
47 using android::meminfo::ProcMemInfo;
48 using namespace android::meminfo;
49
50 #define COMPACT_ACTION_FILE_FLAG 1
51 #define COMPACT_ACTION_ANON_FLAG 2
52
53 using VmaToAdviseFunc = std::function<int(const Vma&)>;
54 using android::base::unique_fd;
55
56 #define SYNC_RECEIVED_WHILE_FROZEN (1)
57 #define ASYNC_RECEIVED_WHILE_FROZEN (2)
58 #define TXNS_PENDING_WHILE_FROZEN (4)
59
60 namespace android {
61
62 static bool cancelRunningCompaction;
63 static bool compactionInProgress;
64
65 // Legacy method for compacting processes, any new code should
66 // use compactProcess instead.
compactProcessProcfs(int pid,const std::string & compactionType)67 static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
68 std::string reclaim_path = StringPrintf("/proc/%d/reclaim", pid);
69 WriteStringToFile(compactionType, reclaim_path);
70 }
71
72 // Compacts a set of VMAs for pid using an madviseType accepted by process_madvise syscall
73 // On success returns the total bytes that where compacted. On failure it returns
74 // a negative error code from the standard linux error codes.
compactMemory(const std::vector<Vma> & vmas,int pid,int madviseType)75 static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
76 // UIO_MAXIOV is currently a small value and we might have more addresses
77 // we do multiple syscalls if we exceed its maximum
78 static struct iovec vmasToKernel[UIO_MAXIOV];
79
80 if (vmas.empty()) {
81 return 0;
82 }
83
84 unique_fd pidfd(pidfd_open(pid, 0));
85 if (pidfd < 0) {
86 // Skip compaction if failed to open pidfd with any error
87 return -errno;
88 }
89 compactionInProgress = true;
90 cancelRunningCompaction = false;
91
92 int64_t totalBytesCompacted = 0;
93 for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
94 if (CC_UNLIKELY(cancelRunningCompaction)) {
95 // There could be a significant delay betweenwhen a compaction
96 // is requested and when it is handled during this time
97 // our OOM adjust could have improved.
98 cancelRunningCompaction = false;
99 break;
100 }
101 int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
102 for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
103 vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
104 vmasToKernel[iVec].iov_len = vmas[iVma].end - vmas[iVma].start;
105 }
106
107 auto bytesCompacted =
108 process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
109 if (CC_UNLIKELY(bytesCompacted == -1)) {
110 compactionInProgress = false;
111 return -errno;
112 }
113
114 totalBytesCompacted += bytesCompacted;
115 }
116 compactionInProgress = false;
117
118 return totalBytesCompacted;
119 }
120
getFilePageAdvice(const Vma & vma)121 static int getFilePageAdvice(const Vma& vma) {
122 if (vma.inode > 0 && !vma.is_shared) {
123 return MADV_COLD;
124 }
125 return -1;
126 }
getAnonPageAdvice(const Vma & vma)127 static int getAnonPageAdvice(const Vma& vma) {
128 if (vma.inode == 0 && !vma.is_shared) {
129 return MADV_PAGEOUT;
130 }
131 return -1;
132 }
getAnyPageAdvice(const Vma & vma)133 static int getAnyPageAdvice(const Vma& vma) {
134 if (vma.inode == 0 && !vma.is_shared) {
135 return MADV_PAGEOUT;
136 }
137 return MADV_COLD;
138 }
139
140 // Perform a full process compaction using process_madvise syscall
141 // reading all filtering VMAs and filtering pages as specified by pageFilter
compactProcess(int pid,VmaToAdviseFunc vmaToAdviseFunc)142 static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
143 ProcMemInfo meminfo(pid);
144 std::vector<Vma> pageoutVmas, coldVmas;
145 auto vmaCollectorCb = [&coldVmas,&pageoutVmas,&vmaToAdviseFunc](const Vma& vma) {
146 int advice = vmaToAdviseFunc(vma);
147 switch (advice) {
148 case MADV_COLD:
149 coldVmas.push_back(vma);
150 break;
151 case MADV_PAGEOUT:
152 pageoutVmas.push_back(vma);
153 break;
154 }
155 };
156 meminfo.ForEachVmaFromMaps(vmaCollectorCb);
157
158 int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
159 if (pageoutBytes < 0) {
160 // Error, just forward it.
161 return pageoutBytes;
162 }
163
164 int64_t coldBytes = compactMemory(coldVmas, pid, MADV_COLD);
165 if (coldBytes < 0) {
166 // Error, just forward it.
167 return coldBytes;
168 }
169
170 return pageoutBytes + coldBytes;
171 }
172
173 // Compact process using process_madvise syscall or fallback to procfs in
174 // case syscall does not exist.
compactProcessOrFallback(int pid,int compactionFlags)175 static void compactProcessOrFallback(int pid, int compactionFlags) {
176 if ((compactionFlags & (COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG)) == 0) return;
177
178 bool compactAnon = compactionFlags & COMPACT_ACTION_ANON_FLAG;
179 bool compactFile = compactionFlags & COMPACT_ACTION_FILE_FLAG;
180
181 // Set when the system does not support process_madvise syscall to avoid
182 // gathering VMAs in subsequent calls prior to falling back to procfs
183 static bool shouldForceProcFs = false;
184 std::string compactionType;
185 VmaToAdviseFunc vmaToAdviseFunc;
186
187 if (compactAnon) {
188 if (compactFile) {
189 compactionType = "all";
190 vmaToAdviseFunc = getAnyPageAdvice;
191 } else {
192 compactionType = "anon";
193 vmaToAdviseFunc = getAnonPageAdvice;
194 }
195 } else {
196 compactionType = "file";
197 vmaToAdviseFunc = getFilePageAdvice;
198 }
199
200 if (shouldForceProcFs || compactProcess(pid, vmaToAdviseFunc) == -ENOSYS) {
201 shouldForceProcFs = true;
202 compactProcessProcfs(pid, compactionType);
203 }
204 }
205
206 // This performs per-process reclaim on all processes belonging to non-app UIDs.
207 // For the most part, these are non-zygote processes like Treble HALs, but it
208 // also includes zygote-derived processes that run in system UIDs, like bluetooth
209 // or potentially some mainline modules. The only process that should definitely
210 // not be compacted is system_server, since compacting system_server around the
211 // time of BOOT_COMPLETE could result in perceptible issues.
com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *,jobject)212 static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
213 std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
214 struct dirent* current;
215 while ((current = readdir(proc.get()))) {
216 if (current->d_type != DT_DIR) {
217 continue;
218 }
219
220 // don't compact system_server, rely on persistent compaction during screen off
221 // in order to avoid mmap_sem-related stalls
222 if (atoi(current->d_name) == getpid()) {
223 continue;
224 }
225
226 std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
227 struct stat status_info;
228
229 if (stat(status_name.c_str(), &status_info) != 0) {
230 // must be some other directory that isn't a pid
231 continue;
232 }
233
234 // android.os.Process.FIRST_APPLICATION_UID
235 if (status_info.st_uid >= 10000) {
236 continue;
237 }
238
239 int pid = atoi(current->d_name);
240
241 compactProcessOrFallback(pid, COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG);
242 }
243 }
244
com_android_server_am_CachedAppOptimizer_cancelCompaction(JNIEnv *,jobject)245 static void com_android_server_am_CachedAppOptimizer_cancelCompaction(JNIEnv*, jobject) {
246 if (compactionInProgress) {
247 cancelRunningCompaction = true;
248 }
249 }
250
com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv *,jobject,jint pid,jint compactionFlags)251 static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
252 jint compactionFlags) {
253 compactProcessOrFallback(pid, compactionFlags);
254 }
255
com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv * env,jobject clazz,jint pid,jboolean freeze)256 static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
257 JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
258
259 jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
260 if (retVal != 0 && retVal != -EAGAIN) {
261 jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
262 }
263
264 return retVal;
265 }
266
com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv * env,jobject clazz,jint pid)267 static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
268 jobject clazz, jint pid) {
269 uint32_t syncReceived = 0, asyncReceived = 0;
270
271 int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
272
273 if (error < 0) {
274 jniThrowException(env, "java/lang/RuntimeException", strerror(error));
275 }
276
277 jint retVal = 0;
278
279 // bit 0 of sync_recv goes to bit 0 of retVal
280 retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
281 // bit 0 of async_recv goes to bit 1 of retVal
282 retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
283 // bit 1 of sync_recv goes to bit 2 of retVal
284 retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
285
286 return retVal;
287 }
288
com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv * env,jobject clazz)289 static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env,
290 jobject clazz) {
291 std::string path;
292
293 if (!getAttributePathForTask("FreezerState", getpid(), &path)) {
294 path = "";
295 }
296
297 return env->NewStringUTF(path.c_str());
298 }
299
300 static const JNINativeMethod sMethods[] = {
301 /* name, signature, funcPtr */
302 {"cancelCompaction", "()V",
303 (void*)com_android_server_am_CachedAppOptimizer_cancelCompaction},
304 {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
305 {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
306 {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
307 {"getBinderFreezeInfo", "(I)I",
308 (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
309 {"getFreezerCheckPath", "()Ljava/lang/String;",
310 (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
311
register_android_server_am_CachedAppOptimizer(JNIEnv * env)312 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
313 {
314 return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
315 sMethods, NELEM(sMethods));
316 }
317
318 }
319