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