blob: e2fcf08ce82b75927dd9fae35ca4944f7253ed37 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.am;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
import android.os.Handler;
import android.os.Process;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.ProcessCpuTracker;
import libcore.io.IoUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
/**
* Activity manager code dealing with phantom processes.
*/
public final class PhantomProcessList {
static final String TAG = TAG_WITH_CLASS_NAME ? "PhantomProcessList" : TAG_AM;
final Object mLock = new Object();
/**
* All of the phantom process record we track, key is the pid of the process.
*/
@GuardedBy("mLock")
final SparseArray<PhantomProcessRecord> mPhantomProcesses = new SparseArray<>();
/**
* The mapping between app processes and their phantom processess, outer key is the pid of
* the app process, while the inner key is the pid of the phantom process.
*/
@GuardedBy("mLock")
final SparseArray<SparseArray<PhantomProcessRecord>> mAppPhantomProcessMap =
new SparseArray<>();
/**
* The mapping of the pidfd to PhantomProcessRecord.
*/
@GuardedBy("mLock")
final SparseArray<PhantomProcessRecord> mPhantomProcessesPidFds = new SparseArray<>();
/**
* The list of phantom processes tha's being signaled to be killed but still undead yet.
*/
@GuardedBy("mLock")
final SparseArray<PhantomProcessRecord> mZombiePhantomProcesses = new SparseArray<>();
@GuardedBy("mLock")
private final ArrayList<PhantomProcessRecord> mTempPhantomProcesses = new ArrayList<>();
@GuardedBy("mLock")
private boolean mTrimPhantomProcessScheduled = false;
@GuardedBy("mLock")
int mUpdateSeq;
private final ActivityManagerService mService;
private final Handler mKillHandler;
PhantomProcessList(final ActivityManagerService service) {
mService = service;
mKillHandler = service.mProcessList.sKillHandler;
}
/**
* Get the existing phantom process record, or create if it's not existing yet;
* however, before creating it, we'll check if this is really a phantom process
* and we'll return null if it's not.
*/
@GuardedBy("mLock")
PhantomProcessRecord getOrCreatePhantomProcessIfNeededLocked(final String processName,
final int uid, final int pid) {
// First check if it's actually an app process we know
if (isAppProcess(pid)) {
return null;
}
// Have we already been aware of this?
final int index = mPhantomProcesses.indexOfKey(pid);
if (index >= 0) {
final PhantomProcessRecord proc = mPhantomProcesses.valueAt(index);
if (proc.equals(processName, uid, pid)) {
return proc;
}
// Somehow our record doesn't match, remove it anyway
Slog.w(TAG, "Stale " + proc + ", removing");
mPhantomProcesses.removeAt(index);
} else {
// Is this one of the zombie processes we've known?
final int idx = mZombiePhantomProcesses.indexOfKey(pid);
if (idx >= 0) {
final PhantomProcessRecord proc = mZombiePhantomProcesses.valueAt(idx);
if (proc.equals(processName, uid, pid)) {
return proc;
}
// Our zombie process information is outdated, let's remove this one, it shoud
// have been gone.
mZombiePhantomProcesses.removeAt(idx);
}
}
int ppid = getParentPid(pid);
// Walk through its parents and see if it could be traced back to an app process.
while (ppid > 1) {
if (isAppProcess(ppid)) {
// It's a phantom process, bookkeep it
try {
final PhantomProcessRecord proc = new PhantomProcessRecord(
processName, uid, pid, ppid, mService,
this::onPhantomProcessKilledLocked);
proc.mUpdateSeq = mUpdateSeq;
mPhantomProcesses.put(pid, proc);
SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(ppid);
if (array == null) {
array = new SparseArray<>();
mAppPhantomProcessMap.put(ppid, array);
}
array.put(pid, proc);
if (proc.mPidFd != null) {
mKillHandler.getLooper().getQueue().addOnFileDescriptorEventListener(
proc.mPidFd, EVENT_INPUT | EVENT_ERROR,
this::onPhantomProcessFdEvent);
mPhantomProcessesPidFds.put(proc.mPidFd.getInt$(), proc);
}
scheduleTrimPhantomProcessesLocked();
return proc;
} catch (IllegalStateException e) {
return null;
}
}
ppid = getParentPid(ppid);
}
return null;
}
private static int getParentPid(int pid) {
try {
return Process.getParentPid(pid);
} catch (Exception e) {
}
return -1;
}
private boolean isAppProcess(int pid) {
synchronized (mService.mPidsSelfLocked) {
return mService.mPidsSelfLocked.get(pid) != null;
}
}
private int onPhantomProcessFdEvent(FileDescriptor fd, int events) {
synchronized (mLock) {
final PhantomProcessRecord proc = mPhantomProcessesPidFds.get(fd.getInt$());
if ((events & EVENT_INPUT) != 0) {
proc.onProcDied(true);
} else {
// EVENT_ERROR, kill the process
proc.killLocked("Process error", true);
}
}
return 0;
}
@GuardedBy("mLock")
private void onPhantomProcessKilledLocked(final PhantomProcessRecord proc) {
if (proc.mPidFd != null && proc.mPidFd.valid()) {
mKillHandler.getLooper().getQueue()
.removeOnFileDescriptorEventListener(proc.mPidFd);
mPhantomProcessesPidFds.remove(proc.mPidFd.getInt$());
IoUtils.closeQuietly(proc.mPidFd);
}
mPhantomProcesses.remove(proc.mPid);
final int index = mAppPhantomProcessMap.indexOfKey(proc.mPpid);
if (index < 0) {
return;
}
SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.valueAt(index);
array.remove(proc.mPid);
if (array.size() == 0) {
mAppPhantomProcessMap.removeAt(index);
}
if (proc.mZombie) {
// If it's not really dead, bookkeep it
mZombiePhantomProcesses.put(proc.mPid, proc);
} else {
// In case of race condition, let's try to remove it from zombie list
mZombiePhantomProcesses.remove(proc.mPid);
}
}
@GuardedBy("mLock")
private void scheduleTrimPhantomProcessesLocked() {
if (!mTrimPhantomProcessScheduled) {
mTrimPhantomProcessScheduled = true;
mService.mHandler.post(this::trimPhantomProcessesIfNecessary);
}
}
/**
* Clamp the number of phantom processes to
* {@link ActivityManagerConstants#MAX_PHANTOM_PROCESSE}, kills those surpluses in the
* order of the oom adjs of their parent process.
*/
void trimPhantomProcessesIfNecessary() {
synchronized (mLock) {
mTrimPhantomProcessScheduled = false;
if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
}
synchronized (mService.mPidsSelfLocked) {
Collections.sort(mTempPhantomProcesses, (a, b) -> {
final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
if (ra == null) {
// parent is gone, this process should have been killed too
return 1;
}
final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
if (rb == null) {
// parent is gone, this process should have been killed too
return -1;
}
if (ra.curAdj != rb.curAdj) {
return ra.curAdj - rb.curAdj;
}
if (a.mKnownSince != b.mKnownSince) {
// In case of identical oom adj, younger one first
return a.mKnownSince < b.mKnownSince ? 1 : -1;
}
return 0;
});
}
for (int i = mTempPhantomProcesses.size() - 1;
i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
proc.killLocked("Trimming phantom processes", true);
}
mTempPhantomProcesses.clear();
}
}
}
/**
* Remove all entries with outdated seq num.
*/
@GuardedBy("mLock")
void pruneStaleProcessesLocked() {
for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
final PhantomProcessRecord proc = mPhantomProcesses.valueAt(i);
if (proc.mUpdateSeq < mUpdateSeq) {
if (DEBUG_PROCESSES) {
Slog.v(TAG, "Pruning " + proc + " as it should have been dead.");
}
proc.killLocked("Stale process", true);
}
}
for (int i = mZombiePhantomProcesses.size() - 1; i >= 0; i--) {
final PhantomProcessRecord proc = mZombiePhantomProcesses.valueAt(i);
if (proc.mUpdateSeq < mUpdateSeq) {
if (DEBUG_PROCESSES) {
Slog.v(TAG, "Pruning " + proc + " as it should have been dead.");
}
}
}
}
/**
* Kill the given phantom process, all its siblings (if any) and their parent process
*/
@GuardedBy("mService")
void killPhantomProcessGroupLocked(ProcessRecord app, PhantomProcessRecord proc,
@Reason int reasonCode, @SubReason int subReason, String msg) {
synchronized (mLock) {
int index = mAppPhantomProcessMap.indexOfKey(proc.mPpid);
if (index >= 0) {
final SparseArray<PhantomProcessRecord> array =
mAppPhantomProcessMap.valueAt(index);
for (int i = array.size() - 1; i >= 0; i--) {
final PhantomProcessRecord r = array.valueAt(i);
if (r == proc) {
r.killLocked(msg, true);
} else {
r.killLocked("Caused by siling process: " + msg, false);
}
}
}
}
// Lastly, kill the parent process too
app.kill("Caused by child process: " + msg, reasonCode, subReason, true);
}
/**
* Iterate all phantom process belonging to the given app, and invokve callback
* for each of them.
*/
void forEachPhantomProcessOfApp(final ProcessRecord app,
final Function<PhantomProcessRecord, Boolean> callback) {
synchronized (mLock) {
int index = mAppPhantomProcessMap.indexOfKey(app.pid);
if (index >= 0) {
final SparseArray<PhantomProcessRecord> array =
mAppPhantomProcessMap.valueAt(index);
for (int i = array.size() - 1; i >= 0; i--) {
final PhantomProcessRecord r = array.valueAt(i);
if (!callback.apply(r)) {
break;
}
}
}
}
}
@GuardedBy("tracker")
void updateProcessCpuStatesLocked(ProcessCpuTracker tracker) {
synchronized (mLock) {
// refresh the phantom process list with the latest cpu stats results.
mUpdateSeq++;
for (int i = tracker.countStats() - 1; i >= 0; i--) {
final ProcessCpuTracker.Stats st = tracker.getStats(i);
final PhantomProcessRecord r =
getOrCreatePhantomProcessIfNeededLocked(st.name, st.uid, st.pid);
if (r != null) {
r.mUpdateSeq = mUpdateSeq;
r.mCurrentCputime += st.rel_utime + st.rel_stime;
if (r.mLastCputime == 0) {
r.mLastCputime = r.mCurrentCputime;
}
r.updateAdjLocked();
}
}
// remove the stale ones
pruneStaleProcessesLocked();
}
}
void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
dumpPhantomeProcessLocked(pw, prefix, "All Active App Child Processes:",
mPhantomProcesses);
dumpPhantomeProcessLocked(pw, prefix, "All Zombie App Child Processes:",
mZombiePhantomProcesses);
}
}
void dumpPhantomeProcessLocked(PrintWriter pw, String prefix, String headline,
SparseArray<PhantomProcessRecord> list) {
final int size = list.size();
if (size == 0) {
return;
}
pw.println();
pw.print(prefix);
pw.println(headline);
for (int i = 0; i < size; i++) {
final PhantomProcessRecord proc = list.valueAt(i);
pw.print(prefix);
pw.print(" proc #");
pw.print(i);
pw.print(": ");
pw.println(proc.toString());
proc.dump(pw, prefix + " ");
}
}
}