diff options
11 files changed, 496 insertions, 50 deletions
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java new file mode 100644 index 000000000000..22756934abc0 --- /dev/null +++ b/core/java/android/os/AppZygote.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 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 android.os; + +import android.content.pm.ApplicationInfo; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +/** + * AppZygote is responsible for interfacing with an application-specific zygote. + * + * Application zygotes can pre-load app-specific code and data, and this interface can + * be used to spawn isolated services from such an application zygote. + * + * Note that we'll have only one instance of this per application / uid combination. + * + * @hide + */ +public class AppZygote { + private static final String LOG_TAG = "AppZygote"; + + private final Object mLock = new Object(); + + /** + * Instance that maintains the socket connection to the zygote. This is {@code null} if the + * zygote is not running or is not connected. + */ + @GuardedBy("mLock") + private ChildZygoteProcess mZygote; + + private final ApplicationInfo mAppInfo; + + public AppZygote(ApplicationInfo appInfo) { + mAppInfo = appInfo; + } + + /** + * Returns the zygote process associated with this app zygote. + * Creates the process if it's not already running. + */ + public ChildZygoteProcess getProcess() { + synchronized (mLock) { + if (mZygote != null) return mZygote; + + connectToZygoteIfNeededLocked(); + return mZygote; + } + } + + /** + * Stops the Zygote and kills the zygote process. + */ + public void stopZygote() { + synchronized (mLock) { + stopZygoteLocked(); + } + } + + public ApplicationInfo getAppInfo() { + return mAppInfo; + } + + @GuardedBy("mLock") + private void stopZygoteLocked() { + if (mZygote != null) { + // Close the connection and kill the zygote process. This will not cause + // child processes to be killed by itself. + mZygote.close(); + Process.killProcess(mZygote.getPid()); + mZygote = null; + } + } + + @GuardedBy("mLock") + private void connectToZygoteIfNeededLocked() { + String abi = mAppInfo.primaryCpuAbi != null ? mAppInfo.primaryCpuAbi : + Build.SUPPORTED_ABIS[0]; + try { + mZygote = Process.zygoteProcess.startChildZygote( + "com.android.internal.os.AppZygoteInit", + mAppInfo.processName + "_zygote", + mAppInfo.uid, + mAppInfo.uid, + null, // gids + 0, // runtimeFlags + "app_zygote", // seInfo + abi, // abi + abi, // acceptedAbiList + null); // instructionSet + + ZygoteProcess.waitForConnectionToZygote(mZygote.getPrimarySocketAddress()); + // preload application code in the zygote + Log.i(LOG_TAG, "Starting application preload."); + mZygote.preloadApp(mAppInfo, abi); + Log.i(LOG_TAG, "Application preload done."); + } catch (Exception e) { + Log.e(LOG_TAG, "Error connecting to app zygote", e); + stopZygoteLocked(); + } + } +} diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index f136cd6699a7..251c5eebadc4 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.ApplicationInfo; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.util.Log; @@ -34,6 +35,7 @@ import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -673,6 +675,36 @@ public class ZygoteProcess { } /** + * Instructs the zygote to pre-load the application code for the given Application. + * Only the app zygote supports this function. + * TODO preloadPackageForAbi() can probably be removed and the callers an use this instead. + */ + public boolean preloadApp(ApplicationInfo appInfo, String abi) throws ZygoteStartFailedEx, + IOException { + synchronized (mLock) { + ZygoteState state = openZygoteSocketIfNeeded(abi); + state.writer.write("2"); + state.writer.newLine(); + + state.writer.write("--preload-app"); + state.writer.newLine(); + + // Zygote args needs to be strings, so in order to pass ApplicationInfo, + // write it to a Parcel, and base64 the raw Parcel bytes to the other side. + Parcel parcel = Parcel.obtain(); + appInfo.writeToParcel(parcel, 0 /* flags */); + String encodedParcelData = Base64.getEncoder().encodeToString(parcel.marshall()); + parcel.recycle(); + state.writer.write(encodedParcelData); + state.writer.newLine(); + + state.writer.flush(); + + return (state.inputStream.readInt() == 0); + } + } + + /** * Instructs the zygote to pre-load the classes and native libraries at the given paths * for the specified abi. Not all zygotes support this function. */ @@ -763,6 +795,20 @@ public class ZygoteProcess { * secondary zygotes that inherit data from the zygote that this object * communicates with. This returns a new ZygoteProcess representing a connection * to the newly created zygote. Throws an exception if the zygote cannot be started. + * + * @param processClass The class to use as the child zygote's main entry + * point. + * @param niceName A more readable name to use for the process. + * @param uid The user-id under which the child zygote will run. + * @param gid The group-id under which the child zygote will run. + * @param gids Additional group-ids associated with the child zygote process. + * @param runtimeFlags Additional flags. + * @param seInfo null-ok SELinux information for the child zygote process. + * @param abi non-null the ABI of the child zygote + * @param acceptedAbiList ABIs this child zygote will accept connections for; this + * may be different from <code>abi</code> in case the children + * spawned from this Zygote only communicate using ABI-safe methods. + * @param instructionSet null-ok the instruction set to use. */ public ChildZygoteProcess startChildZygote(final String processClass, final String niceName, @@ -770,12 +816,14 @@ public class ZygoteProcess { int runtimeFlags, String seInfo, String abi, + String acceptedAbiList, String instructionSet) { // Create an unguessable address in the global abstract namespace. final LocalSocketAddress serverAddress = new LocalSocketAddress( processClass + "/" + UUID.randomUUID().toString()); - final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName()}; + final String[] extraArgs = {Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + serverAddress.getName(), + Zygote.CHILD_ZYGOTE_ABI_LIST_ARG + acceptedAbiList}; Process.ProcessStartResult result; try { diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java index 49e11b8baf51..9f7aa6a2852a 100644 --- a/core/java/android/webkit/WebViewZygote.java +++ b/core/java/android/webkit/WebViewZygote.java @@ -159,6 +159,7 @@ public class WebViewZygote { 0, // runtimeFlags "webview_zygote", // seInfo sPackage.applicationInfo.primaryCpuAbi, // abi + TextUtils.join(",", Build.SUPPORTED_ABIS), null); // instructionSet // All the work below is usually done by LoadedApk, but the zygote can't talk to diff --git a/core/java/com/android/internal/os/AppZygoteInit.java b/core/java/com/android/internal/os/AppZygoteInit.java new file mode 100644 index 000000000000..09f4b36027cf --- /dev/null +++ b/core/java/com/android/internal/os/AppZygoteInit.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 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.internal.os; + +import android.app.LoadedApk; +import android.content.pm.ApplicationInfo; +import android.net.LocalSocket; +import android.util.Log; + +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Startup class for an Application zygote process. + * + * See {@link ZygoteInit} for generic zygote startup documentation. + * + * @hide + */ +class AppZygoteInit { + public static final String TAG = "AppZygoteInit"; + + private static ZygoteServer sServer; + + private static class AppZygoteServer extends ZygoteServer { + @Override + protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList) + throws IOException { + return new AppZygoteConnection(socket, abiList); + } + } + + private static class AppZygoteConnection extends ZygoteConnection { + AppZygoteConnection(LocalSocket socket, String abiList) throws IOException { + super(socket, abiList); + } + + @Override + protected void preload() { + // Nothing to preload by default. + } + + @Override + protected boolean isPreloadComplete() { + // App zygotes don't preload any classes or resources or defaults, all of their + // preloading is package specific. + return true; + } + + @Override + protected boolean canPreloadApp() { + return true; + } + + @Override + protected void handlePreloadApp(ApplicationInfo appInfo) { + Log.i(TAG, "Beginning application preload for " + appInfo.packageName); + LoadedApk loadedApk = new LoadedApk(null, appInfo, null, null, false, true, false); + // Initialize the classLoader + ClassLoader loader = loadedApk.getClassLoader(); + + try { + DataOutputStream socketOut = getSocketOutputStream(); + socketOut.writeInt(loader != null ? 1 : 0); + } catch (IOException e) { + throw new IllegalStateException("Error writing to command socket", e); + } + + Log.i(TAG, "Application preload done"); + } + } + + public static void main(String[] argv) { + AppZygoteServer server = new AppZygoteServer(); + ChildZygoteInit.runZygoteServer(server, argv); + } +} diff --git a/core/java/com/android/internal/os/ChildZygoteInit.java b/core/java/com/android/internal/os/ChildZygoteInit.java new file mode 100644 index 000000000000..f90cd0224596 --- /dev/null +++ b/core/java/com/android/internal/os/ChildZygoteInit.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 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.internal.os; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Log; + +/** + * ChildZygoteInit is shared by both the Application and WebView zygote to initialize + * and run a (child) Zygote server. + * + * @hide + */ +public class ChildZygoteInit { + private static final String TAG = "ChildZygoteInit"; + + static String parseSocketNameFromArgs(String[] argv) { + for (String arg : argv) { + if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { + return arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length()); + } + } + + return null; + } + + static String parseAbiListFromArgs(String[] argv) { + for (String arg : argv) { + if (arg.startsWith(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG)) { + return arg.substring(Zygote.CHILD_ZYGOTE_ABI_LIST_ARG.length()); + } + } + + return null; + } + + /** + * Starts a ZygoteServer and listens for requests + * + * @param server An instance of a ZygoteServer to listen on + * @param args Passed in arguments for this ZygoteServer + */ + static void runZygoteServer(ZygoteServer server, String[] args) { + String socketName = parseSocketNameFromArgs(args); + if (socketName == null) { + throw new NullPointerException("No socketName specified"); + } + + String abiList = parseAbiListFromArgs(args); + if (abiList == null) { + throw new NullPointerException("No abiList specified"); + } + + try { + Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + } catch (ErrnoException ex) { + throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex); + } + + final Runnable caller; + try { + server.registerServerSocketAtAbstractName(socketName); + + // Add the abstract socket to the FD whitelist so that the native zygote code + // can properly detach it after forking. + Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName); + + // The select loop returns early in the child process after a fork and + // loops forever in the zygote. + caller = server.runSelectLoop(abiList); + } catch (RuntimeException e) { + Log.e(TAG, "Fatal exception:", e); + throw e; + } finally { + server.closeServerSocket(); + } + + // We're in the child process and have exited the select loop. Proceed to execute the + // command. + if (caller != null) { + caller.run(); + } + } +} diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index 9f2434e97d7a..2c8e66d010f9 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -18,11 +18,7 @@ package com.android.internal.os; import android.app.ApplicationLoaders; import android.net.LocalSocket; -import android.net.LocalServerSocket; import android.os.Build; -import android.system.ErrnoException; -import android.system.Os; -import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; import android.webkit.WebViewFactory; @@ -44,8 +40,6 @@ import java.lang.reflect.Method; class WebViewZygoteInit { public static final String TAG = "WebViewZygoteInit"; - private static ZygoteServer sServer; - private static class WebViewZygoteServer extends ZygoteServer { @Override protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList) @@ -128,47 +122,7 @@ class WebViewZygoteInit { public static void main(String argv[]) { Log.i(TAG, "Starting WebViewZygoteInit"); - String socketName = null; - for (String arg : argv) { - Log.i(TAG, arg); - if (arg.startsWith(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG)) { - socketName = arg.substring(Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG.length()); - } - } - if (socketName == null) { - throw new RuntimeException("No " + Zygote.CHILD_ZYGOTE_SOCKET_NAME_ARG + " specified"); - } - - try { - Os.prctl(OsConstants.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); - } catch (ErrnoException ex) { - throw new RuntimeException("Failed to set PR_SET_NO_NEW_PRIVS", ex); - } - - sServer = new WebViewZygoteServer(); - - final Runnable caller; - try { - sServer.registerServerSocketAtAbstractName(socketName); - - // Add the abstract socket to the FD whitelist so that the native zygote code - // can properly detach it after forking. - Zygote.nativeAllowFileAcrossFork("ABSTRACT/" + socketName); - - // The select loop returns early in the child process after a fork and - // loops forever in the zygote. - caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS)); - } catch (RuntimeException e) { - Log.e(TAG, "Fatal exception:", e); - throw e; - } finally { - sServer.closeServerSocket(); - } - - // We're in the child process and have exited the select loop. Proceed to execute the - // command. - if (caller != null) { - caller.run(); - } + WebViewZygoteServer server = new WebViewZygoteServer(); + ChildZygoteInit.runZygoteServer(server, argv); } } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 65b9fad97d89..d720c689f5de 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -98,6 +98,12 @@ public final class Zygote { */ public static final String CHILD_ZYGOTE_SOCKET_NAME_ARG = "--zygote-socket="; + /** + * An extraArg passed when a zygote process is forking a child-zygote, specifying the + * requested ABI for the child Zygote. + */ + public static final String CHILD_ZYGOTE_ABI_LIST_ARG = "--abi-list="; + private Zygote() {} /** Called for some security initialization before any fork. */ diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index f182c4d447df..5990d72d89b4 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -27,9 +27,11 @@ import static com.android.internal.os.ZygoteConnectionConstants.CONNECTION_TIMEO import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; import static com.android.internal.os.ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS; +import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalSocket; import android.os.FactoryTest; +import android.os.Parcel; import android.os.Process; import android.os.SystemProperties; import android.os.Trace; @@ -52,6 +54,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; /** * A connection that can make spawn requests. @@ -168,6 +171,21 @@ class ZygoteConnection { return null; } + if (canPreloadApp() && parsedArgs.mPreloadApp != null) { + byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); + Parcel appInfoParcel = Parcel.obtain(); + appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); + appInfoParcel.setDataPosition(0); + ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); + appInfoParcel.recycle(); + if (appInfo != null) { + handlePreloadApp(appInfo); + } else { + throw new IllegalArgumentException("Failed to deserialize --preload-app"); + } + return null; + } + if (parsedArgs.apiBlacklistExemptions != null) { handleApiBlacklistExemptions(parsedArgs.apiBlacklistExemptions); return null; @@ -341,7 +359,15 @@ class ZygoteConnection { protected void handlePreloadPackage(String packagePath, String libsPath, String libFileName, String cacheKey) { - throw new RuntimeException("Zyogte does not support package preloading"); + throw new RuntimeException("Zygote does not support package preloading"); + } + + protected boolean canPreloadApp() { + return false; + } + + protected void handlePreloadApp(ApplicationInfo aInfo) { + throw new RuntimeException("Zygote does not support app preloading"); } /** @@ -467,6 +493,12 @@ class ZygoteConnection { String preloadPackage; /** + * A Base64 string representing a serialize ApplicationInfo Parcel, + when using --preload-app. + */ + String mPreloadApp; + + /** * The native library path of the package to preload, when using --preload-package. */ String preloadPackageLibs; @@ -666,6 +698,8 @@ class ZygoteConnection { instructionSet = arg.substring(arg.indexOf('=') + 1); } else if (arg.startsWith("--app-data-dir=")) { appDataDir = arg.substring(arg.indexOf('=') + 1); + } else if (arg.equals("--preload-app")) { + mPreloadApp = args[++curArg]; } else if (arg.equals("--preload-package")) { preloadPackage = args[++curArg]; preloadPackageLibs = args[++curArg]; @@ -714,6 +748,11 @@ class ZygoteConnection { throw new IllegalArgumentException( "Unexpected arguments after --preload-package."); } + } else if (mPreloadApp != null) { + if (args.length - curArg > 0) { + throw new IllegalArgumentException( + "Unexpected arguments after --preload-app."); + } } else if (expectRuntimeArgs) { if (!seenRuntimeArgs) { throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a94fa12e4f35..ed39d8302bf7 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2503,6 +2503,9 @@ public final class ActiveServices { && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) { hostingType = "webview_service"; } + if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) { + hostingType = "app_zygote"; + } } // Not running -- get it started, and enqueue this service record diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b053979f1fcd..e171736bd9cc 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -235,6 +235,7 @@ import android.media.audiofx.AudioEffect; import android.net.Proxy; import android.net.ProxyInfo; import android.net.Uri; +import android.os.AppZygote; import android.os.BatteryStats; import android.os.Binder; import android.os.BinderProxy; @@ -452,6 +453,9 @@ public class ActivityManagerService extends IActivityManager.Stub // before we decide it must be hung. static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; + // How long we wait to kill an application zygote, after the last process using + // it has gone away. + static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000; /** * How long we wait for an provider to be published. Should be longer than * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}. @@ -1441,6 +1445,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PUSH_TEMP_WHITELIST_UI_MSG = 68; static final int SERVICE_FOREGROUND_CRASH_MSG = 69; static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70; + static final int KILL_APP_ZYGOTE_MSG = 71; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1644,6 +1649,12 @@ public class ActivityManagerService extends IActivityManager.Stub false, userId, reason); } } break; + case KILL_APP_ZYGOTE_MSG: { + synchronized (ActivityManagerService.this) { + final AppZygote appZygote = (AppZygote) msg.obj; + mProcessList.killAppZygoteIfNeededLocked(appZygote); + } + } break; case CHECK_EXCESSIVE_POWER_USE_MSG: { synchronized (ActivityManagerService.this) { checkExcessivePowerUsageLocked(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 895a86ba9fc7..896452b4ad62 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -34,6 +34,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_DELAY_MS; +import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_MSG; import static com.android.server.am.ActivityManagerService.PERSISTENT_MASK; import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT; import static com.android.server.am.ActivityManagerService.PROC_START_TIMEOUT_MSG; @@ -58,6 +60,7 @@ import android.graphics.Point; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.net.Uri; +import android.os.AppZygote; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -75,6 +78,7 @@ import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.EventLog; import android.util.LongSparseArray; import android.util.Slog; @@ -354,6 +358,17 @@ public final class ProcessList { final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>(); /** + * The currently running application zygotes. + */ + final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>(); + + /** + * The processes that are forked off an application zygote. + */ + final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses = + new ArrayMap<AppZygote, ArrayList<ProcessRecord>>(); + + /** * Counter for assigning isolated process uids, to avoid frequently reusing the * same ones. */ @@ -1505,6 +1520,54 @@ public final class ProcessList { } } + @GuardedBy("mService") + public void killAppZygoteIfNeededLocked(AppZygote appZygote) { + final ApplicationInfo appInfo = appZygote.getAppInfo(); + ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote); + if (zygoteProcesses.size() == 0) { // Only remove if no longer in use now + mAppZygotes.remove(appInfo.processName, appInfo.uid); + mAppZygoteProcesses.remove(appZygote); + appZygote.stopZygote(); + } + } + + @GuardedBy("mService") + private void removeProcessFromAppZygoteLocked(final ProcessRecord app) { + final AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid); + if (appZygote != null) { + ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote); + zygoteProcesses.remove(app); + if (zygoteProcesses.size() == 0) { + Message msg = mService.mHandler.obtainMessage(KILL_APP_ZYGOTE_MSG); + msg.obj = appZygote; + mService.mHandler.sendMessageDelayed(msg, KILL_APP_ZYGOTE_DELAY_MS); + } + } + } + + private AppZygote createAppZygoteForProcessIfNeeded(final ProcessRecord app) { + synchronized (mService) { + AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid); + final ArrayList<ProcessRecord> zygoteProcessList; + if (appZygote == null) { + appZygote = new AppZygote(app.info); + mAppZygotes.put(app.info.processName, app.info.uid, appZygote); + zygoteProcessList = new ArrayList<ProcessRecord>(); + mAppZygoteProcesses.put(appZygote, zygoteProcessList); + } else { + mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG, appZygote); + zygoteProcessList = mAppZygoteProcesses.get(appZygote); + } + // Note that we already add the app to mAppZygoteProcesses here; + // this is so that another thread can't come in and kill the zygote + // before we've even tried to start the process. If the process launch + // goes wrong, we'll clean this up in removeProcessNameLocked() + zygoteProcessList.add(app); + + return appZygote; + } + } + private Process.ProcessStartResult startProcess(String hostingType, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, @@ -1525,6 +1588,15 @@ public final class ProcessList { app.info.dataDir, null, app.info.packageName, packageNames, visibleVolIds, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); + } else if (hostingType.equals("app_zygote")) { + final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app); + + startResult = appZygote.getProcess().start(entryPoint, + app.processName, uid, uid, gids, runtimeFlags, mountExternal, + app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, + app.info.dataDir, null, app.info.packageName, + packageNames, visibleVolIds, + new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, @@ -2093,6 +2165,12 @@ public final class ProcessList { old.uidRecord = null; } mIsolatedProcesses.remove(uid); + // Remove the (expected) ProcessRecord from the app zygote + final ProcessRecord record = expecting != null ? expecting : old; + if (record != null && record.isolated) { + removeProcessFromAppZygoteLocked(record); + } + return old; } |