Android applications and native system components ultimately execute as processes on a Linux-based system. To understand what a process can access—and why app code cannot simply read or change every byte—it helps to connect program, memory, and kernel concepts. This lesson is conceptual; it does not ask you to compile code or run device commands.
Before you start#
- Linux files and processes — paths, processes, streams, status, and signals.
- C/C++ source reading — source notation and the difference between a library call and compiled execution.
- Bytes, hex, and trust — binary representation and evidence vocabulary.
Learning goals#
You can distinguish program, process, and thread; explain why a pointer is a process-local virtual address rather than a universal RAM address; describe how a library wrapper can invoke a system call; and explain why native code does not automatically gain root privileges.
Program, process, thread#
A program is stored instructions. A process is a running instance with its own execution context and resources. Running a program twice can create two processes with distinct PIDs. A thread is a sequence of execution within a process. Threads share much of their process's memory, so one thread's bug can affect data used by another. An operating system manages CPU time, memory, files, and devices. The kernel is its privileged core. “Privileged” means processor/system rules allow operations unavailable to ordinary application code. User mode is a less-privileged execution mode; kernel mode is where kernel code performs protected work. Exact names/mechanisms vary by processor architecture. Linux kernel documentation index (general background)
Virtual memory: a process's view#
Read the text version
- Process A: Virtual address 0x1000 → Physical page X: Bytes for A’s mapping (A’s mapping)
- Process B: Virtual address 0x1000 → Physical page Y: Different bytes in this example (B’s mapping)
A process normally sees a virtual address space, a range of addresses its code can use. Hardware and operating-system mechanisms translate virtual addresses to physical memory as needed. A C pointer is a virtual address meaningful in that process's context, not a universal physical address. Two processes can use the same numeric address and refer to different memory. Controlled shared-memory mechanisms can map memory into multiple processes, but guessing an address does not grant access.
Memory is managed in pages; exact size depends on architecture and configuration. A process may have areas for executable code, static data, a heap commonly used for dynamic allocation, and stacks for function-call state and local variables. Each thread usually has a stack. These are useful models, not a promise that every object maps to a neat visible region or every language manages memory alike. Address-space isolation is one protection; Android layers app identity, permissions, and other controls on top. Linux memory-management docs index · Android app sandbox
A system call crosses a boundary#
- App codeLibrary wrapper
call read(fd, buffer, count)
- Library wrapperKernel
architecture-specific syscall entry
- KernelLibrary wrapper
Return. byte count or error
- Library wrapperApp code
Return. wrapper result / error convention
Time runs downward. Dashed arrows show returns.
Read the text version
- App code → Library wrapper: call read(fd, buffer, count)
- Library wrapper → Kernel: architecture-specific syscall entry
- Kernel → Library wrapper (return): byte count or error
- Library wrapper → App code (return): wrapper result / error convention
A system call asks the kernel for a service requiring kernel authority, such as reading a file, creating a process, or communicating over a socket. A program uses a defined interface; an architecture-specific mechanism transfers control to the kernel; the kernel validates the request and performs or rejects it, then returns a result. This is not the same as every function call: sum(2, 3) may be an ordinary in-process function, while a read library call may ultimately request kernel work.
Programmers commonly call a library wrapper, not a processor syscall instruction directly. A wrapper may prepare arguments, translate errors, or provide a language-level interface. Conventional GNU/Linux C programs often use glibc; Android native programs commonly use Bionic, Android's C library (Bionic README at android-14.0.0_r1). Familiar function names do not guarantee all details or interfaces are identical. The Linux manual explains that syscalls are generally invoked through wrappers. Linux syscalls(2)
Conceptual file-read path (simplified, not a trace of one actual Android call):
application/library code
↓ wrapper prepares a request
architecture-specific syscall entry
↓ controlled privilege transition
kernel checks descriptor, buffer, and access
↓ byte count or error result
wrapper reports a result to the program
Runtime libraries, buffering, caches, asynchronous APIs, framework services, and Android-specific policy may add work. A syscall is a boundary, not a guarantee that each app feature makes exactly one direct call. The manual's summary—“The system call is the fundamental interface between an application and the Linux kernel”—is a general Linux statement, not proof of an Android feature's complete route. Linux syscalls(2), DESCRIPTION
File descriptors and a C example#
A file descriptor is a small integer handle a process uses for an open file or another I/O resource, including pipes and sockets. It is meaningful in that process; descriptor 3 in one process need not refer to descriptor 3 in another. Linux conventionally uses 0, 1, and 2 for standard input, output, and error (stdin(3)).
Why use a descriptor instead of a path for every read? The kernel resolves a name when opening and returns a reference to an open file description, which carries state such as an offset and status flags. A descriptor names that reference in the process's table. Duplicated descriptors can refer to the same open file description and share its offset; two independent opens normally create separate descriptions. Removing or covering a pathname does not automatically invalidate an existing open reference. See open(2) and dup(2).
Worked example: A opens a file and duplicates its descriptor. Reading two bytes through the first descriptor advances the shared offset, so a subsequent read through the duplicate continues from that offset. B independently opens the same pathname and has a separate offset. The same numeric descriptor in B proves nothing about sharing. This distinction later explains Binder descriptor transfer and why a systemless mount change affects new path lookups but not every already-open file.
The following is an illustrative C source example, not Android source and not a runnable exercise in this lesson. See C/C++ source reading for the source-to-compiler-to-program bridge.
#include <stdio.h>
int main(void) {
puts("Hello from user space.");
return 0;
}
stdio.h declares standard I/O interfaces; main is the entry function; puts writes a line to standard output; return 0 conventionally reports success. The library may buffer output and eventually use a kernel interface. This code does not directly issue a syscall. On many POSIX interfaces, a wrapper reports failure with a sentinel such as -1 and records a reason in errno; raw syscall mechanisms and return conventions vary. Syscall numbers and calling conventions vary by architecture and ABI (application binary interface, low-level rules for calling code and passing data). Linux syscalls(2)
Why Android separates apps#
Android assigns app identities and runs applications within a sandbox—a boundary limiting default access. Unique Linux user IDs are used for installed apps in relevant contexts, along with permissions and other mechanisms. Ordinary apps therefore do not automatically inspect another app's private files or control kernel resources. Implementation has evolved; shared processes and special system roles have additional rules. Treat app identity as a security boundary, not a decorative label. Android app sandbox
A shell prompt does not imply kernel privilege. Native C/C++ code inside an ordinary app still runs subject to that app's identity and protections. Root conventionally means UID 0 in a given environment, but additional controls and device configuration still matter. Rooting and bootloader changes are outside this lesson; never experiment on a device or data you cannot afford to lose.
Practice#
Context: source-only reasoning; no host command, device command, or state change. Draw a two-column comparison for two apps that both use virtual address 0x1000. In one sentence, explain why equal numbers do not prove shared physical memory or permission. Then trace a hypothetical app file read: app/framework API (possibly) → native/runtime wrapper(s) → kernel request(s). Mark every “possibly” and do not claim one exact call chain without a version-specific source trace. If you cannot distinguish a normal function call from a system-call boundary, revisit the simplified diagram above and Linux files and processes.
Expected reasoning: addresses are interpreted in each process's virtual address space; isolation and access checks prevent address guessing from becoming access. An Android feature may use framework, Binder, libraries, and more than one kernel interaction. There is no runtime output to compare because this is a source-only exercise.
Knowledge check#
- Two processes both print pointer value
0x1000. What can you conclude about their physical memory? - Does calling C library
putsprove that the C source directly invoked a syscall instruction? - An Android app contains native C code. Does that alone make it root?
Answers#
- Nothing about shared physical memory follows from matching virtual-address numbers. Each process interprets addresses in its own address space unless an explicit shared mechanism is used.
- No.
putsis a library interface and can buffer output; the library may later request kernel work. The source does not issue a raw syscall here. - No. Native code retains the process identity and platform restrictions unless a separate authorized configuration changes them.
Checkpoint#
Artifact: the two-column address-space sketch and a three-step explanation distinguishing library wrapper, syscall boundary, and kernel work. Ready: the explanation avoids saying a pointer is physical or that every API maps to one syscall. Revise: revisit “Virtual memory” and “A system call crosses a boundary.” Not yet evidenced: the terms remain memorized rather than connected; review C/C++ source reading and Linux files and processes. No device behavior has been tested by this exercise.
Sources#
- Linux
syscalls(2)— syscall boundary, wrappers, and architecture-specific notes; general Linux reference. - Linux memory management docs — primary kernel documentation for memory-management concepts.
- Android app sandbox — Android-specific application isolation model.
- Android kernel overview — Android's Linux-kernel relationship.
- Bionic README at
android-14.0.0_r1— pinned statement that Bionic is Android's C library; no implementation details are excerpted here. stdin(3)— the stdin, stdout, and stderr descriptors are 0, 1, and 2 at program startup.
Next: Emulator-first lab.