Pages

Android OS - Processes and the Zygote!

Abstract
Android process management is similar to that of Linux at a low level, but the Android Runtime provides a layer of abstraction to help keep often used processes in memory as long as it can.  This is done using some memory management techniques that are not common.  In this article I investigate the way that processes are managed and how the actual startup of a process differs from a typical operating system.  This involves using an existing process called the zygote, which is at the most basic level a "warmed-up" virtual machine.  I will also investigate when and how processes are finally killed.


Outline
  1. Introduction
  2. Is Android a Linux distribution?
  3. Android anatomy
  4. Android processes
  5. Android applications
  6. Startup of a process
  7. Process termination
Introduction
What is Android OS?
Android OS is an operating system that was developed by Google for use on mobile devices.  This means that it was designed for systems with little memory and a processor that isn't as fast as desktop processors.  While keeping the limitations in mind, Google's vision for Android is that it would have a robust set of programming APIs and a very responsive UI.  In order to facilitate this vision, they created an abstraction layer, which allows application developers to be hardware agnostic in their design.  


This article is designed to give an overview of the structure of Android OS and show an in depth look at processes and the Zygote.

Is Android a Linux distribution?
The short answer is no.  Android is based on the Linux kernel, but is not actually purely a “Linux distribution”.  A standard Linux distribution has a native windowing system, glibc and some standard utilities.  It does not have a layer of abstraction between the user applications and the libraries.  In general, it simply looks like this:


Linux hierarchy(source)
Android Anatomy
Android has a layer of abstraction (Application Framework) and lacks the native windowing system, glibc and most of the standard utilities of Linux, which gives it a very unique anatomy, which is represented here:

Android Anatomy(source)


Let's break down the anatomy.


Kernel 
As you can see, the Android OS is built on the Linux 2.6 kernel.  There are significant modifications that have been made to the kernel, but it has the same core.   You might wonder, if it has significant modifications, why use it?
  • Proven models for process management and memory management.
  • Permissions based security model is tested.
  • It’s open source!


The Android OS is designed as a single user OS, so Android takes advantage of this and runs each component as a separate user.  This allows Android to use the security model of Linux and keep processes in their own sandbox.

Libraries
Native libraries are a very important part of any OS.  In this case the import pieces here are
  • Bionic libc
  • Function libraries (for standard calls)
  •  Native servers for UI and sound.
  • Hardware abstraction. (provided so that people could use proprietary drivers in an open source OS)
Android Runtime
The Android Runtime is where things began to get very unique and interesting.  At the lowest level is the Dalvik Virtual Machine, which is an interpreter for the Java programming language.  This is similar to the JVM which is written by Sun, but was chosen because it operates better in an embedded environment (it’s written for CPU utilization and to minimize memory usage).  The Dalvik VM supports a standard set of core java APIs.  One important thing to note is that above the Android runtime, everything is written in Java.

Application Framework
This is a layer that has been written entirely in Java that provides the building blocks for the application developer.  All of these pieces are important to the OS, but I'll detail a couple here:
  • Activity Manager - This is responsible for keeping track of what activities are currently running as well as their states.  I'll talk more about the Activity Manager later.
  • Window Manager - This is responsible for the organization of the screen and allocating surfaces to the application, which it draws on directly.
Applications
All applications, including the ones that come with Android OS are written at this level.  This means a couple important things:
  1. All applications are written in Java, which means they are able to be run on ANY installation of Android OS.  These are hardware independent and are compiled into dex format (which is a more compressed than java bytecode).
  2. Any software developer can write the same applications as Google.  This means the UI (including the homescreen) can be 100% customized.  
Now that we have a good background on what Android OS is (and isn't), I'm going to dive into the meat of this article.  I want to focus on how processes work in Android and how the Zygote works.


Android Processes 
Process Management Overview
Process management in a typical operating system involves many complex data structures and algorithms, but doesn’t go much beyond the level managing the typical process data structure.  Android is similar in that at the base level the control structures look the same.  Similar to this:

Process Control Block
This data structure is managed by a standard process management, which is something like this:



Android Applications
Android applications differ from standard applications in a couple very significant ways.
  • Every android application runs in a separate process, has its own Dalvik VM and since Android is a single user OS, the designers assign each application a unique UID at install time.  This means the underlying Linux kernel can protect each applications files and memory without additional effort.
  • There is no single entry point for android applications.  An application is a collection of components that can be used in other applications if desired.  The details of this can be found here as this is out of the scope of this article.
Android applications at the lowest form are Linux processes.  Each application runs as its own process and by default has 1 thread.  


Android Kernel Processes
At this point you might be thinking that ALL processes in the Android OS have their own Dalvik VM, but that would be a slight overstatement.  Deep down in the kernel, there exists some processes which are not at the application level.  We will discuss these when we talk about "How android processes die".

Zygote
Android at its core has a process they call the “Zygote”, which starts up at init. It gets it's name from dictionary definition: "It is the initial cell formed when a new organism is produced". This process is a “Warmed-up” process, which means it’s a process that’s been initialized and has all the core libraries linked in.  When you start an application, the Zygote is forked, so now there are 2 VMs.  The real speedup is achieved by NOT copying the shared libraries.  This memory will only be copied if the new process tries to modify it.  This means that all of the core libraries can exist in a single place because they are read only.


Process Priority
Process priority can be set via the Process.setThreadPriority, but should only be done by the system-server, which is not something we are going to cover in this article.  At the base level, it uses the same process nice levels as Linux, which you can read about here.  


Application Launch
This diagram is an overview of the launch process.
Overview of the application launch(source)
Intent
All applications in the Android OS are started via an Intent object, whose sole purpose is to notify the ActivityManagerService that the user wants something to happen.  The details of what is in the Intent object are not important for our discussion, but you can go here if you're interested.


Startup of a process
Dive into launch 
First we look at the ActivityManagerService.startActivityMayWait(Intent intent).  The Activity Manager will look at information regarding the target of the intent.
ResolveInfo rInfo =
    ActivityThread.getPackageManager().resolveIntent(
            intent, resolvedType,
            PackageManager.MATCH_DEFAULT_ONLY
            | STOCK_PM_FLAGS);


This information is saved inside the intent object so that it doesn't need to be calculated again.

// Store the found target back into the intent, because now that
// we have it we never want to do this again.  For example, if the
// user navigates back to this point in the history, we should
// always restart the exact same activity.
intent.setComponent(new ComponentName(
        aInfo.applicationInfo.packageName, aInfo.name));



Then there is a bunch of methods calling each other that don't really matter to this discussion.  I will list them here anyways just so that people know the amount of calls.  The next call is startActivityLocked, which figures out package permissions, adds the activity to a list of pending activity launches and calls down a bit further into doPendingActivityLaunchesLocked, which calls ANOTHER startActivityLocked, which calls resumeTopActivityLocked(null).  This is where the interesting stuff begins.


First, get the activity we should "resume".
HistoryRecord next = topRunningActivityLocked(null);


Next it checks a bunch of special cases, such as:
// If we are sleeping, and there is no resumed activity, and the top
// activity is paused, well that is the state we want.
if ((mSleeping || mShuttingDown)
        && mLastPausedActivity == next && next.state == ActivityState.PAUSED) {
    // Make sure we have executed any pending transitions, since there
    // should be nothing left to do at this point.
    mWindowManager.executeAppTransition();
    mNoAnimActivities.clear();
    return false;
}



and

// If the top activity is the resumed one, nothing to do.
if (mResumedActivity == next && next.state == ActivityState.RESUMED) {
    // Make sure we have executed any pending transitions, since there
    // should be nothing left to do at this point.
    mWindowManager.executeAppTransition();
    mNoAnimActivities.clear();
    return false;
}



Assuming that all these special cases pass, it then calls startSpecificActivityLocked on next.
startSpecificActivityLocked(next, true, true);


This method then checks to see if there is a Process for the activity.
// Is this activity's application already running?
ProcessRecord app = getProcessRecordLocked(r.processName,
        r.info.applicationInfo.uid);



If the process exists, just call in to bring it to front.

if (app != null && app.thread != null) {
    try {
        realStartActivityLocked(r, app, andResume, checkConfig);
        return;
    } catch (RemoteException e) {
        Slog.w(TAG, "Exception when starting activity "
                + r.intent.getComponent().flattenToShortString(), e);
    }

    // If a dead object exception was thrown -- fall through to
    // restart the application.
}



Assuming that the process isn't running, call startProcessLocked

startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
        "activity", r.intent.getComponent(), false);



The startProcessLocked checks some debug flags, which can be applied in testing or safe mode, then calls

int pid = Process.start("android.app.ActivityThread",
        mSimpleProcessManagement ? app.processName : null, uid, uid,
        gids, debugFlags, null);



The Process class is responsible for the actual call to the Zygote, which will cause a fork and return a new pid. It begins that process through its run method, which calls startViaZygote.

try {
    return startViaZygote(processClass, niceName, uid, gid, gids,
            debugFlags, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
    Log.e(LOG_TAG,
            "Starting VM process through Zygote failed");
    throw new RuntimeException(
            "Starting VM process through Zygote failed", ex);
}



The call to startViaZygote simply forms up the arguments that will be needed and calls zygoteSendArgsAndGetPid.
pid = zygoteSendArgsAndGetPid(argsForZygote);


After the arguments are formed up, it writes them all out to a stream writer, then asks the socket to read the integer.  The first integer is the number of arguments.  
/**
 * See com.android.internal.os.ZygoteInit.readArgumentList()
 * Presently the wire format to the zygote process is:
 * a) a count of arguments (argc, in essence)
 * b) a number of newline-separated argument strings equal to count
 *
 * After the zygote process reads these it will write the pid of
 * the child or -1 on failure.
 */

sZygoteWriter.write(Integer.toString(args.size()));
sZygoteWriter.newLine();

int sz = args.size();
for (int i = 0; i < sz; i++) {
    String arg = args.get(i);
    if (arg.indexOf('\n') >= 0) {
        throw new ZygoteStartFailedEx(
                "embedded newlines not allowed");
    }
    sZygoteWriter.write(arg);
    sZygoteWriter.newLine();
}

sZygoteWriter.flush();

// Should there be a timeout on this?
pid = sZygoteInputStream.readInt();



From here, control is given to the Zygote, which finally does the fork.
pid = Zygote.fork();


And FINALLY, it calls a native function!
/* native public static int fork(); */
static void Dalvik_dalvik_system_Zygote_fork(const u4* args, JValue* pResult)
{
    pid_t pid;
    int err;

    if (!gDvm.zygote) {
        dvmThrowException("Ljava/lang/IllegalStateException;",
            "VM instance not started with -Xzygote");

        RETURN_VOID();
    }

    if (!dvmGcPreZygoteFork()) {
        LOGE("pre-fork heap failed\n");
        dvmAbort();
    }

    setSignalHandler();

    dvmDumpLoaderStats("zygote");
    pid = fork();

This pid is returned all they way out.  The process is started at this point.  If you want to read more detail about the way that things interact after a process is launched, you can read more about it here.

Process Termination
When does a process die?
Processes can be killed in a couple discrete ways.
  1. An application can call a method to kill processes it has permission to kill.  This means if the process isn't part of the same application, it can't kill other processes.  On install you can actually grant an application permission to kill other applications, but this is something you don't typically do.
  2. The Android OS has a least recently used queue that keeps track of which applications haven't been used.  If the OS starts to run out of memory, it will kill the least recently used application.  There is also priority given to applications that a user is interacting with, or background services the user is interacting with.  A detailed article on these preferences can be found here.
How does a process die?
Obviously as detailed above, a process can be killed using Process.killProcess(int pid), but point 2 is a bit vague.  This is done via a Linux driver that is loaded for android only (right now).  This driver has been the subject of much debate as the mainstream Linux guys believe the code should be different. Oh the beauty of Open Source.  If you wish to read the thread, it is detailed here.


This driver is called lowmemkiller and it takes advantage of a field oomkilladj on the task_struct (which seems to exist only in certain branches of the kernel, though I cannot enumerate them for you).  The ActivityManager adjusts this value based on what type of application it is, which I talked about above.  This value is also adjusted based on a least recently used algorithm.  


How this driver works is that it has 6 values that are free memory cutoffs.
static size_t lowmem_minfree[6] = {
    3*512, // 6MB
    2*1024, // 8MB
    4*1024, // 16MB
    16*1024, // 64MB
};

Then it uses the lowmem_minfree array to determine what the minimum oomkilladj value needs to be in order to actually kill the process.
for(i = 0; i < array_size; i++) {
    if (other_free < lowmem_minfree[i] &&
        other_file < lowmem_minfree[i]) {
        min_adj = lowmem_adj[i];
        break;
    }
}


If the free memory is below the specified amount, it will kill off the process with the highest oomkilladj value.  If two processes have the same oomkilladj value, it will kill the one with the highest task size.  Beyond this, the code is pretty simple and doesn't need further explanation.
for_each_process(p) {
    if (p->oomkilladj < min_adj || !p->mm)
        continue;
    tasksize = get_mm_rss(p->mm);
    if (tasksize <= 0)
        continue;
    if (selected) {
        if (p->oomkilladj < selected->oomkilladj)
            continue;
        if (p->oomkilladj == selected->oomkilladj &&
            tasksize <= selected_tasksize)
            continue;
    }
    selected = p;
    selected_tasksize = tasksize;
    lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
                 p->pid, p->comm, p->oomkilladj, tasksize);
}
if(selected != NULL) {
    lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
                 selected->pid, selected->comm,
                 selected->oomkilladj, selected_tasksize);
    force_sig(SIGKILL, selected);
    rem -= selected_tasksize;
}


Summary
People commonly think the Android OS as a Linux distribution, but as you can see, this is a misconception.  In this article we only looked at Android from a process management standpoint, but as you can see, though it leverages the Linux kernel, it is very different because of it's unique anatomy.  The Activity Manager starts up a process and the processes are kept around and cleaned up only when memory is needed.

References
I used information from many places to put this article together.
http://en.wikipedia.org/wiki/Zygote
http://developer.android.com/guide/topics/fundamentals.html
http://blogs.techrepublic.com.com/opensource/?p=140
http://multi-core-dump.blogspot.com/2010/04/android-application-launch-part-2.html
http://projects.gemberdesign.com/research/index.php?title=Zygote#Fork
http://blog.vlad1.com/2009/11/19/android-hacking-part-1-of-probably-many/
http://developer.android.com/reference/android/os/Process.html#killProcess(int)
http://android.git.kernel.org
http://www.youtube.com/watch?v=ptjedOZEXPM&feature=channel