blob: 0156ee5bb018b27917f29176589364425f5e1da3 [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.Process.PROC_NEWLINE_TERM;
import static android.os.Process.PROC_OUT_LONG;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.os.Handler;
import android.os.Process;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.function.Consumer;
/**
* The "phantom" app processes, which are forked by app processes so we are not aware of
* them until we walk through the process list in /proc.
*/
public final class PhantomProcessRecord {
static final String TAG = TAG_WITH_CLASS_NAME ? "PhantomProcessRecord" : TAG_AM;
static final long[] LONG_OUT = new long[1];
static final int[] LONG_FORMAT = new int[] {PROC_NEWLINE_TERM | PROC_OUT_LONG};
final String mProcessName; // name of the process
final int mUid; // uid of the process
final int mPid; // The id of the process
final int mPpid; // Ancestor (managed app process) pid of the process
final long mKnownSince; // The timestamp when we're aware of the process
final FileDescriptor mPidFd; // The fd to monitor the termination of this process
long mLastCputime; // How long proc has run CPU at last check
long mCurrentCputime; // How long proc has run CPU most recently
int mUpdateSeq; // Seq no, indicating the last check on this process
int mAdj; // The last known oom adj score
boolean mKilled; // Whether it has been killed by us or not
boolean mZombie; // Whether it was signaled to be killed but timed out
String mStringName; // Caching of the toString() result
final ActivityManagerService mService;
final Object mLock;
final Consumer<PhantomProcessRecord> mOnKillListener;
final Handler mKillHandler;
PhantomProcessRecord(final String processName, final int uid, final int pid,
final int ppid, final ActivityManagerService service,
final Consumer<PhantomProcessRecord> onKillListener) throws IllegalStateException {
mProcessName = processName;
mUid = uid;
mPid = pid;
mPpid = ppid;
mKilled = false;
mAdj = ProcessList.NATIVE_ADJ;
mKnownSince = SystemClock.elapsedRealtime();
mService = service;
mLock = service.mPhantomProcessList.mLock;
mOnKillListener = onKillListener;
mKillHandler = service.mProcessList.sKillHandler;
if (Process.supportsPidFd()) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
mPidFd = Process.openPidFd(pid, 0);
if (mPidFd == null) {
throw new IllegalStateException();
}
} catch (IOException e) {
// Maybe a race condition, the process is gone.
Slog.w(TAG, "Unable to open process " + pid + ", it might be gone");
IllegalStateException ex = new IllegalStateException();
ex.initCause(e);
throw ex;
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
} else {
mPidFd = null;
}
}
@GuardedBy("mLock")
void killLocked(String reason, boolean noisy) {
if (!mKilled) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (noisy || mUid == mService.mCurOomAdjUid) {
mService.reportUidInfoMessageLocked(TAG,
"Killing " + toString() + ": " + reason, mUid);
}
if (mPid > 0) {
EventLog.writeEvent(EventLogTags.AM_KILL, UserHandle.getUserId(mUid),
mPid, mProcessName, mAdj, reason);
if (!Process.supportsPidFd()) {
onProcDied(false);
} else {
// We'll notify the listener when we're notified it's dead.
// Meanwhile, we'd also need handle the case of zombie processes.
mKillHandler.postDelayed(mProcKillTimer, this,
ProcessList.PROC_KILL_TIMEOUT);
}
Process.killProcessQuiet(mPid);
ProcessList.killProcessGroup(mUid, mPid);
}
mKilled = true;
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
private Runnable mProcKillTimer = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
// The process is maybe in either D or Z state.
Slog.w(TAG, "Process " + toString() + " is still alive after "
+ ProcessList.PROC_KILL_TIMEOUT + "ms");
// Force a cleanup as we can't keep the fd open forever
mZombie = true;
onProcDied(false);
// But still bookkeep it, so it won't be added as a new one if it's spotted again.
}
}
};
@GuardedBy("mLock")
void updateAdjLocked() {
if (Process.readProcFile("/proc/" + mPid + "/oom_score_adj",
LONG_FORMAT, null, LONG_OUT, null)) {
mAdj = (int) LONG_OUT[0];
}
}
@GuardedBy("mLock")
void onProcDied(boolean reallyDead) {
if (reallyDead) {
Slog.i(TAG, "Process " + toString() + " died");
}
mKillHandler.removeCallbacks(mProcKillTimer, this);
if (mOnKillListener != null) {
mOnKillListener.accept(this);
}
}
@Override
public String toString() {
if (mStringName != null) {
return mStringName;
}
StringBuilder sb = new StringBuilder(128);
sb.append("PhantomProcessRecord {");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
sb.append(mPid);
sb.append(':');
sb.append(mPpid);
sb.append(':');
sb.append(mProcessName);
sb.append('/');
if (mUid < Process.FIRST_APPLICATION_UID) {
sb.append(mUid);
} else {
sb.append('u');
sb.append(UserHandle.getUserId(mUid));
int appId = UserHandle.getAppId(mUid);
if (appId >= Process.FIRST_APPLICATION_UID) {
sb.append('a');
sb.append(appId - Process.FIRST_APPLICATION_UID);
} else {
sb.append('s');
sb.append(appId);
}
if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
sb.append('i');
sb.append(appId - Process.FIRST_ISOLATED_UID);
}
}
sb.append('}');
return mStringName = sb.toString();
}
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.elapsedRealtime();
pw.print(prefix);
pw.print("user #");
pw.print(UserHandle.getUserId(mUid));
pw.print(" uid=");
pw.print(mUid);
pw.print(" pid=");
pw.print(mPid);
pw.print(" ppid=");
pw.print(mPpid);
pw.print(" knownSince=");
TimeUtils.formatDuration(mKnownSince, now, pw);
pw.print(" killed=");
pw.println(mKilled);
pw.print(prefix);
pw.print("lastCpuTime=");
pw.print(mLastCputime);
if (mLastCputime > 0) {
pw.print(" timeUsed=");
TimeUtils.formatDuration(mCurrentCputime - mLastCputime, pw);
}
pw.print(" oom adj=");
pw.print(mAdj);
pw.print(" seq=");
pw.println(mUpdateSeq);
}
boolean equals(final String processName, final int uid, final int pid) {
return mUid == uid && mPid == pid && TextUtils.equals(mProcessName, processName);
}
}