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