/* * Copyright (C) 2007 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 static android.system.OsConstants.S_IRWXG; import static android.system.OsConstants.S_IRWXO; import android.content.res.Resources; import android.content.res.TypedArray; import android.net.LocalServerSocket; import android.opengl.EGL14; import android.os.Build; import android.os.Debug; import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.EventLog; import android.util.Log; import android.util.Slog; import android.webkit.WebViewFactory; import dalvik.system.DexFile; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; import libcore.io.IoUtils; import java.io.BufferedReader; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; /** * Startup class for the zygote process. * * Pre-initializes some classes, and then waits for commands on a UNIX domain * socket. Based on these commands, forks off child processes that inherit * the initial state of the VM. * * Please see {@link ZygoteConnection.Arguments} for documentation on the * client protocol. * * @hide */ public class ZygoteInit { private static final String TAG = "Zygote"; private static final String PROPERTY_DISABLE_OPENGL_PRELOADING = "ro.zygote.disable_gl_preload"; private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; private static final int LOG_BOOT_PROGRESS_PRELOAD_START = 3020; private static final int LOG_BOOT_PROGRESS_PRELOAD_END = 3030; /** when preloading, GC after allocating this many bytes */ private static final int PRELOAD_GC_THRESHOLD = 50000; private static final String ABI_LIST_ARG = "--abi-list="; private static final String SOCKET_NAME_ARG = "--socket-name="; private static LocalServerSocket sServerSocket; /** * Used to pre-load resources. We hold a global reference on it so it * never gets destroyed. */ private static Resources mResources; /** * The number of times that the main Zygote loop * should run before calling gc() again. */ static final int GC_LOOP_COUNT = 10; /** * The name of a resource file that contains classes to preload. */ private static final String PRELOADED_CLASSES = "preloaded-classes"; /** Controls whether we should preload resources during zygote init. */ private static final boolean PRELOAD_RESOURCES = true; /** * Invokes a static "main(argv[]) method on class "className". * Converts various failing exceptions into RuntimeExceptions, with * the assumption that they will then cause the VM instance to exit. * * @param loader class loader to use * @param className Fully-qualified class name * @param argv Argument vector for main() */ static void invokeStaticMain(ClassLoader loader, String className, String[] argv) throws ZygoteInit.MethodAndArgsCaller { Class cl; try { cl = loader.loadClass(className); } catch (ClassNotFoundException ex) { throw new RuntimeException( "Missing class when invoking static main " + className, ex); } Method m; try { m = cl.getMethod("main", new Class[] { String[].class }); } catch (NoSuchMethodException ex) { throw new RuntimeException( "Missing static main on " + className, ex); } catch (SecurityException ex) { throw new RuntimeException( "Problem getting static main on " + className, ex); } int modifiers = m.getModifiers(); if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) { throw new RuntimeException( "Main method is not public and static on " + className); } /* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */ throw new ZygoteInit.MethodAndArgsCaller(m, argv); } /** * Registers a server socket for zygote command connections * * @throws RuntimeException when open fails */ private static void registerZygoteSocket(String socketName) { if (sServerSocket == null) { int fileDesc; final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName; try { String env = System.getenv(fullSocketName); fileDesc = Integer.parseInt(env); } catch (RuntimeException ex) { throw new RuntimeException(fullSocketName + " unset or invalid", ex); } try { sServerSocket = new LocalServerSocket( createFileDescriptor(fileDesc)); } catch (IOException ex) { throw new RuntimeException( "Error binding to local socket '" + fileDesc + "'", ex); } } } /** * Waits for and accepts a single command connection. Throws * RuntimeException on failure. */ private static ZygoteConnection acceptCommandPeer(String abiList) { try { return new ZygoteConnection(sServerSocket.accept(), abiList); } catch (IOException ex) { throw new RuntimeException( "IOException during accept()", ex); } } /** * Close and clean up zygote sockets. Called on shutdown and on the * child's exit path. */ static void closeServerSocket() { try { if (sServerSocket != null) { FileDescriptor fd = sServerSocket.getFileDescriptor(); sServerSocket.close(); if (fd != null) { Os.close(fd); } } } catch (IOException ex) { Log.e(TAG, "Zygote: error closing sockets", ex); } catch (ErrnoException ex) { Log.e(TAG, "Zygote: error closing descriptor", ex); } sServerSocket = null; } /** * Return the server socket's underlying file descriptor, so that * ZygoteConnection can pass it to the native code for proper * closure after a child process is forked off. */ static FileDescriptor getServerSocketFileDescriptor() { return sServerSocket.getFileDescriptor(); } private static final int UNPRIVILEGED_UID = 9999; private static final int UNPRIVILEGED_GID = 9999; private static final int ROOT_UID = 0; private static final int ROOT_GID = 0; /** * Sets effective user ID. */ private static void setEffectiveUser(int uid) { int errno = setreuid(ROOT_UID, uid); if (errno != 0) { Log.e(TAG, "setreuid() failed. errno: " + errno); } } /** * Sets effective group ID. */ private static void setEffectiveGroup(int gid) { int errno = setregid(ROOT_GID, gid); if (errno != 0) { Log.e(TAG, "setregid() failed. errno: " + errno); } } static void preload() { Log.d(TAG, "begin preload"); preloadClasses(); preloadResources(); preloadOpenGL(); preloadSharedLibraries(); // Ask the WebViewFactory to do any initialization that must run in the zygote process, // for memory sharing purposes. WebViewFactory.prepareWebViewInZygote(); Log.d(TAG, "end preload"); } private static void preloadSharedLibraries() { Log.i(TAG, "Preloading shared libraries..."); System.loadLibrary("android"); System.loadLibrary("compiler_rt"); System.loadLibrary("jnigraphics"); } private static void preloadOpenGL() { if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false)) { EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); } } /** * Performs Zygote process initialization. Loads and initializes * commonly used classes. * * Most classes only cause a few hundred bytes to be allocated, but * a few will allocate a dozen Kbytes (in one case, 500+K). */ private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream( PRELOADED_CLASSES); if (is == null) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "."); } else { Log.i(TAG, "Preloading classes..."); long startTime = SystemClock.uptimeMillis(); // Drop root perms while running static initializers. setEffectiveGroup(UNPRIVILEGED_GID); setEffectiveUser(UNPRIVILEGED_UID); // Alter the target heap utilization. With explicit GCs this // is not likely to have any effect. float defaultUtilization = runtime.getTargetHeapUtilization(); runtime.setTargetHeapUtilization(0.8f); // Start with a clean slate. System.gc(); runtime.runFinalizationSync(); Debug.startAllocCounting(); try { BufferedReader br = new BufferedReader(new InputStreamReader(is), 256); int count = 0; String line; while ((line = br.readLine()) != null) { // Skip comments and blank lines. line = line.trim(); if (line.startsWith("#") || line.equals("")) { continue; } try { if (false) { Log.v(TAG, "Preloading " + line + "..."); } Class.forName(line); if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) { if (false) { Log.v(TAG, " GC at " + Debug.getGlobalAllocSize()); } System.gc(); runtime.runFinalizationSync(); Debug.resetGlobalAllocSize(); } count++; } catch (ClassNotFoundException e) { Log.w(TAG, "Class not found for preloading: " + line); } catch (UnsatisfiedLinkError e) { Log.w(TAG, "Problem preloading " + line + ": " + e); } catch (Throwable t) { Log.e(TAG, "Error preloading " + line + ".", t); if (t instanceof Error) { throw (Error) t; } if (t instanceof RuntimeException) { throw (RuntimeException) t; } throw new RuntimeException(t); } } Log.i(TAG, "...preloaded " + count + " classes in " + (SystemClock.uptimeMillis()-startTime) + "ms."); } catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { IoUtils.closeQuietly(is); // Restore default. runtime.setTargetHeapUtilization(defaultUtilization); // Fill in dex caches with classes, fields, and methods brought in by preloading. runtime.preloadDexCaches(); Debug.stopAllocCounting(); // Bring back root. We'll need it later. setEffectiveUser(ROOT_UID); setEffectiveGroup(ROOT_GID); } } } /** * Load in commonly used resources, so they can be shared across * processes. * * These tend to be a few Kbytes, but are frequently in the 20-40K * range, and occasionally even larger. */ private static void preloadResources() { final VMRuntime runtime = VMRuntime.getRuntime(); Debug.startAllocCounting(); try { System.gc(); runtime.runFinalizationSync(); mResources = Resources.getSystem(); mResources.startPreloading(); if (PRELOAD_RESOURCES) { Log.i(TAG, "Preloading resources..."); long startTime = SystemClock.uptimeMillis(); TypedArray ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_drawables); int N = preloadDrawables(runtime, ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); startTime = SystemClock.uptimeMillis(); ar = mResources.obtainTypedArray( com.android.internal.R.array.preloaded_color_state_lists); N = preloadColorStateLists(runtime, ar); ar.recycle(); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); } mResources.finishPreloading(); } catch (RuntimeException e) { Log.w(TAG, "Failure preloading resources", e); } finally { Debug.stopAllocCounting(); } } private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) { int N = ar.length(); for (int i=0; i PRELOAD_GC_THRESHOLD) { if (false) { Log.v(TAG, " GC at " + Debug.getGlobalAllocSize()); } System.gc(); runtime.runFinalizationSync(); Debug.resetGlobalAllocSize(); } int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); } if (id != 0) { if (mResources.getColorStateList(id) == null) { throw new IllegalArgumentException( "Unable to find preloaded color resource #0x" + Integer.toHexString(id) + " (" + ar.getString(i) + ")"); } } } return N; } private static int preloadDrawables(VMRuntime runtime, TypedArray ar) { int N = ar.length(); for (int i=0; i PRELOAD_GC_THRESHOLD) { if (false) { Log.v(TAG, " GC at " + Debug.getGlobalAllocSize()); } System.gc(); runtime.runFinalizationSync(); Debug.resetGlobalAllocSize(); } int id = ar.getResourceId(i, 0); if (false) { Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); } if (id != 0) { if (mResources.getDrawable(id, null) == null) { throw new IllegalArgumentException( "Unable to find preloaded drawable resource #0x" + Integer.toHexString(id) + " (" + ar.getString(i) + ")"); } } } return N; } /** * Runs several special GCs to try to clean up a few generations of * softly- and final-reachable objects, along with any other garbage. * This is only useful just before a fork(). */ /*package*/ static void gc() { final VMRuntime runtime = VMRuntime.getRuntime(); /* runFinalizationSync() lets finalizers be called in Zygote, * which doesn't have a HeapWorker thread. */ System.gc(); runtime.runFinalizationSync(); System.gc(); runtime.runFinalizationSync(); System.gc(); runtime.runFinalizationSync(); } /** * Finish remaining work for the newly forked system server process. */ private static void handleSystemServerProcess( ZygoteConnection.Arguments parsedArgs) throws ZygoteInit.MethodAndArgsCaller { closeServerSocket(); // set umask to 0077 so new files and directories will default to owner-only permissions. Os.umask(S_IRWXG | S_IRWXO); if (parsedArgs.niceName != null) { Process.setArgV0(parsedArgs.niceName); } final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH"); if (systemServerClasspath != null) { performSystemServerDexOpt(systemServerClasspath); } if (parsedArgs.invokeWith != null) { String[] args = parsedArgs.remainingArgs; // If we have a non-null system server class path, we'll have to duplicate the // existing arguments and append the classpath to it. ART will handle the classpath // correctly when we exec a new process. if (systemServerClasspath != null) { String[] amendedArgs = new String[args.length + 2]; amendedArgs[0] = "-cp"; amendedArgs[1] = systemServerClasspath; System.arraycopy(parsedArgs.remainingArgs, 0, amendedArgs, 2, parsedArgs.remainingArgs.length); } WrapperInit.execApplication(parsedArgs.invokeWith, parsedArgs.niceName, parsedArgs.targetSdkVersion, null, args); } else { ClassLoader cl = null; if (systemServerClasspath != null) { cl = new PathClassLoader(systemServerClasspath, ClassLoader.getSystemClassLoader()); Thread.currentThread().setContextClassLoader(cl); } /* * Pass the remaining arguments to SystemServer. */ RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl); } /* should never reach here */ } /** * Performs dex-opt on the elements of {@code classPath}, if needed. We * choose the instruction set of the current runtime. */ private static void performSystemServerDexOpt(String classPath) { final String[] classPathElements = classPath.split(":"); final InstallerConnection installer = new InstallerConnection(); final String instructionSet = VMRuntime.getRuntime().vmInstructionSet(); try { for (String classPathElement : classPathElements) { final byte dexopt = DexFile.isDexOptNeededInternal(classPathElement, "*", instructionSet, false /* defer */); if (dexopt == DexFile.DEXOPT_NEEDED) { installer.dexopt(classPathElement, Process.SYSTEM_UID, false, instructionSet); } else if (dexopt == DexFile.PATCHOAT_NEEDED) { installer.patchoat(classPathElement, Process.SYSTEM_UID, false, instructionSet); } } } catch (IOException ioe) { throw new RuntimeException("Error starting system_server", ioe); } finally { installer.disconnect(); } } /** * Prepare the arguments and fork for the system server process. */ private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller, RuntimeException { long capabilities = posixCapabilitiesAsBits( OsConstants.CAP_BLOCK_SUSPEND, OsConstants.CAP_KILL, OsConstants.CAP_NET_ADMIN, OsConstants.CAP_NET_BIND_SERVICE, OsConstants.CAP_NET_BROADCAST, OsConstants.CAP_NET_RAW, OsConstants.CAP_SYS_MODULE, OsConstants.CAP_SYS_NICE, OsConstants.CAP_SYS_RESOURCE, OsConstants.CAP_SYS_TIME, OsConstants.CAP_SYS_TTY_CONFIG ); /* Hardcoded command line to start the system server */ String args[] = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1032,3001,3002,3003,3006,3007", "--capabilities=" + capabilities + "," + capabilities, "--runtime-init", "--nice-name=system_server", "com.android.server.SystemServer", }; ZygoteConnection.Arguments parsedArgs = null; int pid; try { parsedArgs = new ZygoteConnection.Arguments(args); ZygoteConnection.applyDebuggerSystemProperty(parsedArgs); ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.debugFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ if (pid == 0) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } handleSystemServerProcess(parsedArgs); } return true; } /** * Gets the bit array representation of the provided list of POSIX capabilities. */ private static long posixCapabilitiesAsBits(int... capabilities) { long result = 0; for (int capability : capabilities) { if ((capability < 0) || (capability > OsConstants.CAP_LAST_CAP)) { throw new IllegalArgumentException(String.valueOf(capability)); } result |= (1L << capability); } return result; } public static void main(String argv[]) { try { // Start profiling the zygote initialization. SamplingProfilerIntegration.start(); boolean startSystemServer = false; String socketName = "zygote"; String abiList = null; for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { socketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } if (abiList == null) { throw new RuntimeException("No ABI list supplied."); } registerZygoteSocket(socketName); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); preload(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); // Finish profiling the zygote initialization. SamplingProfilerIntegration.writeZygoteSnapshot(); // Do an initial gc to clean up after startup gc(); // Disable tracing so that forked processes do not inherit stale tracing tags from // Zygote. Trace.setTracingEnabled(false); if (startSystemServer) { startSystemServer(abiList, socketName); } Log.i(TAG, "Accepting command socket connections"); runSelectLoop(abiList); closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } } /** * Return {@code true} if this device configuration has another zygote. * * We determine this by comparing the device ABI list with this zygotes * list. If this zygote supports all ABIs this device supports, there won't * be another zygote. */ private static boolean hasSecondZygote(String abiList) { return !SystemProperties.get("ro.product.cpu.abilist").equals(abiList); } private static void waitForSecondaryZygote(String socketName) { String otherZygoteName = Process.ZYGOTE_SOCKET.equals(socketName) ? Process.SECONDARY_ZYGOTE_SOCKET : Process.ZYGOTE_SOCKET; while (true) { try { final Process.ZygoteState zs = Process.ZygoteState.connect(otherZygoteName); zs.close(); break; } catch (IOException ioe) { Log.w(TAG, "Got error connecting to zygote, retrying. msg= " + ioe.getMessage()); } try { Thread.sleep(1000); } catch (InterruptedException ie) { } } } /** * Runs the zygote process's select loop. Accepts new connections as * they happen, and reads commands from connections one spawn-request's * worth at a time. * * @throws MethodAndArgsCaller in a child process when a main() should * be executed. */ private static void runSelectLoop(String abiList) throws MethodAndArgsCaller { ArrayList fds = new ArrayList(); ArrayList peers = new ArrayList(); FileDescriptor[] fdArray = new FileDescriptor[4]; fds.add(sServerSocket.getFileDescriptor()); peers.add(null); int loopCount = GC_LOOP_COUNT; while (true) { int index; /* * Call gc() before we block in select(). * It's work that has to be done anyway, and it's better * to avoid making every child do it. It will also * madvise() any free memory as a side-effect. * * Don't call it every time, because walking the entire * heap is a lot of overhead to free a few hundred bytes. */ if (loopCount <= 0) { gc(); loopCount = GC_LOOP_COUNT; } else { loopCount--; } try { fdArray = fds.toArray(fdArray); index = selectReadable(fdArray); } catch (IOException ex) { throw new RuntimeException("Error in select()", ex); } if (index < 0) { throw new RuntimeException("Error in select()"); } else if (index == 0) { ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); fds.add(newPeer.getFileDescriptor()); } else { boolean done; done = peers.get(index).runOnce(); if (done) { peers.remove(index); fds.remove(index); } } } } /** * The Linux syscall "setreuid()" * @param ruid real uid * @param euid effective uid * @return 0 on success, non-zero errno on fail */ static native int setreuid(int ruid, int euid); /** * The Linux syscall "setregid()" * @param rgid real gid * @param egid effective gid * @return 0 on success, non-zero errno on fail */ static native int setregid(int rgid, int egid); /** * Invokes the linux syscall "setpgid" * * @param pid pid to change * @param pgid new process group of pid * @return 0 on success or non-zero errno on fail */ static native int setpgid(int pid, int pgid); /** * Invokes the linux syscall "getpgid" * * @param pid pid to query * @return pgid of pid in question * @throws IOException on error */ static native int getpgid(int pid) throws IOException; /** * Invokes the syscall dup2() to copy the specified descriptors into * stdin, stdout, and stderr. The existing stdio descriptors will be * closed and errors during close will be ignored. The specified * descriptors will also remain open at their original descriptor numbers, * so the caller may want to close the original descriptors. * * @param in new stdin * @param out new stdout * @param err new stderr * @throws IOException */ static native void reopenStdio(FileDescriptor in, FileDescriptor out, FileDescriptor err) throws IOException; /** * Toggles the close-on-exec flag for the specified file descriptor. * * @param fd non-null; file descriptor * @param flag desired close-on-exec flag state * @throws IOException */ static native void setCloseOnExec(FileDescriptor fd, boolean flag) throws IOException; /** * Invokes select() on the provider array of file descriptors (selecting * for readability only). Array elements of null are ignored. * * @param fds non-null; array of readable file descriptors * @return index of descriptor that is now readable or -1 for empty array. * @throws IOException if an error occurs */ static native int selectReadable(FileDescriptor[] fds) throws IOException; /** * Creates a file descriptor from an int fd. * * @param fd integer OS file descriptor * @return non-null; FileDescriptor instance * @throws IOException if fd is invalid */ static native FileDescriptor createFileDescriptor(int fd) throws IOException; /** * Class not instantiable. */ private ZygoteInit() { } /** * Helper exception class which holds a method and arguments and * can call them. This is used as part of a trampoline to get rid of * the initial process setup stack frames. */ public static class MethodAndArgsCaller extends Exception implements Runnable { /** method to call */ private final Method mMethod; /** argument array */ private final String[] mArgs; public MethodAndArgsCaller(Method method, String[] args) { mMethod = method; mArgs = args; } public void run() { try { mMethod.invoke(null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(ex); } } } }