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