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
59 namespace android {
60
61 // Legacy method for compacting processes, any new code should
62 // use compactProcess instead.
compactProcessProcfs(int pid,const std::string & compactionType)63 static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
64 std::string reclaim_path = StringPrintf("/proc/%d/reclaim", pid);
65 WriteStringToFile(compactionType, reclaim_path);
66 }
67
68 // Compacts a set of VMAs for pid using an madviseType accepted by process_madvise syscall
69 // On success returns the total bytes that where compacted. On failure it returns
70 // a negative error code from the standard linux error codes.
compactMemory(const std::vector<Vma> & vmas,int pid,int madviseType)71 static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
72 // UIO_MAXIOV is currently a small value and we might have more addresses
73 // we do multiple syscalls if we exceed its maximum
74 static struct iovec vmasToKernel[UIO_MAXIOV];
75
76 if (vmas.empty()) {
77 return 0;
78 }
79
80 unique_fd pidfd(pidfd_open(pid, 0));
81 if (pidfd < 0) {
82 // Skip compaction if failed to open pidfd with any error
83 return -errno;
84 }
85
86 int64_t totalBytesCompacted = 0;
87 for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
88 int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
89 for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
90 vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
91 vmasToKernel[iVec].iov_len = vmas[iVma].end - vmas[iVma].start;
92 }
93
94 auto bytesCompacted =
95 process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
96 if (CC_UNLIKELY(bytesCompacted == -1)) {
97 return -errno;
98 }
99
100 totalBytesCompacted += bytesCompacted;
101 }
102
103 return totalBytesCompacted;
104 }
105
getFilePageAdvice(const Vma & vma)106 static int getFilePageAdvice(const Vma& vma) {
107 if (vma.inode > 0 && !vma.is_shared) {
108 return MADV_COLD;
109 }
110 return -1;
111 }
getAnonPageAdvice(const Vma & vma)112 static int getAnonPageAdvice(const Vma& vma) {
113 if (vma.inode == 0 && !vma.is_shared) {
114 return MADV_PAGEOUT;
115 }
116 return -1;
117 }
getAnyPageAdvice(const Vma & vma)118 static int getAnyPageAdvice(const Vma& vma) {
119 if (vma.inode == 0 && !vma.is_shared) {
120 return MADV_PAGEOUT;
121 }
122 return MADV_COLD;
123 }
124
125 // Perform a full process compaction using process_madvise syscall
126 // reading all filtering VMAs and filtering pages as specified by pageFilter
compactProcess(int pid,VmaToAdviseFunc vmaToAdviseFunc)127 static int64_t compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
128 ProcMemInfo meminfo(pid);
129 std::vector<Vma> pageoutVmas, coldVmas;
130 auto vmaCollectorCb = [&coldVmas,&pageoutVmas,&vmaToAdviseFunc](const Vma& vma) {
131 int advice = vmaToAdviseFunc(vma);
132 switch (advice) {
133 case MADV_COLD:
134 coldVmas.push_back(vma);
135 break;
136 case MADV_PAGEOUT:
137 pageoutVmas.push_back(vma);
138 break;
139 }
140 };
141 meminfo.ForEachVmaFromMaps(vmaCollectorCb);
142
143 int64_t pageoutBytes = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
144 if (pageoutBytes < 0) {
145 // Error, just forward it.
146 return pageoutBytes;
147 }
148
149 int64_t coldBytes = compactMemory(coldVmas, pid, MADV_COLD);
150 if (coldBytes < 0) {
151 // Error, just forward it.
152 return coldBytes;
153 }
154
155 return pageoutBytes + coldBytes;
156 }
157
158 // Compact process using process_madvise syscall or fallback to procfs in
159 // case syscall does not exist.
compactProcessOrFallback(int pid,int compactionFlags)160 static void compactProcessOrFallback(int pid, int compactionFlags) {
161 if ((compactionFlags & (COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG)) == 0) return;
162
163 bool compactAnon = compactionFlags & COMPACT_ACTION_ANON_FLAG;
164 bool compactFile = compactionFlags & COMPACT_ACTION_FILE_FLAG;
165
166 // Set when the system does not support process_madvise syscall to avoid
167 // gathering VMAs in subsequent calls prior to falling back to procfs
168 static bool shouldForceProcFs = false;
169 std::string compactionType;
170 VmaToAdviseFunc vmaToAdviseFunc;
171
172 if (compactAnon) {
173 if (compactFile) {
174 compactionType = "all";
175 vmaToAdviseFunc = getAnyPageAdvice;
176 } else {
177 compactionType = "anon";
178 vmaToAdviseFunc = getAnonPageAdvice;
179 }
180 } else {
181 compactionType = "file";
182 vmaToAdviseFunc = getFilePageAdvice;
183 }
184
185 if (shouldForceProcFs || compactProcess(pid, vmaToAdviseFunc) == -ENOSYS) {
186 shouldForceProcFs = true;
187 compactProcessProcfs(pid, compactionType);
188 }
189 }
190
191 // This performs per-process reclaim on all processes belonging to non-app UIDs.
192 // For the most part, these are non-zygote processes like Treble HALs, but it
193 // also includes zygote-derived processes that run in system UIDs, like bluetooth
194 // or potentially some mainline modules. The only process that should definitely
195 // not be compacted is system_server, since compacting system_server around the
196 // time of BOOT_COMPLETE could result in perceptible issues.
com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *,jobject)197 static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
198 std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
199 struct dirent* current;
200 while ((current = readdir(proc.get()))) {
201 if (current->d_type != DT_DIR) {
202 continue;
203 }
204
205 // don't compact system_server, rely on persistent compaction during screen off
206 // in order to avoid mmap_sem-related stalls
207 if (atoi(current->d_name) == getpid()) {
208 continue;
209 }
210
211 std::string status_name = StringPrintf("/proc/%s/status", current->d_name);
212 struct stat status_info;
213
214 if (stat(status_name.c_str(), &status_info) != 0) {
215 // must be some other directory that isn't a pid
216 continue;
217 }
218
219 // android.os.Process.FIRST_APPLICATION_UID
220 if (status_info.st_uid >= 10000) {
221 continue;
222 }
223
224 int pid = atoi(current->d_name);
225
226 compactProcessOrFallback(pid, COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG);
227 }
228 }
229
com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv *,jobject,jint pid,jint compactionFlags)230 static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
231 jint compactionFlags) {
232 compactProcessOrFallback(pid, compactionFlags);
233 }
234
com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv * env,jobject clazz,jint pid,jboolean freeze)235 static void com_android_server_am_CachedAppOptimizer_freezeBinder(
236 JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
237
238 if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) {
239 jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
240 }
241 }
242
com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv * env,jobject clazz,jint pid)243 static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
244 jobject clazz, jint pid) {
245 bool syncReceived = false, asyncReceived = false;
246
247 int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
248
249 if (error < 0) {
250 jniThrowException(env, "java/lang/RuntimeException", strerror(error));
251 }
252
253 jint retVal = 0;
254
255 if(syncReceived) {
256 retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
257 }
258
259 if(asyncReceived) {
260 retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
261 }
262
263 return retVal;
264 }
265
com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv * env,jobject clazz)266 static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env,
267 jobject clazz) {
268 std::string path;
269
270 if (!getAttributePathForTask("FreezerState", getpid(), &path)) {
271 path = "";
272 }
273
274 return env->NewStringUTF(path.c_str());
275 }
276
277 static const JNINativeMethod sMethods[] = {
278 /* name, signature, funcPtr */
279 {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
280 {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
281 {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
282 {"getBinderFreezeInfo", "(I)I",
283 (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
284 {"getFreezerCheckPath", "()Ljava/lang/String;",
285 (void*)com_android_server_am_CachedAppOptimizer_getFreezerCheckPath}};
286
register_android_server_am_CachedAppOptimizer(JNIEnv * env)287 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
288 {
289 return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
290 sMethods, NELEM(sMethods));
291 }
292
293 }
294