FileDocCategorySizeDatePackage
ZygoteInit.javaAPI DocAndroid 1.5 API27164Wed May 06 22:41:56 BST 2009com.android.internal.os

ZygoteInit

public class ZygoteInit extends Object
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 of child processes that inherit the initial state of the VM. Please see {@link ZygoteConnection.Arguments} for documentation on the client protocol.
hide

Fields Summary
private static final String
TAG
private static final String
ANDROID_SOCKET_ENV
private static final int
LOG_BOOT_PROGRESS_PRELOAD_START
private static final int
LOG_BOOT_PROGRESS_PRELOAD_END
private static final int
PRELOAD_GC_THRESHOLD
when preloading, GC after allocating this many bytes
private static android.net.LocalServerSocket
sServerSocket
private static android.content.res.Resources
mResources
Used to pre-load resources. We hold a global reference on it so it never gets destroyed.
static final int
GC_LOOP_COUNT
The number of times that the main Zygote loop should run before calling gc() again.
private static final boolean
ZYGOTE_FORK_MODE
If true, zygote forks for each peer. If false, a select loop is used inside a single process. The latter is preferred.
private static final String
PRELOADED_CLASSES
The name of a resource file that contains classes to preload.
private static final boolean
PRELOAD_RESOURCES
Controls whether we should preload resources during zygote init.
private static final int
UNPRIVILEGED_UID
private static final int
UNPRIVILEGED_GID
private static final int
ROOT_UID
private static final int
ROOT_GID
Constructors Summary
private ZygoteInit()
Class not instantiable.

    
Methods Summary
private static ZygoteConnectionacceptCommandPeer()
Waits for and accepts a single command connection. Throws RuntimeException on failure.

        try {            
            return new ZygoteConnection(sServerSocket.accept());
        } catch (IOException ex) {
            throw new RuntimeException(
                    "IOException during accept()", ex);
        }
    
static native longcapgetPermitted(int pid)
Retrieves the permitted capability set from another process.

param
pid >=0 process ID or 0 for this process
throws
IOException on error

static native voidcloseDescriptor(java.io.FileDescriptor fd)
Calls close() on a file descriptor

param
fd descriptor to close
throws
IOException

static voidcloseServerSocket()
Close and clean up zygote sockets. Called on shutdown and on the child's exit path.

        try {
            if (sServerSocket != null) {
                sServerSocket.close();
            }
        } catch (IOException ex) {
            Log.e(TAG, "Zygote:  error closing sockets", ex);
        }

        sServerSocket = null;
    
static native java.io.FileDescriptorcreateFileDescriptor(int fd)
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 voidgc()
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().

        final VMRuntime runtime = VMRuntime.getRuntime();

        /* runFinalizationSync() lets finalizers be called in Zygote,
         * which doesn't have a HeapWorker thread.
         */
        runtime.gcSoftReferences();
        runtime.runFinalizationSync();
        runtime.gcSoftReferences();
        runtime.runFinalizationSync();
        runtime.gcSoftReferences();
        runtime.runFinalizationSync();
    
static native intgetpgid(int pid)
Invokes the linux syscall "getpgid"

param
pid pid to query
return
pgid of pid in question
throws
IOException on error

private static voidhandleSystemServerProcess(ZygoteConnection.Arguments parsedArgs)
Finish remaining work for the newly forked system server process.

        /*
         * First, set the capabilities if necessary
         */

        if (parsedArgs.uid != 0) {
            try {
                setCapabilities(parsedArgs.permittedCapabilities,
                                parsedArgs.effectiveCapabilities);
            } catch (IOException ex) {
                Log.e(TAG, "Error setting capabilities", ex);
            }
        }

        closeServerSocket();

        /*
         * Pass the remaining arguments to SystemServer.
         * "--nice-name=system_server com.android.server.SystemServer"
         */
        RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
        /* should never reach here */
    
static voidinvokeStaticMain(java.lang.ClassLoader loader, java.lang.String className, java.lang.String[] argv)
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()

    
                                                     
       
               
              
        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);
    
public static voidmain(java.lang.String[] argv)

        try {
            registerZygoteSocket();
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                SystemClock.uptimeMillis());
            preloadClasses();
            preloadResources();
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                SystemClock.uptimeMillis());

            // Do an initial gc to clean up after startup
            gc();

            // If requested, start system server directly from Zygote
            if (argv.length != 2) {
                throw new RuntimeException(
                        "ZygoteInit.main expects two arguments");
            }

            if (argv[1].equals("true")) {
                startSystemServer();
            }

            Log.i(TAG, "Accepting command socket connections");

            if (ZYGOTE_FORK_MODE) {
                runForkMode();
            } else {
                runSelectLoopMode();
            }

            closeServerSocket();
        } catch (MethodAndArgsCaller caller) {
            caller.run();
        } catch (RuntimeException ex) {
            Log.e(TAG, "Zygote died with exception", ex);
            closeServerSocket();
            throw ex;
        }
    
private static voidpreloadClasses()
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).

        final VMRuntime runtime = VMRuntime.getRuntime();
        
        InputStream is = ZygoteInit.class.getClassLoader().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.
            runtime.gcSoftReferences();
            runtime.runFinalizationSync();
            Debug.startAllocCounting();

            try {
                BufferedReader br 
                    = new BufferedReader(new InputStreamReader(is), 256);

                int count = 0;
                String line;
                String missingClasses = null;
                while ((line = br.readLine()) != null) {
                    // Skip comments and blank lines.
                    line = line.trim();
                    if (line.startsWith("#") || line.equals("")) {
                        continue;
                    }

                    try {
                        if (Config.LOGV) {
                            Log.v(TAG, "Preloading " + line + "...");
                        }
                        Class.forName(line);
                        if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
                            if (Config.LOGV) {
                                Log.v(TAG,
                                    " GC at " + Debug.getGlobalAllocSize());
                            }
                            runtime.gcSoftReferences();
                            runtime.runFinalizationSync();
                            Debug.resetGlobalAllocSize();
                        }
                        count++;
                    } catch (ClassNotFoundException e) {
                        Log.e(TAG, "Class not found for preloading: " + line);
                        if (missingClasses == null) {
                            missingClasses = line;
                        } else {
                            missingClasses += " " + line;
                        }
                    }
                }

                if (missingClasses != null &&
                        "1".equals(SystemProperties.get("persist.service.adb.enable"))) {
                    throw new IllegalStateException(
                            "Missing class(es) for preloading, update preloaded-classes ["
                            + missingClasses + "]");
                }

                Log.i(TAG, "...preloaded " + count + " classes in "
                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
            } catch (IOException e) {
                Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
            } finally {
                // Restore default.
                runtime.setTargetHeapUtilization(defaultUtilization);

                Debug.stopAllocCounting();

                // Bring back root. We'll need it later.
                setEffectiveUser(ROOT_UID);
                setEffectiveGroup(ROOT_GID);
            }
        }
    
private static intpreloadColorStateLists(dalvik.system.VMRuntime runtime, android.content.res.TypedArray ar)

        int N = ar.length();
        for (int i=0; i<N; i++) {
            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
                if (Config.LOGV) {
                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
                }
                runtime.gcSoftReferences();
                runtime.runFinalizationSync();
                Debug.resetGlobalAllocSize();
            }
            int id = ar.getResourceId(i, 0);
            if (Config.LOGV) {
                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
            }
            if (id != 0) {
                mResources.getColorStateList(id);
            }
        }
        return N;
    
private static intpreloadDrawables(dalvik.system.VMRuntime runtime, android.content.res.TypedArray ar)

        int N = ar.length();
        for (int i=0; i<N; i++) {
            if (Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {
                if (Config.LOGV) {
                    Log.v(TAG, " GC at " + Debug.getGlobalAllocSize());
                }
                runtime.gcSoftReferences();
                runtime.runFinalizationSync();
                Debug.resetGlobalAllocSize();
            }
            int id = ar.getResourceId(i, 0);
            if (Config.LOGV) {
                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
            }
            if (id != 0) {
                Drawable dr = mResources.getDrawable(id);
                if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
                    Log.w(TAG, "Preloaded drawable resource #0x"
                            + Integer.toHexString(id)
                            + " (" + ar.getString(i) + ") that varies with configuration!!");
                }
            }
        }
        return N;
    
private static voidpreloadResources()
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.

        final VMRuntime runtime = VMRuntime.getRuntime();
        
        Debug.startAllocCounting();
        try {
            runtime.gcSoftReferences();
            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);
                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);
                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 voidregisterZygoteSocket()
Registers a server socket for zygote command connections

throws
RuntimeException when open fails

        if (sServerSocket == null) {
            int fileDesc;
            try {
                String env = System.getenv(ANDROID_SOCKET_ENV);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(
                        ANDROID_SOCKET_ENV + " unset or invalid", ex);
            }

            try {
                sServerSocket = new LocalServerSocket(
                        createFileDescriptor(fileDesc));
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    
static native voidreopenStdio(java.io.FileDescriptor in, java.io.FileDescriptor out, java.io.FileDescriptor err)
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

private static voidrunForkMode()
Runs the zygote in accept-and-fork mode. In this mode, each peer gets its own zygote spawner process. This code is retained for reference only.

throws
MethodAndArgsCaller in a child process when a main() should be executed.

        while (true) {
            ZygoteConnection peer = acceptCommandPeer();

            int pid;

            pid = Zygote.fork();

            if (pid == 0) {
                // The child process should handle the peer requests

                // The child does not accept any more connections
                try {
                    sServerSocket.close();
                } catch (IOException ex) {
                    Log.e(TAG, "Zygote Child: error closing sockets", ex);
                } finally {
                    sServerSocket = null;
                }

                peer.run();
                break;
            } else if (pid > 0) {
                peer.closeSocket();
            } else {
                throw new RuntimeException("Error invoking fork()");
            }
        }
    
private static voidrunSelectLoopMode()
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.

        ArrayList<FileDescriptor> fds = new ArrayList();
        ArrayList<ZygoteConnection> 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();
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                boolean done;
                done = peers.get(index).runOnce();

                if (done) {
                    peers.remove(index);
                    fds.remove(index);
                }
            }
        }
    
static native intselectReadable(java.io.FileDescriptor[] fds)
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 voidsetCapabilities(long permittedCapabilities, long effectiveCapabilities)
Sets the permitted and effective capability sets of this process.

param
permittedCapabilities permitted set
param
effectiveCapabilities effective set
throws
IOException on error

static native voidsetCloseOnExec(java.io.FileDescriptor fd, boolean flag)
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

private static voidsetEffectiveGroup(int gid)
Sets effective group ID.

        int errno = setregid(ROOT_GID, gid);
        if (errno != 0) {
            Log.e(TAG, "setregid() failed. errno: " + errno);
        }
    
private static voidsetEffectiveUser(int uid)
Sets effective user ID.


             
         
        int errno = setreuid(ROOT_UID, uid);
        if (errno != 0) {
            Log.e(TAG, "setreuid() failed. errno: " + errno);
        }
    
static native intsetpgid(int pid, int pgid)
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 intsetregid(int rgid, int egid)
The Linux syscall "setregid()"

param
rgid real gid
param
egid effective gid
return
0 on success, non-zero errno on fail

static native intsetreuid(int ruid, int euid)
The Linux syscall "setreuid()"

param
ruid real uid
param
euid effective uid
return
0 on success, non-zero errno on fail

private static booleanstartSystemServer()
Prepare the arguments and fork for the system server process.

        /* 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,3001,3002,3003",
            "--capabilities=121715744,121715744",
            "--runtime-init",
            "--nice-name=system_server",
            "com.android.server.SystemServer",
        };
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteConnection.Arguments(args);

            /*
             * Enable debugging of the system process if *either* the command line flags
             * indicate it should be debuggable or the ro.debuggable system property
             * is set to "1"
             */
            int debugFlags = parsedArgs.debugFlags;
            if ("1".equals(SystemProperties.get("ro.debuggable")))
                debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;

            /* Request to fork the system server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids, debugFlags, null);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        } 
 
        /* For child process */
        if (pid == 0) {
            handleSystemServerProcess(parsedArgs);
        }

        return true;