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