Process Creation: fork() and exec()
In UNIX-like operating systems like Android, process creation is handled by two fundamental system calls: fork() and exec().
fork(): Creates a near-exact duplicate of the calling process (the parent). The new process (the child) receives a new Process ID (PID). It inherits the parent's memory layout (via Copy-on-Write), file descriptors, and environment variables. The only difference is the return value offork():0in the child, and the child's PID in the parent.exec()(orexecve()): Replaces the current process's memory space with a new program. The process ID remains the same, but the code, data, heap, and stack are entirely overwritten by the new executable.
Typically, these are used together: fork() to create a new process, immediately followed by exec() in the child to run a different program.
Process Address Space Layout
When a process runs in Android, it operates within its own isolated virtual address space. This memory layout typically consists of the following segments:
- Text Segment (.text): Contains the compiled machine code of the executable (read-only).
- Data Segment (.data & .bss): Contains initialized global and static variables (.data) and uninitialized global/static variables (.bss, initialized to zero).
- Heap: Dynamic memory allocated at runtime (e.g., using
mallocin C/C++ ornewin Java/C++). It grows upwards. - Stack: Used for local variables, function parameters, and return addresses. It grows downwards.
- Memory-Mapped Region: Used for dynamically loaded shared libraries (
.sofiles) and memory-mapped files (mmap).
Process Lifecycle and Exit
A process can terminate in several ways:
- Normal Termination: Calling
exit(), returning frommain(), or falling off the end ofmain(). - Abnormal Termination: Receiving a signal (like
SIGKILLorSIGSEGV) that the process does not handle.
When a process terminates, the kernel reclaims its resources (memory, open files). However, its exit status must be preserved until the parent process acknowledges it.
Zombie Processes and wait()
When a child process terminates, it enters a "zombie" state. It consumes almost no resources, but its entry in the system process table remains. This exists solely to allow the parent process to read the child's exit status using the wait() or waitpid() system calls.
If the parent process fails to call wait(), the child remains a zombie indefinitely. This is a resource leak (process table entries are finite).
If a parent process terminates before its child, the child becomes an "orphan". The init process (PID 1) automatically adopts orphaned processes and periodically calls wait() to clean up any that terminate, preventing them from becoming permanent zombies.
Android-Specific: The Zygote Fork Model
Android heavily optimizes process creation for Java/Kotlin applications using a specialized model called Zygote.
Instead of the traditional fork() + exec() for every new app, Android does the following:
- Zygote Initialization: On boot, a special process called
zygote(orzygote64) starts. It initializes the Android Runtime (ART) and preloads hundreds of core framework classes and resources into memory. - Listening for Requests: Zygote enters a loop, waiting for socket connections from the
ActivityManagerService. - App Launch via
fork(): When a user launches an app,ActivityManagerServicetells Zygote to start it. Zygote callsfork(). - No
exec(): Crucially, the child process does not callexec(). It is already a fully functioning Java VM with the framework loaded. It simply drops its root privileges, sets up its specific application context, and begins executing the app'smain()method.
This model leverages Copy-on-Write (COW). The massive preloaded memory is shared among all running Android apps, drastically reducing memory usage and app startup time.