• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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