• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 package com.android.server.am;
18 
19 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
20 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
21 
22 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
23 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
24 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
25 
26 import android.app.ApplicationExitInfo.Reason;
27 import android.app.ApplicationExitInfo.SubReason;
28 import android.os.Handler;
29 import android.os.Process;
30 import android.os.StrictMode;
31 import android.util.Slog;
32 import android.util.SparseArray;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.os.ProcStatsUtil;
37 import com.android.internal.os.ProcessCpuTracker;
38 
39 import libcore.io.IoUtils;
40 
41 import java.io.File;
42 import java.io.FileDescriptor;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.Collections;
50 import java.util.function.Function;
51 
52 /**
53  * Activity manager code dealing with phantom processes.
54  */
55 public final class PhantomProcessList {
56     static final String TAG = TAG_WITH_CLASS_NAME ? "PhantomProcessList" : TAG_AM;
57 
58     final Object mLock = new Object();
59 
60     /**
61      * All of the phantom process record we track, key is the pid of the process.
62      */
63     @GuardedBy("mLock")
64     final SparseArray<PhantomProcessRecord> mPhantomProcesses = new SparseArray<>();
65 
66     /**
67      * The mapping between app processes and their phantom processess, outer key is the pid of
68      * the app process, while the inner key is the pid of the phantom process.
69      */
70     @GuardedBy("mLock")
71     final SparseArray<SparseArray<PhantomProcessRecord>> mAppPhantomProcessMap =
72             new SparseArray<>();
73 
74     /**
75      * The mapping of the pidfd to PhantomProcessRecord.
76      */
77     @GuardedBy("mLock")
78     final SparseArray<PhantomProcessRecord> mPhantomProcessesPidFds = new SparseArray<>();
79 
80     /**
81      * The list of phantom processes tha's being signaled to be killed but still undead yet.
82      */
83     @GuardedBy("mLock")
84     final SparseArray<PhantomProcessRecord> mZombiePhantomProcesses = new SparseArray<>();
85 
86     @GuardedBy("mLock")
87     private final ArrayList<PhantomProcessRecord> mTempPhantomProcesses = new ArrayList<>();
88 
89     /**
90      * The mapping between a phantom process ID to its parent process (an app process)
91      */
92     @GuardedBy("mLock")
93     private final SparseArray<ProcessRecord> mPhantomToAppProcessMap = new SparseArray<>();
94 
95     @GuardedBy("mLock")
96     private final SparseArray<InputStream> mCgroupProcsFds = new SparseArray<>();
97 
98     @GuardedBy("mLock")
99     private final byte[] mDataBuffer = new byte[4096];
100 
101     @GuardedBy("mLock")
102     private boolean mTrimPhantomProcessScheduled = false;
103 
104     @GuardedBy("mLock")
105     int mUpdateSeq;
106 
107     @VisibleForTesting
108     Injector mInjector;
109 
110     private final ActivityManagerService mService;
111     private final Handler mKillHandler;
112 
113     private static final int CGROUP_V1 = 0;
114     private static final int CGROUP_V2 = 1;
115     private static final String[] CGROUP_PATH_PREFIXES = {
116         "/acct/uid_" /* cgroup v1 */,
117         "/sys/fs/cgroup/uid_" /* cgroup v2 */
118     };
119     private static final String CGROUP_PID_PREFIX = "/pid_";
120     private static final String CGROUP_PROCS = "/cgroup.procs";
121 
122     @VisibleForTesting
123     int mCgroupVersion = CGROUP_V1;
124 
PhantomProcessList(final ActivityManagerService service)125     PhantomProcessList(final ActivityManagerService service) {
126         mService = service;
127         mKillHandler = service.mProcessList.sKillHandler;
128         mInjector = new Injector();
129         probeCgroupVersion();
130     }
131 
132     @VisibleForTesting
133     @GuardedBy("mLock")
lookForPhantomProcessesLocked()134     void lookForPhantomProcessesLocked() {
135         mPhantomToAppProcessMap.clear();
136         StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
137         try {
138             synchronized (mService.mPidsSelfLocked) {
139                 for (int i = mService.mPidsSelfLocked.size() - 1; i >= 0; i--) {
140                     final ProcessRecord app = mService.mPidsSelfLocked.valueAt(i);
141                     lookForPhantomProcessesLocked(app);
142                 }
143             }
144         } finally {
145             StrictMode.setThreadPolicy(oldPolicy);
146         }
147     }
148 
149     @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
lookForPhantomProcessesLocked(ProcessRecord app)150     private void lookForPhantomProcessesLocked(ProcessRecord app) {
151         if (app.appZygote || app.isKilled() || app.isKilledByAm()) {
152             // process forked from app zygote doesn't have its own acct entry
153             return;
154         }
155         final int appPid = app.getPid();
156         InputStream input = mCgroupProcsFds.get(appPid);
157         if (input == null) {
158             final String path = getCgroupFilePath(app.info.uid, appPid);
159             try {
160                 input = mInjector.openCgroupProcs(path);
161             } catch (FileNotFoundException | SecurityException e) {
162                 if (DEBUG_PROCESSES) {
163                     Slog.w(TAG, "Unable to open " + path, e);
164                 }
165                 return;
166             }
167             // Keep the FD open for better performance
168             mCgroupProcsFds.put(appPid, input);
169         }
170         final byte[] buf = mDataBuffer;
171         try {
172             int read = 0;
173             int pid = 0;
174             long totalRead = 0;
175             do {
176                 read = mInjector.readCgroupProcs(input, buf, 0, buf.length);
177                 if (read == -1) {
178                     break;
179                 }
180                 totalRead += read;
181                 for (int i = 0; i < read; i++) {
182                     final byte b = buf[i];
183                     if (b == '\n') {
184                         addChildPidLocked(app, pid, appPid);
185                         pid = 0;
186                     } else {
187                         pid = pid * 10 + (b - '0');
188                     }
189                 }
190                 if (read < buf.length) {
191                     // we may break from here safely as sysfs reading should return the whole page
192                     // if the remaining data is larger than a page
193                     break;
194                 }
195             } while (true);
196             if (pid != 0) {
197                 addChildPidLocked(app, pid, appPid);
198             }
199             // rewind the fd for the next read
200             input.skip(-totalRead);
201         } catch (IOException e) {
202             Slog.e(TAG, "Error in reading cgroup procs from " + app, e);
203             IoUtils.closeQuietly(input);
204             mCgroupProcsFds.delete(appPid);
205         }
206     }
207 
probeCgroupVersion()208     private void probeCgroupVersion() {
209         for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) {
210             if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) {
211                 mCgroupVersion = i;
212                 break;
213             }
214         }
215     }
216 
217     @VisibleForTesting
getCgroupFilePath(int uid, int pid)218     String getCgroupFilePath(int uid, int pid) {
219         return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS;
220     }
221 
getProcessName(int pid)222     static String getProcessName(int pid) {
223         String procName = ProcStatsUtil.readTerminatedProcFile(
224                 "/proc/" + pid + "/cmdline", (byte) '\0');
225         if (procName == null) {
226             return null;
227         }
228         int l = procName.lastIndexOf('/');
229         if (l > 0 && l < procName.length() - 1) {
230             procName = procName.substring(l + 1);
231         }
232         return procName;
233     }
234 
235     @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
addChildPidLocked(final ProcessRecord app, final int pid, final int appPid)236     private void addChildPidLocked(final ProcessRecord app, final int pid, final int appPid) {
237         if (appPid != pid) {
238             // That's something else...
239             final ProcessRecord r = mService.mPidsSelfLocked.get(pid);
240             if (r != null) {
241                 // Is this a process forked via app zygote?
242                 if (!r.appZygote) {
243                     // Unexpected...
244                     if (DEBUG_PROCESSES) {
245                         Slog.w(TAG, "Unexpected: " + r + " appears in the cgroup.procs of " + app);
246                     }
247                 } else {
248                     // Just a child process of app zygote, no worries
249                 }
250             } else {
251                 final int index = mPhantomToAppProcessMap.indexOfKey(pid);
252                 if (index >= 0) { // unlikely since we cleared the map at the beginning
253                     final ProcessRecord current = mPhantomToAppProcessMap.valueAt(index);
254                     if (app == current) {
255                         // Okay it's unchanged
256                         return;
257                     }
258                     mPhantomToAppProcessMap.setValueAt(index, app);
259                 } else {
260                     mPhantomToAppProcessMap.put(pid, app);
261                 }
262                 // Its UID isn't necessarily to be the same as the app.info.uid, since it could be
263                 // forked from child processes of app zygote
264                 final int uid = Process.getUidForPid(pid);
265                 String procName = mInjector.getProcessName(pid);
266                 if (procName == null || uid < 0) {
267                     mPhantomToAppProcessMap.delete(pid);
268                     return;
269                 }
270                 getOrCreatePhantomProcessIfNeededLocked(procName, uid, pid, true);
271             }
272         }
273     }
274 
onAppDied(final int pid)275     void onAppDied(final int pid) {
276         synchronized (mLock) {
277             final int index = mCgroupProcsFds.indexOfKey(pid);
278             if (index >= 0) {
279                 final InputStream inputStream = mCgroupProcsFds.valueAt(index);
280                 mCgroupProcsFds.removeAt(index);
281                 IoUtils.closeQuietly(inputStream);
282             }
283         }
284     }
285 
286     /**
287      * Get the existing phantom process record, or create if it's not existing yet;
288      * however, before creating it, we'll check if this is really a phantom process
289      * and we'll return null if it's not.
290      */
291     @GuardedBy("mLock")
getOrCreatePhantomProcessIfNeededLocked(final String processName, final int uid, final int pid, boolean createIfNeeded)292     PhantomProcessRecord getOrCreatePhantomProcessIfNeededLocked(final String processName,
293             final int uid, final int pid, boolean createIfNeeded) {
294         // First check if it's actually an app process we know
295         if (isAppProcess(pid)) {
296             return null;
297         }
298 
299         // Have we already been aware of this?
300         final int index = mPhantomProcesses.indexOfKey(pid);
301         if (index >= 0) {
302             final PhantomProcessRecord proc = mPhantomProcesses.valueAt(index);
303             if (proc.equals(processName, uid, pid)) {
304                 return proc;
305             }
306             // Somehow our record doesn't match, remove it anyway
307             Slog.w(TAG, "Stale " + proc + ", removing");
308             onPhantomProcessKilledLocked(proc);
309         } else {
310             // Is this one of the zombie processes we've known?
311             final int idx = mZombiePhantomProcesses.indexOfKey(pid);
312             if (idx >= 0) {
313                 final PhantomProcessRecord proc = mZombiePhantomProcesses.valueAt(idx);
314                 if (proc.equals(processName, uid, pid)) {
315                     return proc;
316                 }
317                 // Our zombie process information is outdated, let's remove this one, it should
318                 // have been gone.
319                 mZombiePhantomProcesses.removeAt(idx);
320             }
321         }
322 
323         if (!createIfNeeded) {
324             return null;
325         }
326 
327         final ProcessRecord r = mPhantomToAppProcessMap.get(pid);
328 
329         if (r != null) {
330             // It's a phantom process, bookkeep it
331             try {
332                 final int appPid = r.getPid();
333                 final PhantomProcessRecord proc = new PhantomProcessRecord(
334                         processName, uid, pid, appPid, mService,
335                         this::onPhantomProcessKilledLocked);
336                 proc.mUpdateSeq = mUpdateSeq;
337                 mPhantomProcesses.put(pid, proc);
338                 SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(appPid);
339                 if (array == null) {
340                     array = new SparseArray<>();
341                     mAppPhantomProcessMap.put(appPid, array);
342                 }
343                 array.put(pid, proc);
344                 if (proc.mPidFd != null) {
345                     mKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener(
346                             proc.mPidFd, EVENT_INPUT | EVENT_ERROR,
347                             this::onPhantomProcessFdEvent);
348                     mPhantomProcessesPidFds.put(proc.mPidFd.getInt$(), proc);
349                 }
350                 scheduleTrimPhantomProcessesLocked();
351                 return proc;
352             } catch (IllegalStateException e) {
353                 return null;
354             }
355         }
356         return null;
357     }
358 
isAppProcess(int pid)359     private boolean isAppProcess(int pid) {
360         synchronized (mService.mPidsSelfLocked) {
361             return mService.mPidsSelfLocked.get(pid) != null;
362         }
363     }
364 
onPhantomProcessFdEvent(FileDescriptor fd, int events)365     private int onPhantomProcessFdEvent(FileDescriptor fd, int events) {
366         synchronized (mLock) {
367             final PhantomProcessRecord proc = mPhantomProcessesPidFds.get(fd.getInt$());
368             if (proc == null) {
369                 return 0;
370             }
371             if ((events & EVENT_INPUT) != 0) {
372                 proc.onProcDied(true);
373             } else {
374                 // EVENT_ERROR, kill the process
375                 proc.killLocked("Process error", true);
376             }
377         }
378         return 0;
379     }
380 
381     @GuardedBy("mLock")
onPhantomProcessKilledLocked(final PhantomProcessRecord proc)382     private void onPhantomProcessKilledLocked(final PhantomProcessRecord proc) {
383         if (proc.mPidFd != null && proc.mPidFd.valid()) {
384             mKillHandler.getLooper().getQueue()
385                     .removeOnFileDescriptorEventListener(proc.mPidFd);
386             mPhantomProcessesPidFds.remove(proc.mPidFd.getInt$());
387             IoUtils.closeQuietly(proc.mPidFd);
388         }
389         mPhantomProcesses.remove(proc.mPid);
390         final int index = mAppPhantomProcessMap.indexOfKey(proc.mPpid);
391         if (index < 0) {
392             return;
393         }
394         SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.valueAt(index);
395         array.remove(proc.mPid);
396         if (array.size() == 0) {
397             mAppPhantomProcessMap.removeAt(index);
398         }
399         if (proc.mZombie) {
400             // If it's not really dead, bookkeep it
401             mZombiePhantomProcesses.put(proc.mPid, proc);
402         } else {
403             // In case of race condition, let's try to remove it from zombie list
404             mZombiePhantomProcesses.remove(proc.mPid);
405         }
406     }
407 
408     @GuardedBy("mLock")
scheduleTrimPhantomProcessesLocked()409     private void scheduleTrimPhantomProcessesLocked() {
410         if (!mTrimPhantomProcessScheduled) {
411             mTrimPhantomProcessScheduled = true;
412             mService.mHandler.post(this::trimPhantomProcessesIfNecessary);
413         }
414     }
415 
416     /**
417      * Clamp the number of phantom processes to
418      * {@link ActivityManagerConstants#MAX_PHANTOM_PROCESSE}, kills those surpluses in the
419      * order of the oom adjs of their parent process.
420      */
trimPhantomProcessesIfNecessary()421     void trimPhantomProcessesIfNecessary() {
422         synchronized (mService.mProcLock) {
423             synchronized (mLock) {
424                 mTrimPhantomProcessScheduled = false;
425                 if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
426                     for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
427                         mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
428                     }
429                     synchronized (mService.mPidsSelfLocked) {
430                         Collections.sort(mTempPhantomProcesses, (a, b) -> {
431                             final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
432                             if (ra == null) {
433                                 // parent is gone, this process should have been killed too
434                                 return 1;
435                             }
436                             final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
437                             if (rb == null) {
438                                 // parent is gone, this process should have been killed too
439                                 return -1;
440                             }
441                             if (ra.mState.getCurAdj() != rb.mState.getCurAdj()) {
442                                 return ra.mState.getCurAdj() - rb.mState.getCurAdj();
443                             }
444                             if (a.mKnownSince != b.mKnownSince) {
445                                 // In case of identical oom adj, younger one first
446                                 return a.mKnownSince < b.mKnownSince ? 1 : -1;
447                             }
448                             return 0;
449                         });
450                     }
451                     for (int i = mTempPhantomProcesses.size() - 1;
452                             i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
453                         final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
454                         proc.killLocked("Trimming phantom processes", true);
455                     }
456                     mTempPhantomProcesses.clear();
457                 }
458             }
459         }
460     }
461 
462     /**
463      * Remove all entries with outdated seq num.
464      */
465     @GuardedBy("mLock")
pruneStaleProcessesLocked()466     void pruneStaleProcessesLocked() {
467         for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
468             final PhantomProcessRecord proc = mPhantomProcesses.valueAt(i);
469             if (proc.mUpdateSeq < mUpdateSeq) {
470                 if (DEBUG_PROCESSES) {
471                     Slog.v(TAG, "Pruning " + proc + " as it should have been dead.");
472                 }
473                 proc.killLocked("Stale process", true);
474             }
475         }
476         for (int i = mZombiePhantomProcesses.size() - 1; i >= 0; i--) {
477             final PhantomProcessRecord proc = mZombiePhantomProcesses.valueAt(i);
478             if (proc.mUpdateSeq < mUpdateSeq) {
479                 if (DEBUG_PROCESSES) {
480                     Slog.v(TAG, "Pruning " + proc + " as it should have been dead.");
481                 }
482             }
483         }
484     }
485 
486     /**
487      * Kill the given phantom process, all its siblings (if any) and their parent process
488      */
489     @GuardedBy("mService")
killPhantomProcessGroupLocked(ProcessRecord app, PhantomProcessRecord proc, @Reason int reasonCode, @SubReason int subReason, String msg)490     void killPhantomProcessGroupLocked(ProcessRecord app, PhantomProcessRecord proc,
491             @Reason int reasonCode, @SubReason int subReason, String msg) {
492         synchronized (mLock) {
493             int index = mAppPhantomProcessMap.indexOfKey(proc.mPpid);
494             if (index >= 0) {
495                 final SparseArray<PhantomProcessRecord> array =
496                         mAppPhantomProcessMap.valueAt(index);
497                 for (int i = array.size() - 1; i >= 0; i--) {
498                     final PhantomProcessRecord r = array.valueAt(i);
499                     if (r == proc) {
500                         r.killLocked(msg, true);
501                     } else {
502                         r.killLocked("Caused by siling process: " + msg, false);
503                     }
504                 }
505             }
506         }
507         // Lastly, kill the parent process too
508         app.killLocked("Caused by child process: " + msg, reasonCode, subReason, true);
509     }
510 
511     /**
512      * Iterate all phantom process belonging to the given app, and invokve callback
513      * for each of them.
514      */
forEachPhantomProcessOfApp(final ProcessRecord app, final Function<PhantomProcessRecord, Boolean> callback)515     void forEachPhantomProcessOfApp(final ProcessRecord app,
516             final Function<PhantomProcessRecord, Boolean> callback) {
517         synchronized (mLock) {
518             int index = mAppPhantomProcessMap.indexOfKey(app.getPid());
519             if (index >= 0) {
520                 final SparseArray<PhantomProcessRecord> array =
521                         mAppPhantomProcessMap.valueAt(index);
522                 for (int i = array.size() - 1; i >= 0; i--) {
523                     final PhantomProcessRecord r = array.valueAt(i);
524                     if (!callback.apply(r)) {
525                         break;
526                     }
527                 }
528             }
529         }
530     }
531 
532     @GuardedBy("tracker")
updateProcessCpuStatesLocked(ProcessCpuTracker tracker)533     void updateProcessCpuStatesLocked(ProcessCpuTracker tracker) {
534         synchronized (mLock) {
535             // refresh the phantom process list with the latest cpu stats results.
536             mUpdateSeq++;
537 
538             // Scan app process's accounting procs
539             lookForPhantomProcessesLocked();
540 
541             for (int i = tracker.countStats() - 1; i >= 0; i--) {
542                 final ProcessCpuTracker.Stats st = tracker.getStats(i);
543                 final PhantomProcessRecord r =
544                         getOrCreatePhantomProcessIfNeededLocked(st.name, st.uid, st.pid, false);
545                 if (r != null) {
546                     r.mUpdateSeq = mUpdateSeq;
547                     r.mCurrentCputime += st.rel_utime + st.rel_stime;
548                     if (r.mLastCputime == 0) {
549                         r.mLastCputime = r.mCurrentCputime;
550                     }
551                     r.updateAdjLocked();
552                 }
553             }
554             // remove the stale ones
555             pruneStaleProcessesLocked();
556         }
557     }
558 
dump(PrintWriter pw, String prefix)559     void dump(PrintWriter pw, String prefix) {
560         synchronized (mLock) {
561             dumpPhantomeProcessLocked(pw, prefix, "All Active App Child Processes:",
562                     mPhantomProcesses);
563             dumpPhantomeProcessLocked(pw, prefix, "All Zombie App Child Processes:",
564                     mZombiePhantomProcesses);
565         }
566     }
567 
dumpPhantomeProcessLocked(PrintWriter pw, String prefix, String headline, SparseArray<PhantomProcessRecord> list)568     void dumpPhantomeProcessLocked(PrintWriter pw, String prefix, String headline,
569             SparseArray<PhantomProcessRecord> list) {
570         final int size = list.size();
571         if (size == 0) {
572             return;
573         }
574         pw.println();
575         pw.print(prefix);
576         pw.println(headline);
577         for (int i = 0; i < size; i++) {
578             final PhantomProcessRecord proc = list.valueAt(i);
579             pw.print(prefix);
580             pw.print("  proc #");
581             pw.print(i);
582             pw.print(": ");
583             pw.println(proc.toString());
584             proc.dump(pw, prefix + "    ");
585         }
586     }
587 
588     @VisibleForTesting
589     static class Injector {
openCgroupProcs(String path)590         InputStream openCgroupProcs(String path) throws FileNotFoundException, SecurityException {
591             return new FileInputStream(path);
592         }
593 
readCgroupProcs(InputStream input, byte[] buf, int offset, int len)594         int readCgroupProcs(InputStream input, byte[] buf, int offset, int len) throws IOException {
595             return input.read(buf, offset, len);
596         }
597 
getProcessName(final int pid)598         String getProcessName(final int pid) {
599             return PhantomProcessList.getProcessName(pid);
600         }
601     }
602 }
603