Chapter 5: Kernel¶
The Linux kernel is the foundation of every Android device. It manages hardware, enforces security boundaries, schedules processes, and provides the low-level primitives -- such as Binder IPC and shared memory -- on which the entire Android framework is built. Yet the kernel running on an Android device is not a stock upstream Linux kernel. Over more than fifteen years, Android has accumulated a set of kernel modifications, out-of-tree drivers, and configuration requirements that distinguish it from any desktop or server Linux distribution.
This chapter examines the Android kernel in depth: what Android adds to upstream Linux, how the Generic Kernel Image (GKI) architecture reduces fragmentation, how individual Android-specific subsystems work at the driver level, how device trees describe hardware, how kernel configuration is managed across releases, how the kernel integrates into the AOSP build system, and how to debug kernel-level problems on real and emulated devices.
Throughout this chapter, we reference real files in the AOSP source tree. Every path, config fragment, and module name cited here can be found in that tree.
5.1 Android Kernel vs Upstream Linux¶
5.1.1 The Fork That Is Not Really a Fork¶
Android does not maintain a permanent fork of the Linux kernel. Instead, Google
maintains a set of "Android Common Kernel" (ACK) branches that track specific
upstream Long-Term Support (LTS) releases. Each ACK branch starts from an
upstream LTS tag (e.g., 6.6, 6.12) and adds a curated set of patches that
provide Android-specific functionality. These patches fall into several
categories:
- Android-specific drivers that implement core platform features (Binder, ashmem, incremental-fs)
- Scheduler and memory management changes that improve interactive performance on mobile devices
- Security hardening beyond what upstream provides by default
- Vendor hook infrastructure (trace events and restricted vendor hooks) that allow SoC vendors to customize behavior without modifying core kernel code
- Test and debug infrastructure integrated with Android's testing pipeline
The goal is to minimize the delta from upstream. Many patches that originated in
the Android tree have been upstreamed over the years -- wakelocks (now
PM_WAKELOCKS), the low memory killer (replaced by PSI-based userspace lmkd),
and ashmem (being superseded by memfd) are all examples of this convergence.
5.1.2 Major Android Additions¶
The following table summarizes the most significant kernel-level features that Android adds or requires beyond a stock upstream kernel:
| Feature | Kernel Config | Purpose | Status |
|---|---|---|---|
| Binder IPC | CONFIG_ANDROID_BINDER_IPC |
Cross-process communication for all framework services | Active, required |
| Binderfs | CONFIG_ANDROID_BINDERFS |
Dynamic Binder device management | Active, required |
| Ashmem | CONFIG_ASHMEM |
Anonymous shared memory regions | Active, transitioning to memfd |
| Wakelocks | CONFIG_PM_WAKELOCKS |
Prevent system suspend during critical operations | Active, required |
| PSI (Pressure Stall Information) | CONFIG_PSI |
Memory pressure monitoring for lmkd | Active, required |
| DMA-BUF Heaps | CONFIG_DMABUF_HEAPS_SYSTEM |
Graphics buffer allocation (ION replacement) | Active |
| FUSE filesystem | CONFIG_FUSE_FS |
Userspace filesystem for storage access | Active, required |
| Incremental FS | CONFIG_INCREMENTAL_FS |
On-demand APK block loading | Active |
| dm-verity | CONFIG_DM_VERITY |
Verified boot for system partitions | Active, required |
| FS encryption | CONFIG_FS_ENCRYPTION |
File-based encryption for userdata | Active, required |
| FS verity | CONFIG_FS_VERITY |
Per-file integrity verification | Active, required |
| UID system stats | CONFIG_UID_SYS_STATS |
Per-UID I/O and CPU accounting | Active, required |
| CPU freq times | CONFIG_CPU_FREQ_TIMES |
Per-UID CPU frequency residency tracking | Active, required |
| GPU memory tracing | CONFIG_TRACE_GPU_MEM |
GPU memory allocation tracing | Active, required |
These config options are declared as mandatory in the Android base configuration
fragments stored in the kernel/configs/ directory of the AOSP tree.
5.1.3 Architectural Comparison¶
The following diagram illustrates how the Android kernel differs from an upstream Linux kernel in terms of its layered architecture:
graph TB
subgraph "Android Kernel"
direction TB
AUP["Upstream Linux LTS<br/>(e.g., 6.12)"]
AAN["Android-Specific Drivers<br/>Binder, ashmem, incremental-fs"]
AVH["Vendor Hook Infrastructure<br/>android_rvh_*, android_vh_*"]
ASH["Security Hardening<br/>SELinux enforcement, dm-verity,<br/>fs-encryption, CFI"]
APM["Power Management<br/>Wakelocks, suspend blockers,<br/>CPU freq governor hooks"]
ADB["Debug & Tracing<br/>GPU mem trace, UID stats,<br/>Perfetto integration"]
AUP --> AAN
AUP --> AVH
AUP --> ASH
AUP --> APM
AUP --> ADB
end
subgraph "Upstream Linux Kernel"
direction TB
UUP["Upstream Linux LTS<br/>(e.g., 6.12)"]
UDR["Standard Drivers<br/>GPU, network, storage, input"]
USH["Security<br/>SELinux, AppArmor,<br/>seccomp, namespaces"]
UPM["Power Management<br/>cpufreq, cpuidle,<br/>runtime PM"]
UDB["Debug & Tracing<br/>ftrace, perf, eBPF"]
UUP --> UDR
UUP --> USH
UUP --> UPM
UUP --> UDB
end
style AAN fill:#e1f5fe
style AVH fill:#e1f5fe
style APM fill:#e1f5fe
style ADB fill:#e1f5fe
5.1.4 The Upstream Convergence Trend¶
Google has been actively working to reduce the delta between the Android Common Kernel and upstream Linux. Several historically Android-only features have been upstreamed or are in the process:
Already upstreamed or converging:
PM_WAKELOCKS-- wakelock infrastructure is now part of upstream Linux- PSI (Pressure Stall Information) -- originally developed for Android's lmkd, now a standard kernel feature
- The old in-kernel low memory killer (
CONFIG_ANDROID_LOW_MEMORY_KILLER) has been removed; Android now uses a userspace daemon (lmkd) that reads PSI events memfd_create()is gradually replacingashmemfor new code- ION allocator has been replaced by the upstream DMA-BUF heap framework
Still Android-specific:
- Binder driver (deeply integrated with Android's IPC model)
- Incremental FS (specialized for APK streaming)
- Vendor hooks (trace_android_rvh_ and trace_android_vh_)
- UID-based resource accounting (
CONFIG_UID_SYS_STATS,CONFIG_CPU_FREQ_TIMES)
The explicit disabling of the old in-kernel low memory killer is visible in the
base config fragments. In the Android 16 (branch b) config for kernel 6.12:
Source: kernel/configs/b/android-6.12/android-base.config, line 2.
This single line tells the story of a multi-year migration: the kernel's in-process OOM killer has been replaced by a sophisticated userspace daemon that uses PSI events for more intelligent memory management decisions.
5.2 GKI (Generic Kernel Image)¶
5.2.1 The Fragmentation Problem¶
Before GKI, every Android device shipped a unique kernel. SoC vendors (Qualcomm, MediaTek, Samsung LSI, etc.) would take an Android Common Kernel branch, apply hundreds of patches for their SoC, and pass it to device OEMs who would apply yet more patches for their specific hardware. The result was a deeply fragmented ecosystem:
- Security patches could not be delivered to kernels without vendor cooperation
- Each device had a unique kernel binary that could not be updated independently
- Kernel bugs required fixes to propagate through multiple vendor trees
- Testing at scale was impossible because no two devices ran the same kernel
graph TD
subgraph "Pre-GKI: Fragmented Kernel Ecosystem"
UL["Upstream Linux LTS"]
ACK["Android Common Kernel"]
QC["Qualcomm Kernel Fork"]
MT["MediaTek Kernel Fork"]
SS["Samsung Kernel Fork"]
D1["Device A Kernel"]
D2["Device B Kernel"]
D3["Device C Kernel"]
D4["Device D Kernel"]
D5["Device E Kernel"]
D6["Device F Kernel"]
UL -->|"merge"| ACK
ACK -->|"fork + SoC patches"| QC
ACK -->|"fork + SoC patches"| MT
ACK -->|"fork + SoC patches"| SS
QC -->|"fork + board patches"| D1
QC -->|"fork + board patches"| D2
MT -->|"fork + board patches"| D3
MT -->|"fork + board patches"| D4
SS -->|"fork + board patches"| D5
SS -->|"fork + board patches"| D6
end
style UL fill:#c8e6c9
style D1 fill:#ffcdd2
style D2 fill:#ffcdd2
style D3 fill:#ffcdd2
style D4 fill:#ffcdd2
style D5 fill:#ffcdd2
style D6 fill:#ffcdd2
5.2.2 GKI 2.0 Architecture¶
GKI solves this by splitting the kernel into two parts:
-
GKI core kernel -- a single binary built by Google from the Android Common Kernel source. This binary is identical across all devices using the same kernel version.
-
Vendor modules -- loadable kernel modules (
.kofiles) that contain all SoC-specific and device-specific code. These are built by vendors against a stable Kernel Module Interface (KMI).
graph TB
subgraph "GKI 2.0 Architecture"
direction TB
subgraph "Google-Built (Identical Across Devices)"
GKI["GKI Core Kernel Image<br/>vmlinux / Image.lz4"]
GKIM["GKI Kernel Modules<br/>(system_dlkm partition)"]
end
subgraph "Kernel Module Interface (KMI)"
KMI["Stable Symbol List<br/>~35,710 exported symbols<br/>abi_symbollist"]
ABI["ABI Definition<br/>abi.stg (7.8 MB)"]
end
subgraph "Vendor-Built (SoC/Device Specific)"
VM["Vendor Kernel Modules<br/>(vendor partition)"]
VD["Vendor DLKM<br/>(vendor_dlkm partition)"]
end
GKI -->|"exports symbols via"| KMI
KMI -->|"consumed by"| VM
KMI -->|"consumed by"| VD
GKIM -->|"also uses"| KMI
end
subgraph "Device Partitions"
BP["boot.img<br/>(GKI kernel)"]
SD["system_dlkm.img<br/>(GKI modules)"]
VP["vendor.img<br/>(vendor modules)"]
VDP["vendor_dlkm.img<br/>(vendor DLKM)"]
end
GKI --> BP
GKIM --> SD
VM --> VP
VD --> VDP
style GKI fill:#c8e6c9
style GKIM fill:#c8e6c9
style KMI fill:#fff9c4
style VM fill:#e1f5fe
style VD fill:#e1f5fe
5.2.3 The Kernel Module Interface (KMI)¶
The KMI is the contract between the GKI core kernel and vendor modules. It consists of:
- A symbol list -- the set of kernel functions and variables that vendor modules are allowed to call. For kernel 6.6, this list contains approximately 35,710 entries.
Source: kernel/prebuilts/6.6/arm64/abi_symbollist (35,710 lines)
The symbol list begins with commonly used symbols and is organized into sections:
[abi_symbol_list]
# commonly used symbols
module_layout
__put_task_struct
utf8_data_table
[abi_symbol_list]
add_cpu
add_device_randomness
add_timer
...
- An ABI definition -- a machine-readable description of the types, structures, and function signatures exported by the KMI. For kernel 6.6, this file is approximately 7.8 MB.
Source: kernel/prebuilts/6.6/arm64/abi.stg (7,819,214 bytes)
- Module versioning (
CONFIG_MODVERSIONS=y) -- CRC checksums are computed for each exported symbol based on its prototype. A module compiled against one version of a symbol cannot be loaded if the symbol's signature has changed.
The KMI is frozen for each GKI release. Once frozen, Google guarantees that the symbol list and ABI will not change in backwards-incompatible ways for the lifetime of that kernel branch. This allows vendors to ship module updates independently of kernel updates, and vice versa.
5.2.4 KMI Symbol Stability Guarantees¶
The config option CONFIG_MODVERSIONS=y (present in all Android base configs)
enables compile-time CRC generation for every exported symbol. When a module is
loaded, the kernel checks that the CRCs in the module match the CRCs in the
running kernel. If they do not match, the module load fails with an error like:
This is the enforcement mechanism for KMI stability: even if the symbol name exists, a change to its type signature will be detected and rejected.
5.2.5 Vendor Hooks¶
Since vendors cannot modify the GKI core kernel, they need a mechanism to customize kernel behavior for their SoC. GKI provides this through vendor hooks -- lightweight tracepoints that vendors can register callbacks for:
android_vh_*(vendor hooks) -- standard tracepoints that vendors can attach to. These are safe to call from any context.android_rvh_*(restricted vendor hooks) -- hooks in performance-critical paths where the callback must meet stricter requirements.
The KMI symbol list includes vendor hook registration functions:
Source: kernel/prebuilts/6.6/arm64/abi_symbollist, line 28
Vendor hooks allow SoC vendors to:
- Customize the scheduler for their big.LITTLE/DynamIQ CPU topology
- Add thermal management logic tied to specific sensor hardware
- Implement custom memory management policies
- Hook into power management decisions
5.2.6 GKI Prebuilt Kernels in AOSP¶
The AOSP tree ships prebuilt GKI kernels for use by the emulator and reference devices. These prebuilts include:
kernel/prebuilts/
6.1/
arm64/
x86_64/
6.6/
arm64/ # 114 files total, ~96 .ko modules
kernel-6.6 # Uncompressed kernel image
kernel-6.6-gz # Gzip-compressed kernel
kernel-6.6-lz4 # LZ4-compressed kernel
kernel-6.6-allsyms # Debug kernel with all symbols
kernel-6.6-gz-allsyms # Debug compressed kernel
kernel-6.6-lz4-allsyms # Debug LZ4 compressed kernel
vmlinux # ELF kernel with debug info
System.map # Symbol address map
System.map-allsyms # Full symbol map
abi_symbollist # KMI symbol list (35,710 lines)
abi_symbollist.raw # Raw symbol names
abi.stg # ABI definition (~7.8 MB)
abi-full.stg # Full ABI definition
kernel_version.mk # Version string for build system
*.ko # ~96 GKI kernel modules
x86_64/
6.12/
arm64/
x86_64/
common-modules/
virtual-device/
6.1/
6.6/
arm64/ # 57 device-specific modules
x86-64/
6.12/
mainline/
trusty/
mainline/
arm64/
x86_64/
The kernel version string for the 6.6 arm64 prebuilt reveals its lineage:
Source: kernel/prebuilts/6.6/arm64/kernel_version.mk
Breaking this down:
6.6.100-- upstream LTS version 6.6, patch level 100android15-- Android 15 ACK branch8-- eighth release from this branchgf988247102d3-- git commit hashab14039625-- Android build ID4k-- 4K page size variant
5.2.7 GKI Release Lifecycle¶
Each GKI kernel branch has a defined lifecycle with launch and end-of-life (EOL)
dates. These are tracked in kernel/configs/kernel-lifetimes.xml:
<branch name="android16-6.12"
min_android_release="16"
version="6.12"
launch="2024-11-17"
eol="2029-07-01">
<lts-versions>
<release version="6.12.23" launch="2025-06-12" eol="2026-10-01"/>
<release version="6.12.30" launch="2025-07-11" eol="2026-11-01"/>
<release version="6.12.38" launch="2025-08-11" eol="2026-12-01"/>
</lts-versions>
</branch>
Source: kernel/configs/kernel-lifetimes.xml, lines 146-152
Key observations from this file:
- Kernel branches span multiple years (e.g., android14-6.1 runs from 2022 to 2029)
- Each branch has specific LTS releases with their own EOL dates
- Quarterly GKI releases have a 12-15 month support window
- Older branches (pre-5.10) are marked as "non-GKI kernel" since GKI was introduced with kernel 5.10 for Android 12
The complete lineage of supported kernel versions:
| Branch | Kernel | Min Android | Launch | EOL |
|---|---|---|---|---|
| android12-5.10 | 5.10 | 12 | 2020-12 | 2027-07 |
| android13-5.15 | 5.15 | 13 | 2021-10 | 2028-07 |
| android14-5.15 | 5.15 | 14 | 2021-10 | 2028-07 |
| android14-6.1 | 6.1 | 14 | 2022-12 | 2029-07 |
| android15-6.6 | 6.6 | 15 | 2023-10 | 2028-07 |
| android16-6.12 | 6.12 | 16 | 2024-11 | 2029-07 |
5.2.8 How Vendors Extend Without Forking¶
Under GKI, the vendor extension model works as follows:
sequenceDiagram
participant G as Google
participant V as Vendor (SoC)
participant O as OEM
G->>G: Build GKI kernel from ACK branch
G->>G: Freeze KMI (symbol list + ABI)
G->>V: Publish GKI kernel + KMI headers
V->>V: Build vendor modules against KMI
V->>V: Register vendor hook callbacks
V->>O: Ship vendor modules (.ko files)
O->>O: Assemble boot.img (GKI kernel)
O->>O: Assemble vendor.img (vendor modules)
O->>O: Assemble vendor_dlkm.img (optional)
Note over G,O: Later: Google releases kernel security update
G->>G: Build updated GKI kernel (KMI preserved)
G->>O: Ship updated boot.img via OTA
Note over O: Vendor modules continue to work<br/>because KMI is stable
This separation means:
- Google can update the kernel for security fixes without vendor involvement
- Vendors can update their modules without waiting for a kernel update
- OEMs can mix and match GKI kernel versions with vendor module versions (within the same KMI generation)
5.3 Key Android-Specific Kernel Features¶
5.3.1 Binder Driver¶
Binder is Android's inter-process communication (IPC) mechanism. Every interaction between apps and system services -- launching an Activity, binding a Service, querying a ContentProvider, sending an Intent -- flows through Binder. The Binder driver is the kernel component that makes this possible.
Kernel Configuration¶
Binder requires two config options in the Android base config:
Source: kernel/configs/b/android-6.12/android-base.config, lines 18-19
The CONFIG_ANDROID_BINDERFS option enables binderfs, a special filesystem
that allows dynamic creation of Binder device nodes. This replaced the
traditional approach of creating /dev/binder, /dev/hwbinder, and
/dev/vndbinder as static device nodes.
Transaction Model¶
The Binder driver implements a synchronous RPC mechanism with the following characteristics:
sequenceDiagram
participant CA as Client Process (App)
participant BD as Binder Driver (/dev/binder)
participant SA as Server Process (system_server)
CA->>BD: ioctl(BINDER_WRITE_READ)<br/>BC_TRANSACTION
Note over BD: Copy transaction data<br/>from client address space
BD->>BD: Allocate buffer in<br/>server's mmap region
Note over BD: Copy data into server's<br/>pre-mapped buffer<br/>(single copy!)
BD->>SA: Wake server thread<br/>BR_TRANSACTION
SA->>SA: Process request
SA->>BD: ioctl(BINDER_WRITE_READ)<br/>BC_REPLY
Note over BD: Copy reply data<br/>to client's mmap region
BD->>CA: Wake client thread<br/>BR_REPLY
Key aspects of the transaction model:
-
Single-copy data transfer: The Binder driver uses
mmap()to create a shared memory region in the server process's address space. When a client sends a transaction, the driver copies data directly from the client's user-space buffer into the server's mmap'ed region. This means data is copied only once (client user-space to server kernel-mapped buffer), rather than the two copies required by traditional IPC mechanisms (client to kernel, kernel to server). -
Object translation: Binder handles (references to remote objects) are translated by the driver as transactions cross process boundaries. The driver maintains a reference-counted mapping of Binder nodes and their proxy handles.
-
Death notifications: A process can register for notification when a Binder object in another process dies. The driver tracks these registrations and sends
BR_DEAD_BINDERnotifications when the hosting process exits. -
Security context propagation: The driver embeds the caller's PID, UID, and SELinux security context into every transaction, allowing the server to make authorization decisions.
Memory Mapping¶
Each process that opens the Binder device maps a region of memory using mmap().
This region is used by the driver to deliver transaction data:
graph LR
subgraph "Client Process"
CUS["User Space<br/>Parcel data buffer"]
end
subgraph "Kernel"
BD["Binder Driver"]
KBuf["Kernel buffer<br/>(copy_from_user)"]
end
subgraph "Server Process"
SMap["mmap'd region<br/>(1 MB default)"]
SUS["User Space<br/>Unmarshalled data"]
end
CUS -->|"1. copy_from_user"| KBuf
KBuf -->|"2. copy to mmap region<br/>(already mapped in server)"| SMap
SMap -->|"3. Direct access<br/>(no copy needed)"| SUS
style KBuf fill:#fff9c4
style SMap fill:#c8e6c9
The default mmap size is 1 MB minus two pages (1,048,576 - 8,192 = 1,040,384 bytes). This is the maximum amount of data that can be in flight for a single process's incoming transactions at any given time.
Thread Management¶
The Binder driver manages a thread pool for each process:
- When a process opens the Binder device, it registers itself with
BINDER_SET_CONTEXT_MGR(for servicemanager) or starts handling transactions. - The driver can request the creation of new threads via
BR_SPAWN_LOOPERwhen all existing threads are busy. - The maximum thread count is set via
BINDER_SET_MAX_THREADS. - Threads enter the driver via
ioctl(BINDER_WRITE_READ)and block until a transaction arrives or there is a reply to deliver.
The userspace side of Binder thread management is implemented in
frameworks/native/libs/binder/IPCThreadState.cpp:
// frameworks/native/libs/binder/IPCThreadState.cpp
#include <binder/IPCThreadState.h>
#include <sys/ioctl.h>
#include "binder_module.h"
Source: frameworks/native/libs/binder/IPCThreadState.cpp
The Three Binder Domains¶
Modern Android uses three separate Binder domains, each with its own device node, to enforce the Treble architecture separation:
| Domain | Device | Purpose | Users |
|---|---|---|---|
| Framework | /dev/binder |
App-to-framework IPC | Apps, system_server |
| Hardware | /dev/hwbinder |
Framework-to-HAL IPC | system_server, HAL processes |
| Vendor | /dev/vndbinder |
Vendor-internal IPC | Vendor processes only |
With binderfs (CONFIG_ANDROID_BINDERFS=y), these device nodes are created
dynamically by mounting a binderfs filesystem, rather than being statically
created via mknod. This allows containerized Android instances to have their
own isolated Binder namespaces.
5.3.2 DMA-BUF Heap System¶
From ION to DMA-BUF Heaps¶
The ION memory allocator was Android's original solution for allocating
physically contiguous or otherwise specially-constrained memory buffers for use
by GPUs, cameras, video codecs, and display hardware. ION was an Android-only
out-of-tree driver that lived in drivers/staging/android/.
Starting with kernel 5.10, ION has been replaced by the upstream DMA-BUF heap framework. This framework provides the same functionality -- allocating DMA-capable buffers that can be shared between hardware devices and userspace -- but through a standard, upstream kernel interface.
graph TB
subgraph "Old: ION Allocator (Deprecated)"
ION_DEV["/dev/ion"]
ION_HEAP["ION Heap<br/>(system, CMA, carveout)"]
ION_BUF["ION Buffer"]
ION_FD["DMA-BUF fd"]
ION_DEV --> ION_HEAP
ION_HEAP --> ION_BUF
ION_BUF --> ION_FD
end
subgraph "New: DMA-BUF Heaps (Upstream)"
HEAP_DEV["/dev/dma_heap/{name}"]
SYS_HEAP["System Heap<br/>/dev/dma_heap/system"]
CMA_HEAP["CMA Heap<br/>/dev/dma_heap/linux,cma"]
VENDOR_HEAP["Vendor Heaps<br/>/dev/dma_heap/{vendor}"]
DMABUF_FD["DMA-BUF fd"]
HEAP_DEV --> SYS_HEAP
HEAP_DEV --> CMA_HEAP
HEAP_DEV --> VENDOR_HEAP
SYS_HEAP --> DMABUF_FD
CMA_HEAP --> DMABUF_FD
VENDOR_HEAP --> DMABUF_FD
end
style ION_DEV fill:#ffcdd2
style HEAP_DEV fill:#c8e6c9
How DMA-BUF Heaps Work¶
-
Heap registration: Kernel drivers register heaps with the DMA-BUF heap framework, each appearing as a character device under
/dev/dma_heap/. -
Allocation: Userspace opens the appropriate heap device and calls
ioctl(DMA_HEAP_IOCTL_ALLOC)to allocate a buffer. The returned file descriptor is a DMA-BUF fd. -
Sharing: The DMA-BUF fd can be passed to other processes via Unix domain sockets or Binder. Any process with the fd can map the buffer into its address space or pass it to a hardware device driver.
-
Zero-copy pipeline: The GPU, camera, and display can all reference the same physical memory through the DMA-BUF fd, avoiding copies in the graphics pipeline.
The emulator's goldfish modules include a system_heap.ko module that provides
a DMA-BUF system heap for the virtual device:
Source: prebuilts/qemu-kernel/arm64/6.12/goldfish_modules/system_heap.ko
Configuration¶
The GKI base config requires sync file support, which is the userspace-facing interface for DMA-BUF synchronization fences:
Source: kernel/configs/b/android-6.12/android-base.config, line 238
5.3.3 FUSE Passthrough and Storage Access¶
The Storage Access Problem¶
Android's storage model has undergone several revisions:
- Pre-Android 10: SDCardFS (a stackable filesystem) provided per-app storage views with different permission sets.
- Android 10+: SDCardFS was deprecated in favor of FUSE, which runs entirely in userspace via the MediaProvider process.
- Android 12+: FUSE passthrough was introduced to recover the performance lost by routing all I/O through a userspace daemon.
graph TB
subgraph "App Storage Access Flow"
APP["Application"]
VFS["VFS Layer"]
subgraph "FUSE Path"
FUSE_K["FUSE Kernel Module"]
MP["MediaProvider<br/>(userspace daemon)"]
PT["Passthrough<br/>(direct to lower fs)"]
end
FS["Lower Filesystem<br/>(ext4 / f2fs)"]
APP -->|"open(/storage/emulated/0/...)"| VFS
VFS -->|"FUSE mount"| FUSE_K
FUSE_K -->|"permission check"| MP
MP -->|"authorized"| PT
PT -->|"direct I/O"| FS
FUSE_K -.->|"slow path:<br/>data through userspace"| MP
MP -.->|"slow path"| FS
end
style PT fill:#c8e6c9
style MP fill:#fff9c4
How FUSE Passthrough Works¶
FUSE passthrough allows the FUSE daemon (MediaProvider) to indicate that certain file operations should be handled directly by the kernel, bypassing the FUSE userspace daemon for data transfer:
- The app opens a file through the FUSE mount (e.g.,
/storage/emulated/0/Download/photo.jpg). - The FUSE kernel module sends an
OPENrequest to MediaProvider. - MediaProvider checks permissions and, if authorized, opens the underlying file on the real filesystem and tells the FUSE kernel module to use passthrough for this file.
- Subsequent
read()andwrite()calls from the app go directly from the FUSE kernel module to the lower filesystem, bypassing MediaProvider entirely.
This provides the security benefits of MediaProvider's permission checking while recovering nearly native filesystem performance for actual data I/O.
Configuration¶
FUSE filesystem support is mandatory in the Android base config:
Source: kernel/configs/b/android-6.12/android-base.config, line 69
5.3.4 Incremental FS¶
Purpose and Design¶
Incremental FS (incfs) enables Android to start using an APK before all of its
data has been downloaded. This is the kernel component that supports Android's
"Incremental APK Installation" feature, which allows large apps (especially
games) to launch while still streaming their data.
sequenceDiagram
participant PM as Package Manager
participant IDS as Incremental Data Service
participant INCFS as Incremental FS (kernel)
participant APP as Application
PM->>INCFS: Mount incfs for package
PM->>IDS: Begin streaming APK data
IDS->>INCFS: Fill initial blocks<br/>(IOC_FILL_BLOCKS)
APP->>INCFS: Read block N
alt Block N is present
INCFS->>APP: Return data immediately
else Block N is missing
INCFS->>IDS: Request block N<br/>(via .pending_reads)
IDS->>IDS: Download block N<br/>from server
IDS->>INCFS: Fill block N<br/>(IOC_FILL_BLOCKS)
INCFS->>APP: Return data
end
Kernel Interface¶
The Incremental FS kernel module exposes its interface through ioctl commands
defined in the userspace header at
system/incremental_delivery/incfs/kernel-headers/linux/incrementalfs.h:
#define INCFS_NAME "incremental-fs"
#define INCFS_MAGIC_NUMBER (0x5346434e49ul & ULONG_MAX)
#define INCFS_DATA_FILE_BLOCK_SIZE 4096
#define INCFS_IOC_CREATE_FILE \
_IOWR(INCFS_IOCTL_BASE_CODE, 30, struct incfs_new_file_args)
#define INCFS_IOC_FILL_BLOCKS \
_IOR(INCFS_IOCTL_BASE_CODE, 32, struct incfs_fill_blocks)
#define INCFS_IOC_GET_FILLED_BLOCKS \
_IOR(INCFS_IOCTL_BASE_CODE, 34, struct incfs_get_filled_blocks_args)
Source: system/incremental_delivery/incfs/kernel-headers/linux/incrementalfs.h
Key design characteristics:
-
Block-level granularity: Files are divided into 4 KB blocks. Each block can be independently present or absent.
-
Demand paging: When a process reads a block that has not yet been delivered, the kernel blocks the read and signals the userspace data loader (via the
.pending_readsspecial file) to fetch that block. -
Compression support: Blocks can be stored compressed using LZ4 or Zstd:
-
Integrity verification: Incremental FS supports per-file hash trees (
INCFS_BLOCK_FLAGS_HASH) and fs-verity integration (INCFS_XATTR_VERITY_NAME) to verify block integrity as blocks arrive. -
Special files: The filesystem exposes several special files for monitoring and control:
.pending_reads-- read by the data loader to discover which blocks are needed.log-- access log for debugging.blocks_written-- tracks write progress.index-- maps file IDs to inodes.incomplete-- lists files that are not yet fully loaded
Userspace Integration¶
The userspace component lives in system/incremental_delivery/incfs/:
system/incremental_delivery/
incfs/
Android.bp
incfs.cpp # Core incfs library
incfs_ndk.c # NDK interface
MountRegistry.cpp # Mount point tracking
path.cpp # Path utilities
kernel-headers/
linux/
incrementalfs.h # Kernel UAPI header
libdataloader/ # Data loader service interface
sysprop/ # System properties for incfs
Source: system/incremental_delivery/incfs/
5.3.5 Ashmem and Shared Memory¶
Android shared memory (ashmem) provides named, reference-counted shared memory regions. It is required by the base config:
Source: kernel/configs/b/android-6.12/android-base.config, line 20
Ashmem differs from standard POSIX shared memory (shm_open) in several ways:
- Regions can be pinned and unpinned, allowing the kernel to reclaim unpinned pages under memory pressure
- Regions are reference-counted by file descriptors -- when the last fd is closed, the memory is freed
- Regions can be sealed (made immutable) for security
While ashmem remains required for backward compatibility, new code is encouraged
to use memfd_create(), which is the upstream Linux equivalent and provides
similar functionality through the standard kernel API.
5.3.6 Wakelocks and Power Management¶
Android's wakelock mechanism prevents the system from entering suspend while critical operations are in progress. The kernel component is:
Source: kernel/configs/b/android-6.12/android-base.config, line 210
Note that CONFIG_PM_AUTOSLEEP is explicitly disabled:
Source: kernel/configs/b/android-6.12/android-base.config, line 12
This is because Android manages the sleep/wake cycle from userspace (through the PowerManager service) rather than relying on the kernel's autosleep mechanism.
The wakelock interface is exposed through:
/sys/power/wake_lock-- write a wakelock name to acquire/sys/power/wake_unlock-- write a wakelock name to release
The userspace PowerManager service (in system_server) uses these interfaces to implement Android's opportunistic suspend model, where the system aggressively tries to enter suspend unless something holds a wakelock.
5.3.7 Low Memory Killer Daemon (lmkd)¶
The in-kernel low memory killer (CONFIG_ANDROID_LOW_MEMORY_KILLER) has been
removed from modern Android kernels. It is replaced by a userspace daemon,
lmkd, that makes more intelligent decisions about which processes to kill
under memory pressure.
Architecture¶
graph TB
subgraph "Kernel"
PSI_K["PSI Monitor<br/>/proc/pressure/memory"]
MEMINFO["/proc/meminfo"]
VMSTAT["/proc/vmstat"]
ZONEINFO["/proc/zoneinfo"]
CGROUPS["cgroups<br/>memory.pressure"]
end
subgraph "lmkd Daemon"
PSI_M["PSI Event Monitor"]
POLL["epoll Loop"]
MEM_CALC["Memory Pressure<br/>Calculator"]
KILL["Process Killer<br/>(reaper threads)"]
STATS["Statistics<br/>(statslog)"]
WATCH["Watchdog"]
end
subgraph "ActivityManager<br/>(system_server)"
OOM_ADJ["OOM Adj Scores<br/>(process priority)"]
PROC_LIST["Process List"]
end
PSI_K -->|"PSI events"| PSI_M
MEMINFO -->|"memory stats"| MEM_CALC
VMSTAT -->|"paging stats"| MEM_CALC
ZONEINFO -->|"zone stats"| MEM_CALC
OOM_ADJ -->|"socket"| POLL
PSI_M --> POLL
POLL --> MEM_CALC
MEM_CALC -->|"kill decision"| KILL
KILL -->|"pidfd_send_signal()"| PSI_K
KILL --> STATS
style PSI_K fill:#c8e6c9
style PSI_M fill:#e1f5fe
style MEM_CALC fill:#fff9c4
PSI-Based Triggering¶
lmkd uses the kernel's Pressure Stall Information (PSI) interface to detect memory pressure. PSI was developed in close collaboration between Google and the kernel community and is now a standard kernel feature.
The PSI monitor configuration in lmkd:
#define DEFAULT_PSI_WINDOW_SIZE_MS 1000
#define PSI_POLL_PERIOD_SHORT_MS 10
#define PSI_POLL_PERIOD_LONG_MS 100
Source: system/memory/lmkd/lmkd.cpp, lines 117-121
The PSI interface is initialized through the libpsi library:
// system/memory/lmkd/libpsi/psi.cpp
int init_psi_monitor(enum psi_stall_type stall_type,
int threshold_us,
int window_us,
enum psi_resource resource) {
fd = TEMP_FAILURE_RETRY(open(psi_resource_file[resource],
O_WRONLY | O_CLOEXEC));
Source: system/memory/lmkd/libpsi/psi.cpp
OOM Adjustment Scores¶
lmkd receives process priority information from ActivityManager in system_server. Each process is assigned an OOM adjustment score that reflects its importance:
#define SYSTEM_ADJ (-900) // System processes (never kill)
#define PERCEPTIBLE_APP_ADJ 200 // Perceptible but not foreground
#define PREVIOUS_APP_ADJ 700 // Previous foreground app
Source: system/memory/lmkd/lmkd.cpp, lines 79-80, 98
When memory pressure is detected, lmkd kills processes starting from the highest OOM adjustment score (least important) and works downward until sufficient memory is freed.
Key Source Files¶
system/memory/lmkd/
lmkd.cpp # Main daemon logic (2000+ lines)
lmkd.rc # init.rc service definition
reaper.cpp # Process kill execution (using pidfd)
reaper.h
watchdog.cpp # Watchdog timer for lmkd hangs
watchdog.h
statslog.cpp # Statistics reporting
statslog.h
libpsi/
psi.cpp # PSI monitor interface
include/
psi/psi.h # PSI header
liblmkd_utils.cpp # Utility functions
Source: system/memory/lmkd/
5.3.8 dm-verity and Verified Boot¶
dm-verity is a device-mapper target that provides transparent integrity checking of block devices. Android uses it to verify the integrity of system partitions during boot:
Source: kernel/configs/b/android-6.12/android-base.config, line 60
dm-verity works by maintaining a hash tree (Merkle tree) of the entire partition. On every read, the driver computes the hash of the data block and verifies it against the hash tree. If verification fails, the read returns an I/O error.
How dm-verity's Merkle Tree Works¶
graph TB
subgraph "Partition Data"
B0["Block 0<br/>4 KB"]
B1["Block 1<br/>4 KB"]
B2["Block 2<br/>4 KB"]
B3["Block 3<br/>4 KB"]
BN["Block N<br/>4 KB"]
end
subgraph "Hash Tree (Merkle Tree)"
H0["hash(Block 0)"]
H1["hash(Block 1)"]
H2["hash(Block 2)"]
H3["hash(Block 3)"]
HN["hash(Block N)"]
P0["hash(H0 || H1)"]
P1["hash(H2 || H3)"]
PN["hash(... || HN)"]
ROOT["Root Hash<br/>(signed by OEM key)"]
end
B0 --> H0
B1 --> H1
B2 --> H2
B3 --> H3
BN --> HN
H0 --> P0
H1 --> P0
H2 --> P1
H3 --> P1
HN --> PN
P0 --> ROOT
P1 --> ROOT
PN --> ROOT
style ROOT fill:#ffcdd2
The root hash is signed by the device OEM's key and verified by the bootloader before the kernel is loaded. This creates an unbroken chain of trust from the bootloader to every individual data block on the system partition.
dm-verity operates in several modes:
- Enforcing (default): I/O errors on verification failure. The device may restart in recovery mode.
- Logging: Verification failures are logged but reads succeed. Used during development.
- EIO: Returns
EIOerrors on verification failure but continues operation.
The verified boot chain in Android combines several kernel subsystems:
graph LR
BL["Bootloader<br/>(verifies boot.img)"]
KERNEL["Kernel<br/>(dm-verity for partitions)"]
FBE["File-Based Encryption<br/>(dm-default-key)"]
VERITY["fs-verity<br/>(per-file integrity)"]
APK["APK Signature<br/>(framework-level)"]
BL -->|"verified boot"| KERNEL
KERNEL -->|"partition integrity"| FBE
FBE -->|"data confidentiality"| VERITY
VERITY -->|"file integrity"| APK
Related config options for the full verified boot chain:
CONFIG_DM_DEFAULT_KEY=y # Default key for dm-crypt
CONFIG_DM_SNAPSHOT=y # Snapshot support for OTA
CONFIG_FS_ENCRYPTION=y # File-based encryption
CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y # Hardware inline crypto
CONFIG_FS_VERITY=y # Per-file integrity (fs-verity)
CONFIG_BLK_INLINE_ENCRYPTION=y # Block-level inline encryption
File-Based Encryption (FBE)¶
Android uses file-based encryption rather than full-disk encryption. This allows different files to be encrypted with different keys, enabling features like Direct Boot (where the device can show the lock screen and receive phone calls before the user unlocks the device).
The encryption configuration is visible in the emulator's fstab:
Source: device/generic/goldfish/board/fstab/arm
This specifies:
aes-256-xtsfor file content encryption (provides confidentiality)aes-256-ctsfor file name encryption (prevents metadata leakage)
fs-verity: Per-File Integrity¶
While dm-verity protects entire partitions, fs-verity (CONFIG_FS_VERITY=y)
provides per-file integrity verification. It is used for:
- Verifying APK files after installation (complementing APK signatures)
- Protecting system files on writable partitions
- Ensuring integrity of downloaded content
fs-verity uses the same Merkle tree concept as dm-verity but applies it to individual files. Once a file has fs-verity enabled, any modification to its contents will be detected as a hash mismatch on the next read.
5.3.9 eBPF Integration¶
Android uses eBPF (extended Berkeley Packet Filter) extensively for networking, monitoring, and security:
CONFIG_BPF_JIT=y
CONFIG_BPF_SYSCALL=y
CONFIG_CGROUP_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=y
CONFIG_NET_ACT_BPF=y
CONFIG_NET_CLS_BPF=y
Source: kernel/configs/b/android-6.12/android-base.config
The 6.12 config additionally requires:
eBPF programs are loaded during boot from /system/etc/bpf/ and are used for:
- Network traffic accounting per UID
- Network firewall rules
- CPU frequency tracking
- Tracing and profiling
eBPF Architecture on Android¶
graph TB
subgraph "Userspace"
LOADER["BPF Loader<br/>(bpfloader service)"]
PROG_DIR["/system/etc/bpf/<br/>BPF ELF programs"]
NETD["netd<br/>(network daemon)"]
TETHERING["Tethering Service"]
TRAFFIC["Traffic Controller"]
end
subgraph "Kernel"
VERIFIER["BPF Verifier"]
JIT["BPF JIT Compiler"]
MAPS["BPF Maps<br/>(per-UID counters,<br/>policy tables)"]
HOOKS["BPF Hook Points"]
subgraph "Hook Locations"
CGH["cgroup/skb<br/>(per-app network)"]
XDP["XDP<br/>(packet processing)"]
TPH["tracepoint<br/>(kernel events)"]
SCH["sched_cls<br/>(traffic control)"]
end
end
PROG_DIR -->|"loaded at boot"| LOADER
LOADER -->|"bpf() syscall"| VERIFIER
VERIFIER -->|"verified safe"| JIT
JIT --> HOOKS
HOOKS --> CGH
HOOKS --> XDP
HOOKS --> TPH
HOOKS --> SCH
NETD --> MAPS
TRAFFIC --> MAPS
TETHERING --> MAPS
style VERIFIER fill:#fff9c4
style JIT fill:#c8e6c9
The BPF loader (bpfloader) is one of the first services started during boot.
It loads all .o (BPF ELF) files from /system/etc/bpf/ and pins them into the
BPF filesystem at /sys/fs/bpf/. Other services like netd and the tethering
service then attach to these pinned programs.
Key eBPF use cases on Android:
-
Per-UID traffic accounting: BPF programs attached to cgroup socket hooks count bytes sent and received per UID, enabling the Settings app's data usage display and per-app data limits.
-
Network firewall: BPF programs implement the iptables replacement for per-app network access control, providing both better performance and more granular control.
-
Tethering offload: BPF programs handle packet forwarding for USB/WiFi tethering, moving the packet processing from userspace (slow) to kernel BPF (fast).
-
CPU frequency tracking: BPF programs attached to scheduler tracepoints track per-UID time spent at each CPU frequency, enabling accurate battery usage attribution.
5.3.10 SELinux Enforcement¶
SELinux (Security-Enhanced Linux) is mandatory on Android and is configured at the kernel level:
CONFIG_SECURITY=y
CONFIG_SECURITY_NETWORK=y
CONFIG_SECURITY_SELINUX=y
CONFIG_DEFAULT_SECURITY_SELINUX=y
Source: kernel/configs/b/android-6.12/android-base.config, lines 222-226, 57
Android runs SELinux in enforcing mode on production devices. Every process,
file, socket, and kernel object is assigned a security label, and the SELinux
policy (compiled from .te files in the AOSP tree) defines which operations are
allowed between labeled objects.
The kernel's SELinux subsystem:
- Labels all kernel objects (inodes, sockets, processes, IPC objects)
- Intercepts security-relevant system calls via Linux Security Module (LSM) hooks
- Checks each operation against the loaded policy
- Denies operations not explicitly allowed
- Logs denied operations to the audit subsystem (
CONFIG_AUDIT=y)
SELinux is the primary mechanism that confines apps to their sandbox, prevents privilege escalation, and limits the impact of compromised system services.
5.3.11 Seccomp Filter¶
Beyond SELinux, Android uses seccomp-BPF filters to restrict the system calls available to each process:
Source: kernel/configs/b/android-6.12/android-base.config, lines 221-222
Seccomp filters use BPF programs (not eBPF -- these are classic BPF) to examine each system call and its arguments. If a system call is not on the allowlist, the process is killed with SIGSYS. This provides defense in depth: even if an attacker escapes the SELinux sandbox, they still cannot invoke dangerous system calls.
Android's seccomp policies are defined per-architecture and are applied by the Zygote process before forking app processes.
5.3.12 Cgroups and Resource Control¶
Android uses Linux cgroups (control groups) extensively for resource management:
CONFIG_CGROUPS=y
CONFIG_CGROUP_BPF=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_SCHED=y
Source: kernel/configs/b/android-6.12/android-base.config, lines 33-37
These cgroups serve specific Android purposes:
| Cgroup Subsystem | Config | Android Usage |
|---|---|---|
| cpuacct | CONFIG_CGROUP_CPUACCT |
Per-UID CPU time accounting |
| freezer | CONFIG_CGROUP_FREEZER |
Freezing cached/background apps |
| cpu (sched) | CONFIG_CGROUP_SCHED |
CPU scheduling priority for app groups |
| bpf | CONFIG_CGROUP_BPF |
Per-app network control via BPF |
The cgroup hierarchy on Android is managed by init and the system_server
process. The init script for the emulator shows the top-level cgroup setup:
on init
mkdir /dev/cpuctl/foreground
mkdir /dev/cpuctl/background
mkdir /dev/cpuctl/top-app
mkdir /dev/cpuctl/rt
Source: device/generic/goldfish/init/init.ranchu.rc, lines 47-50
These cgroup directories correspond to Android's process scheduling groups:
- top-app: The foreground application currently visible to the user
- foreground: Processes the user is aware of (e.g., music player)
- background: Processes running in the background
- rt: Real-time priority processes (audio, sensor processing)
5.4 Device Tree and Board Support¶
5.4.1 Device Tree Fundamentals¶
The device tree is a data structure that describes the hardware topology of a system. On ARM and RISC-V platforms, the bootloader passes a device tree blob (DTB) to the kernel, which uses it to discover and configure hardware devices.
The device tree is necessary because, unlike x86 systems (which use ACPI for hardware discovery), ARM and RISC-V systems do not have a standard mechanism for the kernel to probe hardware. The device tree fills this gap.
The Android base config enforces that at least one hardware description mechanism is present:
<!-- CONFIG_ACPI || CONFIG_OF -->
<group>
<conditions>
<config>
<key>CONFIG_ACPI</key>
<value type="bool">n</value>
</config>
</conditions>
<config>
<key>CONFIG_OF</key>
<value type="bool">y</value>
</config>
</group>
Source: kernel/configs/b/android-6.12/android-base-conditional.xml, lines 148-172
This conditional requirement means: if ACPI is disabled, then Device Tree
(CONFIG_OF) must be enabled, and vice versa. ARM devices use OF (Open
Firmware / Device Tree); x86 devices typically use ACPI.
5.4.2 DTS, DTB, and DTBO¶
Device tree data flows through several formats:
graph LR
DTS[".dts<br/>Device Tree Source<br/>(human-readable)"]
DTSI[".dtsi<br/>Include files<br/>(shared definitions)"]
DTC["dtc<br/>(DT Compiler)"]
DTB[".dtb<br/>Device Tree Blob<br/>(binary, for kernel)"]
DTBO[".dtbo<br/>DT Overlay Blob<br/>(binary overlay)"]
DTSI -->|"#include"| DTS
DTS -->|"compile"| DTC
DTC -->|"output"| DTB
DTC -->|"output"| DTBO
subgraph "Boot Process"
BL["Bootloader"]
MERGED["Merged DTB"]
KERNEL["Linux Kernel"]
BL -->|"base DTB + overlays"| MERGED
MERGED -->|"passed at boot"| KERNEL
end
DTB --> BL
DTBO --> BL
- DTS (Device Tree Source): Human-readable text files that describe hardware. They use a tree structure with nodes representing devices and properties describing their configuration.
- DTSI (Device Tree Source Include): Shared definitions included by multiple DTS files. Typically, the SoC definition is in a DTSI, and each board's DTS includes it and adds board-specific nodes.
- DTB (Device Tree Blob): Compiled binary form of a DTS file. This is what the kernel actually parses.
- DTBO (Device Tree Blob Overlay): A compiled overlay that can be applied on top of a base DTB. Overlays allow board-specific customization without modifying the base SoC DTB.
5.4.3 Device Tree Overlays (DTBO)¶
Android uses device tree overlays extensively to separate SoC-level and board-level hardware descriptions:
graph TB
subgraph "SoC Vendor"
BASE_DTB["Base DTB<br/>(SoC definition)<br/>CPU, memory controller,<br/>interrupt controller,<br/>bus topology"]
end
subgraph "Device OEM"
OV1["DTBO: Display Panel<br/>resolution, timing,<br/>backlight GPIO"]
OV2["DTBO: Camera Sensor<br/>I2C address, lanes,<br/>clock configuration"]
OV3["DTBO: Audio Codec<br/>I2S configuration,<br/>amplifier GPIOs"]
OV4["DTBO: Touch Controller<br/>interrupt GPIO,<br/>I2C address"]
end
subgraph "Bootloader"
MERGE["Overlay Application<br/>(libufdt)"]
FINAL["Final Merged DTB"]
end
BASE_DTB --> MERGE
OV1 --> MERGE
OV2 --> MERGE
OV3 --> MERGE
OV4 --> MERGE
MERGE --> FINAL
FINAL -->|"Passed to kernel<br/>at boot"| KERNEL["Linux Kernel"]
The DTBO partition is a standard Android partition that contains one or more overlays. During boot, the bootloader reads the base DTB (typically compiled into the kernel image or stored in a separate partition), reads the overlays from the DTBO partition, applies them using the libufdt library, and passes the merged result to the kernel.
5.4.4 Emulator (Goldfish) Device Tree¶
The Android emulator uses device tree to describe its virtual hardware. The goldfish virtual device includes a precompiled DTB:
Source: kernel/prebuilts/common-modules/virtual-device/6.6/arm64/fvp-base-revc.dtb
The emulator board configuration in device/generic/goldfish/ explicitly states
that it does not include a DTB in the boot image:
Source: device/generic/goldfish/board/BoardConfigCommon.mk, line 75
Instead, the emulator's QEMU host provides device information through a combination of:
- Device tree passed by QEMU to the virtual machine
- virtio device discovery for paravirtualized devices
- Platform device registration for goldfish-specific virtual hardware
The emulator's goldfish virtual platform includes these device-specific kernel modules:
goldfish_address_space.ko # Virtual address space for host communication
goldfish_battery.ko # Virtual battery with host-controlled state
goldfish_pipe.ko # High-bandwidth host-guest communication pipe
goldfish_sync.ko # Synchronization primitives for GPU emulation
Source: prebuilts/qemu-kernel/arm64/6.12/goldfish_modules/
5.4.5 Virtual Device Modules¶
Beyond the goldfish-specific modules, the emulator loads a substantial set of GKI and virtual device modules. The virtual device common modules for kernel 6.6 arm64 include 57 modules:
kernel/prebuilts/common-modules/virtual-device/6.6/arm64/
virtio_dma_buf.ko # DMA buffer sharing via virtio
virtio_mmio.ko # Memory-mapped virtio transport
virtio-rng.ko # Virtual random number generator
virtio_net.ko # Virtual network adapter
virtio_input.ko # Virtual input devices
virtio_snd.ko # Virtual sound device
virtio-gpu.ko # Virtual GPU (3D acceleration)
virtio-media.ko # Virtual media device
cfg80211.ko # Wireless configuration
mac80211.ko # IEEE 802.11 wireless stack
mac80211_hwsim.ko # Simulated wireless hardware
system_heap.ko # DMA-BUF system heap
...
Source: kernel/prebuilts/common-modules/virtual-device/6.6/arm64/
5.4.6 Device Tree Syntax Reference¶
A simplified example of what a goldfish-style device tree might look like:
/dts-v1/;
/ {
compatible = "android,goldfish";
#address-cells = <2>;
#size-cells = <2>;
chosen {
bootargs = "8250.nr_uarts=1";
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x80000000>; /* 2 GB */
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
compatible = "arm,armv8";
reg = <0x0>;
enable-method = "psci";
};
};
virtio_mmio@a003c00 {
compatible = "virtio,mmio";
reg = <0x0 0xa003c00 0x0 0x200>;
interrupts = <0 43 4>;
/* Block device for /data partition */
};
/* Additional virtio devices for network, GPU, etc. */
};
Key elements:
compatiblestrings identify the driver that should bind to each deviceregproperties specify the memory-mapped I/O address and sizeinterruptsspecify the interrupt number and type- The
chosennode passes kernel command-line arguments
5.4.7 Device Tree and Driver Binding¶
The kernel uses the compatible property to match device tree nodes to drivers.
When the kernel encounters a device tree node, it searches through all registered
platform drivers for one whose of_match_table contains a matching compatible
string.
sequenceDiagram
participant BL as Bootloader
participant K as Kernel
participant OF as OF Subsystem (Device Tree Parser)
participant BUS as Platform Bus
participant DRV as Platform Driver
BL->>K: Pass DTB address at boot
K->>OF: Parse DTB into device nodes
OF->>OF: Build device tree in memory
OF->>BUS: Register platform devices<br/>from device tree nodes
BUS->>BUS: For each device node
BUS->>DRV: Match compatible string<br/>against driver of_match_table
DRV->>DRV: probe() function called
Note over DRV: Driver initializes hardware<br/>using DT properties<br/>(reg, interrupts, clocks, etc.)
For example, the virtio MMIO transport driver matches the "virtio,mmio"
compatible string. When the device tree contains a virtio_mmio node, the
kernel automatically loads and probes the virtio MMIO driver, which then
discovers individual virtio devices (network, block, GPU, etc.) through the
virtio device negotiation protocol.
5.4.8 Device Tree Properties Reference¶
Common device tree properties used in Android device trees:
| Property | Type | Example | Purpose |
|---|---|---|---|
compatible |
string list | "arm,armv8" |
Driver matching |
reg |
address, size pairs | <0x0 0xa003c00 0x0 0x200> |
MMIO registers |
interrupts |
interrupt specifiers | <0 43 4> |
IRQ configuration |
clocks |
phandle + clock-id | <&cru CLK_UART0> |
Clock sources |
clock-names |
string list | "uartclk", "apb_pclk" |
Named clock refs |
status |
string | "okay" or "disabled" |
Enable/disable node |
#address-cells |
u32 | <2> |
Address width in child nodes |
#size-cells |
u32 | <2> |
Size width in child nodes |
pinctrl-0 |
phandle list | <&uart0_pins> |
Pin configuration |
dma-ranges |
ranges | <0x0 0x0 ...> |
DMA address translation |
5.4.9 DTBO Partition Format¶
The DTBO partition uses a specific binary format defined by Android:
graph TB
subgraph "DTBO Partition Layout"
HDR["Header<br/>magic: 0xd7b7ab1e<br/>total_size, header_size<br/>dt_entry_count"]
E1["Entry 1<br/>dt_size, dt_offset<br/>id, rev, custom[4]"]
E2["Entry 2<br/>dt_size, dt_offset<br/>id, rev, custom[4]"]
EN["Entry N<br/>..."]
D1["DTBO Blob 1"]
D2["DTBO Blob 2"]
DN["DTBO Blob N"]
end
HDR --> E1
E1 --> E2
E2 --> EN
E1 -.->|"dt_offset"| D1
E2 -.->|"dt_offset"| D2
EN -.->|"dt_offset"| DN
The bootloader selects which overlay(s) to apply based on hardware identifiers (board ID, revision, etc.) stored in the entry metadata. This allows a single DTBO partition to contain overlays for multiple hardware variants.
5.4.10 Testing Device Tree Changes¶
Android provides several ways to validate device tree changes:
-
dtc (Device Tree Compiler): Compile and decompile DTS files for syntax validation:
-
fdtdump: Dump a DTB in human-readable format:
-
/proc/device-tree on running device: The kernel exposes the parsed device tree as a filesystem hierarchy:
-
VTS tests: The Vendor Test Suite includes tests that verify device tree properties match the framework compatibility matrix.
5.5 Kernel Configuration¶
5.5.1 Configuration Architecture¶
Android's kernel configuration management is a layered system built on top of
Linux's standard Kconfig infrastructure. Rather than maintaining a single
monolithic defconfig file, Android uses a set of configuration fragments
that are combined to produce the final kernel configuration.
graph TB
subgraph "Configuration Layers"
ARCH_DEF["Architecture defconfig<br/>arch/arm64/configs/gki_defconfig"]
BASE["android-base.config<br/>(mandatory requirements)"]
COND["android-base-conditional.xml<br/>(architecture-specific requirements)"]
REC["android-recommended.config<br/>(optional enhancements)"]
NONDEBUG["non_debuggable.config<br/>(production builds only)"]
VENDOR["Vendor fragments<br/>(SoC-specific configs)"]
BOARD["Board fragments<br/>(device-specific configs)"]
end
subgraph "Merge Process"
MERGE["scripts/kconfig/merge_config.sh"]
DOTCONFIG[".config<br/>(final kernel config)"]
end
ARCH_DEF --> MERGE
BASE --> MERGE
COND --> MERGE
REC --> MERGE
NONDEBUG --> MERGE
VENDOR --> MERGE
BOARD --> MERGE
MERGE --> DOTCONFIG
style BASE fill:#ffcdd2
style COND fill:#ffcdd2
style REC fill:#fff9c4
style VENDOR fill:#e1f5fe
style BOARD fill:#e1f5fe
5.5.2 The kernel/configs Repository¶
The kernel configuration fragments are stored in kernel/configs/ with the
following structure:
kernel/configs/
README.md # Comprehensive documentation
kernel-lifetimes.xml # Branch lifecycle and EOL dates
approved-ogki-builds.xml # Approved OEM GKI builds
Android.bp # Build rules
build/
Android.bp
kernel_config.go # Soong config processing
tools/
Android.bp
bump.py # Version bump utility
check_fragments.sh # Fragment validation
kconfig_xml_fixup.py # XML fixup utility
xsd/
approvedBuild/ # XML schema definitions
b/ # Android 16 (Baklava) release
android-6.12/
Android.bp
android-base.config
android-base-conditional.xml
android-tv-base.config
android-tv-base-conditional.xml
c/ # Android 16 QPR (Custard)
android-6.12/
v/ # Android 15 (Vanilla Ice Cream)
android-6.1/
android-6.6/
u/ # Android 14 (Upside Down Cake)
android-5.15/
android-6.1/
t/ # Android 13 (Tiramisu)
android-5.10/
android-5.15/
s/ # Android 12 (Snow Cone)
android-4.19/
android-5.4/
android-5.10/
r/ # Android 11 (Red Velvet)
android-5.4/
Source: kernel/configs/
The directory naming convention uses the first letter of the Android dessert
codename: b for Baklava (Android 16), v for Vanilla Ice Cream (Android 15),
u for Upside Down Cake (Android 14), and so on.
5.5.3 Base Configuration Fragment¶
The android-base.config file contains all kernel configuration options that
are mandatory for Android to function. These are tested as part of VTS
(Vendor Test Suite) and verified during boot through the VINTF (Vendor Interface)
compatibility matrix.
Examining the Android 16 / kernel 6.12 base config
(kernel/configs/b/android-6.12/android-base.config), we find 262 lines
organized into:
Explicitly disabled options (lines 1-15):
# CONFIG_ANDROID_LOW_MEMORY_KILLER is not set
# CONFIG_ANDROID_PARANOID_NETWORK is not set
# CONFIG_BPFILTER is not set
# CONFIG_DEVMEM is not set
# CONFIG_FHANDLE is not set
# CONFIG_FW_CACHE is not set
# CONFIG_IP6_NF_NAT is not set
# CONFIG_MODULE_FORCE_UNLOAD is not set
# CONFIG_NFSD is not set
# CONFIG_NFS_FS is not set
# CONFIG_PM_AUTOSLEEP is not set
# CONFIG_RT_GROUP_SCHED is not set
# CONFIG_SYSVIPC is not set
# CONFIG_USELIB is not set
Notable disablements:
CONFIG_DEVMEM-- disables/dev/memfor security (prevents raw physical memory access)CONFIG_MODULE_FORCE_UNLOAD-- prevents force-unloading modules (stability)CONFIG_SYSVIPC-- SysV IPC is not used on Android (Binder replaces it)CONFIG_USELIB-- legacy syscall disabled for security
Core Android requirements (lines 16-261):
- IPC:
CONFIG_ANDROID_BINDER_IPC,CONFIG_ANDROID_BINDERFS - Memory:
CONFIG_ASHMEM,CONFIG_SHMEM - Filesystems:
CONFIG_FUSE_FS,CONFIG_FS_ENCRYPTION,CONFIG_FS_VERITY - Security:
CONFIG_SECURITY_SELINUX,CONFIG_SECCOMP,CONFIG_SECCOMP_FILTER,CONFIG_STACKPROTECTOR_STRONG - Power:
CONFIG_PM_WAKELOCKS,CONFIG_SUSPEND - Networking: extensive netfilter/iptables configuration (80+ options)
- Monitoring:
CONFIG_PSI,CONFIG_UID_SYS_STATS,CONFIG_TRACE_GPU_MEM - Build toolchain:
CONFIG_CC_IS_CLANG,CONFIG_AS_IS_LLVM,CONFIG_LD_IS_LLD
5.5.4 Conditional Configuration¶
The android-base-conditional.xml file expresses requirements that depend on
the target architecture or other kernel configuration values. For Android 16 /
kernel 6.12:
Minimum LTS version:
Architecture-specific requirements:
For ARM64:
<group>
<conditions>
<config>
<key>CONFIG_ARM64</key>
<value type="bool">y</value>
</config>
</conditions>
<config><key>CONFIG_ARM64_PAN</key><value type="bool">y</value></config>
<config><key>CONFIG_CFI_CLANG</key><value type="bool">y</value></config>
<config><key>CONFIG_SHADOW_CALL_STACK</key><value type="bool">y</value></config>
<config><key>CONFIG_RANDOMIZE_BASE</key><value type="bool">y</value></config>
<config><key>CONFIG_KFENCE</key><value type="bool">y</value></config>
<config><key>CONFIG_USERFAULTFD</key><value type="bool">y</value></config>
</group>
Source: kernel/configs/b/android-6.12/android-base-conditional.xml,
lines 27-90
These ARM64-specific requirements include important security features:
- CFI_CLANG -- Control Flow Integrity, prevents control-flow hijacking attacks
- SHADOW_CALL_STACK -- uses a separate stack for return addresses, preventing ROP attacks
- ARM64_PAN -- Privileged Access Never, prevents kernel from accidentally accessing user memory
- RANDOMIZE_BASE -- KASLR, randomizes kernel address space layout
- KFENCE -- Kernel Electric Fence, low-overhead memory error detector
For x86:
<group>
<conditions>
<config>
<key>CONFIG_X86</key>
<value type="bool">y</value>
</config>
</conditions>
<config><key>CONFIG_MITIGATION_PAGE_TABLE_ISOLATION</key><value type="bool">y</value></config>
<config><key>CONFIG_MITIGATION_RETPOLINE</key><value type="bool">y</value></config>
<config><key>CONFIG_RANDOMIZE_BASE</key><value type="bool">y</value></config>
</group>
x86-specific security requirements include mitigations for Meltdown (PTI) and Spectre (Retpoline).
5.5.5 Configuration Differences Across Kernel Versions¶
Comparing the Android 15 (v) config for kernel 6.6 with the Android 16 (b) config for kernel 6.12 reveals the evolution of Android's kernel requirements:
| Config Option | 6.6 (Android 15) | 6.12 (Android 16) | Notes |
|---|---|---|---|
CONFIG_BPF_JIT_ALWAYS_ON |
absent | y |
Mandatory JIT compilation for security |
CONFIG_SCHED_DEBUG |
y |
absent | Removed from mandatory list |
CONFIG_HID_WACOM |
y |
absent | Moved to optional |
CONFIG_IP_NF_MATCH_RPFILTER |
absent | y |
Added reverse-path filter |
5.5.6 kernel-lifetimes.xml¶
The kernel-lifetimes.xml file tracks the support lifecycle for every Android
kernel branch. It serves as the authoritative source for:
- Branch names and versions -- mapping between Android releases and kernel versions
- Launch and EOL dates -- when each branch was first available and when support ends
- LTS release tracking -- individual GKI releases with their own launch and EOL dates
<branch name="android15-6.6"
min_android_release="15"
version="6.6"
launch="2023-10-29"
eol="2028-07-01">
<lts-versions>
<release version="6.6.30" launch="2024-07-12" eol="2025-11-01"/>
<release version="6.6.46" launch="2024-09-16" eol="2025-11-01"/>
<!-- ... more releases ... -->
<release version="6.6.98" launch="2025-08-11" eol="2026-12-01"/>
</lts-versions>
</branch>
Source: kernel/configs/kernel-lifetimes.xml, lines 128-144
This data is consumed by VTS tests, the framework compatibility matrix checker, and the build system to enforce that devices ship with supported kernel versions.
5.5.7 approved-ogki-builds.xml¶
The approved-ogki-builds.xml file lists specific GKI builds that are approved
for use by OEMs (Original Equipment Manufacturers). "OGKI" stands for OEM GKI --
it refers to GKI builds that OEMs are explicitly permitted to ship on their
devices.
Each entry contains:
- A SHA-256 hash (
id) that uniquely identifies the build - A bug number (
bug) that links to the approval tracking
<ogki-approved version="1">
<branch name="android14-6.1">
<build id="ac5884e09bd22ecd..." bug="352795077"/>
</branch>
<branch name="android15-6.6">
<build id="9541494216af24d2..." bug="359105495"/>
<!-- ... 80+ approved builds ... -->
</branch>
<branch name="android16-6.12">
<build id="38a0ecd98b0b73ee..." bug="435129220"/>
<!-- ... 10+ approved builds ... -->
</branch>
</ogki-approved>
Source: kernel/configs/approved-ogki-builds.xml
This approval process ensures that only tested, validated kernel builds are used on production devices.
5.5.8 TV-Specific Configuration¶
Android TV devices have additional kernel configuration requirements. For Android 16 / kernel 6.12, there are dedicated TV config fragments:
Source: kernel/configs/b/android-6.12/android-tv-base.config
The TV base config is largely identical to the standard base config, reflecting Android TV's convergence with the mainline Android platform. The primary differences relate to media codec support and CEC (Consumer Electronics Control) for HDMI devices.
5.5.9 Configuration Validation¶
Android validates kernel configurations at multiple stages:
-
Build time: The build system checks that the kernel configuration matches the VINTF compatibility matrix.
-
VTS (Vendor Test Suite): The
VtsKernelConfigtest verifies that the running kernel's configuration includes all required options for the device's launch level. -
Boot time: The VINTF framework compares the running kernel's configuration against the framework compatibility matrix and logs warnings or blocks boot if incompatible.
The build rules are generated from the config fragments through
kernel/configs/build/kernel_config.go, which processes the .config and
.xml files into compatibility matrix format.
Source: kernel/configs/build/kernel_config.go
The kernel/configs/tools/check_fragments.sh script can be used to validate
that config fragments are properly formatted and non-conflicting:
Source: kernel/configs/tools/check_fragments.sh
5.6 Kernel Build Integration¶
5.6.1 Two Paths: Prebuilt vs Source¶
The AOSP build system supports two approaches for including the kernel:
graph TB
subgraph "Path 1: Prebuilt Kernels (Default)"
PB["Prebuilt kernel binary<br/>(kernel/prebuilts/6.x/arch/)"]
PM["Prebuilt modules<br/>(kernel/prebuilts/common-modules/)"]
QK["QEMU kernel prebuilts<br/>(prebuilts/qemu-kernel/)"]
BI["Build system copies<br/>to output images"]
end
subgraph "Path 2: Build from Source"
KS["Android Common Kernel<br/>source tree"]
KLEAF["Kleaf (Bazel-based)<br/>kernel build system"]
KB["Built kernel + modules"]
INT["Integrated into<br/>AOSP build"]
end
PB --> BI
PM --> BI
QK --> BI
KS --> KLEAF
KLEAF --> KB
KB --> INT
style PB fill:#c8e6c9
style KLEAF fill:#e1f5fe
Prebuilt Kernels (Default for Emulator)¶
For the emulator and reference builds, AOSP uses prebuilt kernels stored in:
- GKI prebuilts:
kernel/prebuilts/{6.1,6.6,6.12}/{arm64,x86_64}/ - Virtual device modules:
kernel/prebuilts/common-modules/virtual-device/{6.1,6.6,6.12}/ - QEMU-specific prebuilts:
prebuilts/qemu-kernel/{arm64,x86_64}/
The emulator board config selects the kernel version:
TARGET_KERNEL_USE ?= 6.12
KERNEL_ARTIFACTS_PATH := prebuilts/qemu-kernel/arm64/$(TARGET_KERNEL_USE)
EMULATOR_KERNEL_FILE := $(KERNEL_ARTIFACTS_PATH)/kernel-$(TARGET_KERNEL_USE)-gz
Source: device/generic/goldfish/board/kernel/arm64.mk, lines 20-21, 65
Note the ?= assignment: TARGET_KERNEL_USE defaults to 6.12 but can be
overridden on the command line to test with different kernel versions:
Building from Source with Kleaf¶
For vendor-specific kernels, the preferred build system is Kleaf -- a Bazel-based kernel build system. Kleaf was covered in detail in Chapter 2 (Build System), but its key integration points with the AOSP build are:
- Kleaf builds the kernel independently from the platform build
- The output (kernel image + modules) is placed in a staging directory
- The AOSP platform build picks up the kernel artifacts during image generation
5.6.2 Emulator Kernel Update Process¶
The prebuilts/qemu-kernel/update_emu_kernels.sh script documents the process
for updating the emulator's prebuilt kernels:
Source: prebuilts/qemu-kernel/update_emu_kernels.sh
The script:
- Takes a build ID (
--bid) from an internal CI build - Fetches the kernel binary and GKI modules for each architecture
- Fetches the goldfish-specific virtual device modules
- Places them in the
prebuilts/qemu-kernel/tree - Records the bug number for tracking
5.6.3 Module Organization¶
GKI modules are organized into several categories, each delivered through a different partition:
graph TB
subgraph "Module Categories"
direction TB
subgraph "Ramdisk Modules (Boot-Critical)"
RM1["virtio_dma_buf.ko"]
RM2["virtio_mmio.ko"]
RM3["virtio-rng.ko"]
end
subgraph "System Ramdisk Modules (Stage 2)"
SM1["virtio_blk.ko"]
SM2["virtio_console.ko"]
SM3["virtio_pci.ko"]
SM4["vmw_vsock_virtio_transport.ko"]
end
subgraph "System DLKM (system_dlkm partition)"
SYS["~96 GKI kernel modules<br/>Networking, USB, Bluetooth,<br/>filesystems, crypto"]
end
subgraph "Vendor Modules (vendor partition)"
VEN["Device-specific modules<br/>goldfish_pipe.ko,<br/>goldfish_battery.ko, etc."]
end
end
subgraph "Load Order"
BOOT["Boot (initramfs)"] --> STAGE2["Second stage init"]
STAGE2 --> SYSTEM["System modules loaded"]
SYSTEM --> VENDOR_LOAD["Vendor modules loaded"]
end
style RM1 fill:#ffcdd2
style RM2 fill:#ffcdd2
style RM3 fill:#ffcdd2
style SM1 fill:#fff9c4
style SYS fill:#c8e6c9
style VEN fill:#e1f5fe
The emulator's arm64 board config defines this categorization explicitly:
# Boot-critical modules loaded from vendor ramdisk
RAMDISK_KERNEL_MODULES := \
virtio_dma_buf.ko \
virtio_mmio.ko \
virtio-rng.ko \
# System modules loaded during second stage
RAMDISK_SYSTEM_KERNEL_MODULES := \
virtio_blk.ko \
virtio_console.ko \
virtio_pci.ko \
virtio_pci_legacy_dev.ko \
virtio_pci_modern_dev.ko \
vmw_vsock_virtio_transport.ko \
# All GKI modules go to system_dlkm
BOARD_SYSTEM_KERNEL_MODULES := \
$(wildcard $(KERNEL_MODULES_ARTIFACTS_PATH)/*.ko)
# Vendor modules (minus ramdisk ones)
BOARD_VENDOR_KERNEL_MODULES := \
$(filter-out $(BOARD_VENDOR_RAMDISK_KERNEL_MODULES) \
$(EMULATOR_EXCLUDE_KERNEL_MODULES), \
$(wildcard $(VIRTUAL_DEVICE_KERNEL_MODULES_PATH)/*.ko))
Source: device/generic/goldfish/board/kernel/arm64.mk, lines 27-58
5.6.4 Module Blocklisting¶
Some modules are included in the prebuilt set but should not be loaded at runtime. The emulator maintains a blocklist:
blocklist vkms.ko
# When enabled, hijacks the first audio device that's expected to be backed by
# virtio-snd. See also: aosp/3391025
blocklist snd-aloop.ko
Source: device/generic/goldfish/board/kernel/kernel_modules.blocklist
vkms.ko(Virtual Kernel Mode Setting) is blocklisted because the emulator usesvirtio-gpu.koinstead for display.snd-aloop.ko(ALSA loopback) is blocklisted because it conflicts withvirtio_snd.ko, which provides the actual audio device.
5.6.5 Boot Image Generation¶
The boot image (boot.img) contains the kernel image, ramdisk, and boot
parameters. The emulator's board config specifies:
BOARD_BOOT_HEADER_VERSION := 4
BOARD_MKBOOTIMG_ARGS += --header_version $(BOARD_BOOT_HEADER_VERSION)
BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE := 0x06000000
BOARD_RAMDISK_USE_LZ4 := true
Source: device/generic/goldfish/board/BoardConfigCommon.mk, lines 76-79
Boot image version 4 is the latest format, supporting:
- Separate vendor boot image (
vendor_boot.img) - Generic ramdisk in
boot.img - Vendor ramdisk in
vendor_boot.img - Boot configuration in a separate
bootconfigsection
The kernel command line is passed via a file rather than the boot image header:
# BOARD_KERNEL_CMDLINE is not supported (b/361341981), use the file below
PRODUCT_COPY_FILES += \
device/generic/goldfish/board/kernel/arm64_cmdline.txt:kernel_cmdline.txt
Source: device/generic/goldfish/board/kernel/arm64.mk, lines 67-69
The arm64 kernel command line is minimal:
Source: device/generic/goldfish/board/kernel/arm64_cmdline.txt
The x86_64 command line adds a clocksource specification:
Source: device/generic/goldfish/board/kernel/x86_64_cmdline.txt
5.6.6 16K Page Size Support¶
Modern Android (16+) supports 16K page size kernels, which provide better TLB (Translation Lookaside Buffer) utilization and improved performance for memory-intensive workloads. The emulator includes dedicated 16K page size configurations:
device/generic/goldfish/board/kernel/
arm64.mk # Standard 4K page size
arm64_16k.mk # 16K page size variant
arm64_16k_cmdline.txt
x86_64.mk
x86_64_16k.mk
x86_64_16k_cmdline.txt
Source: device/generic/goldfish/board/kernel/
The 16K page size variant uses a separate set of prebuilt kernels:
TARGET_KERNEL_USE := 6.12
KERNEL_ARTIFACTS_PATH := prebuilts/qemu-kernel/arm64_16k/$(TARGET_KERNEL_USE)
Source: device/generic/goldfish/board/kernel/arm64_16k.mk, lines 20-21
The kernel version string for 4K page size builds includes a -4k suffix, while
16K builds would have a -16k suffix.
5.6.7 The GSI (Generic System Image) and Kernel¶
The Generic System Image is Google's reference AOSP build that should work on any GKI-compliant device. The GSI board configuration reveals how the kernel is handled in this context:
Source: build/make/target/board/BoardConfigGsiCommon.mk
The TARGET_NO_KERNEL := true setting means the GSI does not include a kernel.
This is intentional: the GSI's system image is designed to be paired with the
device's existing kernel (which lives in the boot partition). This clean
separation is what makes it possible to run a GSI on any GKI-compliant device
without replacing its kernel.
The GSI also enables system_dlkm for module compatibility:
BOARD_USES_SYSTEM_DLKMIMAGE := true
BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE := ext4
TARGET_COPY_OUT_SYSTEM_DLKM := system_dlkm
Source: build/make/target/board/BoardConfigGsiCommon.mk
5.6.8 Kernel Versioning in the Build System¶
The build system needs to know the kernel version for compatibility checking. For
prebuilt kernels, this is provided by kernel_version.mk:
Source: kernel/prebuilts/6.6/arm64/kernel_version.mk
The version string components are used by:
- VINTF compatibility matrix: Ensures framework and kernel are compatible
- VTS tests: Validates that the kernel meets requirements for the declared Android version
- OTA system: Ensures kernel updates maintain compatibility
5.6.9 Super Partition and Dynamic Partitions¶
The emulator uses Android's dynamic partitioning system with a super partition:
BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT := true
BOARD_SUPER_PARTITION_SIZE ?= 8598323200 # 8G + 8M
BOARD_SUPER_PARTITION_GROUPS := emulator_dynamic_partitions
BOARD_EMULATOR_DYNAMIC_PARTITIONS_PARTITION_LIST := \
system \
system_dlkm \
system_ext \
product \
vendor
Source: device/generic/goldfish/board/BoardConfigCommon.mk, lines 44-55
The system_dlkm partition is specifically for GKI kernel modules:
BOARD_USES_SYSTEM_DLKMIMAGE := true
BOARD_SYSTEM_DLKMIMAGE_FILE_SYSTEM_TYPE := erofs # we never write here
TARGET_COPY_OUT_SYSTEM_DLKM := system_dlkm
Source: device/generic/goldfish/board/BoardConfigCommon.mk, lines 67-69
The comment "we never write here" confirms that system_dlkm is a read-only
partition -- kernel modules are loaded from it but never modified at runtime.
The use of erofs (Enhanced Read-Only File System) further enforces this
immutability.
5.7 Kernel Debugging¶
5.7.1 Kernel Tracing Infrastructure¶
The Linux kernel provides several powerful tracing mechanisms that Android integrates with its tooling:
graph TB
subgraph "Kernel Tracing Mechanisms"
FT["ftrace<br/>/sys/kernel/tracing/"]
TP["Tracepoints<br/>(static instrumentation)"]
KP["kprobes<br/>(dynamic instrumentation)"]
EBPF["eBPF Programs<br/>(programmable tracing)"]
end
subgraph "Android Tracing Tools"
ATRACE["atrace<br/>(Android trace tool)"]
PERFETTO["Perfetto<br/>(system-wide tracing)"]
SYSTRACE["Systrace<br/>(legacy, uses atrace)"]
SIMPLEPERF["simpleperf<br/>(CPU profiling)"]
end
subgraph "Output"
UI["Perfetto UI<br/>(ui.perfetto.dev)"]
REPORT["Trace reports"]
FLAME["Flame graphs"]
end
FT --> ATRACE
FT --> PERFETTO
TP --> PERFETTO
KP --> PERFETTO
EBPF --> PERFETTO
ATRACE --> PERFETTO
PERFETTO --> UI
SIMPLEPERF --> FLAME
PERFETTO --> REPORT
style PERFETTO fill:#c8e6c9
style FT fill:#e1f5fe
5.7.2 ftrace¶
ftrace is the kernel's built-in tracing framework. Android requires profiling support in the base config:
Source: kernel/configs/b/android-6.12/android-base.config, line 214
ftrace provides:
- Function tracing: Trace every kernel function call (or specific functions)
- Function graph tracing: Trace function entry and exit with timing
- Event tracing: Record specific kernel events (scheduling, I/O, memory allocation, etc.)
- Trace markers: Userspace can write to
/sys/kernel/tracing/trace_markerto inject events into the kernel trace
Key ftrace virtual files:
/sys/kernel/tracing/
available_tracers # List of available tracers
current_tracer # Currently active tracer
trace # Human-readable trace output
trace_pipe # Streaming trace output
tracing_on # Enable/disable tracing
buffer_size_kb # Per-CPU buffer size
events/ # Available tracepoints
sched/ # Scheduler events
sched_switch/
sched_wakeup/
binder/ # Binder IPC events
binder_transaction/
binder_lock/
block/ # Block I/O events
ext4/ # ext4 filesystem events
f2fs/ # f2fs filesystem events
Using ftrace on Android¶
To enable function tracing on a running device:
# Enable tracing
adb shell "echo 1 > /sys/kernel/tracing/tracing_on"
# Set the tracer
adb shell "echo function_graph > /sys/kernel/tracing/current_tracer"
# Filter to specific functions (e.g., binder)
adb shell "echo 'binder_*' > /sys/kernel/tracing/set_ftrace_filter"
# Read the trace
adb shell cat /sys/kernel/tracing/trace
# Disable
adb shell "echo 0 > /sys/kernel/tracing/tracing_on"
5.7.3 Tracepoints¶
Tracepoints are static instrumentation points compiled into the kernel. They provide structured event data at specific locations in the kernel code. Android uses tracepoints extensively for:
- Scheduler tracing:
sched_switch,sched_wakeup,sched_process_exit - Binder tracing:
binder_transaction,binder_return,binder_lock - Memory tracing:
mm_page_alloc,mm_page_free,oom_score_adj_update - GPU memory tracing:
gpu_mem_total(required byCONFIG_TRACE_GPU_MEM=y) - Power management:
cpu_frequency,cpu_idle,suspend_resume
The CONFIG_TRACE_GPU_MEM=y requirement in the Android base config enables
GPU memory tracking tracepoints:
Source: kernel/configs/b/android-6.12/android-base.config, line 243
5.7.4 Integration with Perfetto¶
Perfetto is Android's system-wide tracing infrastructure (detailed in a later
chapter). Its kernel integration works through the traced_probes daemon, which
reads ftrace events from the kernel tracing ring buffers.
The Perfetto ftrace integration code is at:
external/perfetto/src/traced/probes/ftrace/
cpu_reader.cc # Reads ftrace per-CPU ring buffers
cpu_reader.h
event_info.cc # Maps ftrace event IDs to names
event_info_constants.cc # Known event definitions
compact_sched.cc # Compact encoding for scheduler events
atrace_hal_wrapper.cc # Android trace HAL integration
atrace_wrapper.cc # atrace command integration
Source: external/perfetto/src/traced/probes/ftrace/
Perfetto's ftrace integration:
- Opens
/sys/kernel/tracing/per_cpu/cpuN/trace_pipe_rawfor each CPU - Enables requested tracepoints via
/sys/kernel/tracing/events/<category>/<event>/enable - Reads binary trace data from the ring buffers
- Encodes events into Perfetto's protobuf trace format
- Writes the trace to a file or streams it to the Perfetto trace viewer
Capturing a Kernel Trace with Perfetto¶
# Record a 10-second trace with scheduler and binder events
adb shell perfetto \
-c - \
-o /data/misc/perfetto-traces/trace.perfetto-trace \
<<EOF
buffers {
size_kb: 63488
}
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "binder/binder_transaction"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
}
}
}
duration_ms: 10000
EOF
# Pull the trace
adb pull /data/misc/perfetto-traces/trace.perfetto-trace .
# Open in Perfetto UI: https://ui.perfetto.dev
5.7.5 kprobes and Dynamic Tracing¶
kprobes allow instrumenting arbitrary kernel functions at runtime without recompiling the kernel. They work by inserting a breakpoint instruction at the target address and executing a handler when it is hit.
Android's base config requires CONFIG_PROFILING=y, which enables the
infrastructure needed for kprobes. When combined with eBPF (CONFIG_BPF_SYSCALL=y,
CONFIG_BPF_JIT=y), kprobes become a powerful tool for custom kernel
instrumentation.
eBPF-Based Kernel Tracing¶
Android's extensive eBPF configuration enables kernel tracing programs:
# List loaded BPF programs
adb shell bpftool prog list
# Show BPF maps (key-value stores used by BPF programs)
adb shell bpftool map list
eBPF programs loaded at boot from /system/etc/bpf/ provide:
- Per-UID network traffic accounting
- Per-UID CPU time tracking
- Memory event monitoring
5.7.6 Kernel Crash Analysis with debuggerd¶
When a process crashes on Android, debuggerd (specifically crash_dump)
captures a tombstone -- a detailed crash report containing register state, stack
traces, memory maps, and signal information.
The crash dump mechanism is implemented at:
system/core/debuggerd/
crash_dump.cpp # Main crash handler
debuggerd.cpp # Debuggerd daemon
libdebuggerd/
tombstone.cpp # Tombstone generation
tombstone_proto.cpp # Protobuf tombstone format
backtrace.cpp # Stack unwinding
utility.cpp # Utility functions
handler/ # Signal handler installed in processes
crasher/ # Test crash program
Source: system/core/debuggerd/
How debuggerd Works¶
sequenceDiagram
participant P as Crashing Process
participant SH as Signal Handler (in-process)
participant CD as crash_dump
participant TS as Tombstone Writer
participant LOG as logcat
P->>P: SIGSEGV / SIGABRT / etc.
P->>SH: Signal delivered
SH->>SH: Clone crash_dump process
SH->>CD: fork + execve crash_dump
CD->>CD: ptrace(ATTACH) to crashed process
CD->>CD: Read registers, memory maps
CD->>CD: Unwind stack (libunwindstack)
CD->>TS: Generate tombstone
TS->>TS: Write /data/tombstones/tombstone_NN
TS->>LOG: Log crash summary to logcat
CD->>P: Resume (process will exit)
Kernel Crash Information Sources¶
debuggerd reads several kernel-provided files to construct the tombstone:
/proc/<pid>/maps-- memory map of the crashed process/proc/<pid>/status-- process status (UID, state, threads)/proc/<pid>/task/<tid>/status-- per-thread status/proc/<pid>/comm-- process command name/proc/<pid>/cmdline-- full command line/proc/version-- kernel version string
The kernel's ptrace() system call is essential for crash analysis -- it allows
crash_dump to read the crashed process's registers and memory.
5.7.7 Kernel Log Analysis¶
The kernel ring buffer (dmesg) is one of the most important debugging tools.
On Android, kernel messages are also forwarded to logcat with the kernel tag.
# Read kernel ring buffer
adb shell dmesg
# Follow kernel messages in real time
adb shell dmesg -w
# Read kernel messages from logcat
adb logcat -b kernel
# Filter for specific subsystems
adb shell dmesg | grep -i binder
adb shell dmesg | grep -i "low memory"
adb shell dmesg | grep -i "oom"
Common Kernel Messages to Watch For¶
| Message Pattern | Subsystem | Meaning |
|---|---|---|
binder: ...: ... got transaction |
Binder | Transaction processing |
lowmemorykiller: |
lmkd/OOM | Process killed for memory |
oom_reaper: |
OOM | Kernel OOM reaper active |
CPU: ... MHz |
cpufreq | CPU frequency change |
audit: |
SELinux | Policy violation |
init: |
init | Service lifecycle events |
dm_verity: |
dm-verity | Integrity verification events |
FUSE: |
FUSE | Filesystem operations |
incfs: |
Incremental FS | Incremental loading events |
pstore: Surviving Kernel Panics¶
The pstore (persistent store) subsystem saves kernel logs across reboots, which is essential for diagnosing kernel panics:
graph LR
subgraph "Before Crash"
DMESG["Kernel Ring Buffer<br/>(dmesg)"]
PSTORE_W["pstore Writer<br/>(ramoops backend)"]
RAM["Reserved Memory Region<br/>(survives reboot)"]
end
subgraph "After Reboot"
RAM2["Reserved Memory Region"]
PSTORE_R["pstore Reader"]
FILES["/sys/fs/pstore/<br/>dmesg-ramoops-0<br/>console-ramoops-0<br/>pmsg-ramoops-0"]
end
DMESG --> PSTORE_W
PSTORE_W --> RAM
RAM -.->|"survives reboot"| RAM2
RAM2 --> PSTORE_R
PSTORE_R --> FILES
On devices with pstore configured, the last kernel messages before a panic are
preserved in a reserved memory region. After the device reboots, these messages
appear as files under /sys/fs/pstore/:
# Check for pstore data after a crash
adb shell ls /sys/fs/pstore/
# Output might include:
# dmesg-ramoops-0 # Last kernel log
# console-ramoops-0 # Last console output
# pmsg-ramoops-0 # Last userspace messages
# Read the crash log
adb shell cat /sys/fs/pstore/dmesg-ramoops-0
5.7.8 Kernel Panic and Ramdump Analysis¶
When the kernel itself crashes (as opposed to a userspace process), different mechanisms apply:
-
Kernel panic logs: The last kernel messages before a panic are preserved in
pstore(persistent store), typically backed by a reserved memory region. On the next boot, these messages appear in/sys/fs/pstore/. -
Ramdumps: Some SoCs support capturing a full memory dump on kernel panic. These can be analyzed with tools like
crashorgdbusing the vmlinux symbol file. -
SysRq: The base config enables the Magic SysRq key (
CONFIG_MAGIC_SYSRQ=y), which allows triggering kernel debugging actions even when the system appears hung:
# Trigger a kernel panic (for testing ramdump capture)
adb shell "echo c > /proc/sysrq-trigger"
# Show all running tasks
adb shell "echo t > /proc/sysrq-trigger"
# Show memory information
adb shell "echo m > /proc/sysrq-trigger"
5.7.9 Debugging the Binder Subsystem¶
Binder has its own set of debug interfaces exposed through debugfs:
/sys/kernel/debug/binder/
state # Global binder state (all processes)
stats # Binder transaction statistics
transactions # Active transactions
proc/<pid> # Per-process binder state
failed_reply # Failed transaction details
Reading binder state:
# Show binder statistics
adb shell cat /sys/kernel/debug/binder/stats
# Show binder state for system_server (PID varies)
adb shell cat /sys/kernel/debug/binder/proc/$(adb shell pidof system_server)
Interpreting Binder Debug Output¶
The binder stats file provides valuable information about system IPC health:
# Example binder stats output structure
binder stats:
BC_TRANSACTION: 12345 # Total transactions sent
BC_REPLY: 12340 # Total replies sent
BR_TRANSACTION: 12345 # Total transactions received
BR_REPLY: 12340 # Total replies received
BR_DEAD_BINDER: 5 # Death notifications
proc: 42 # Number of processes using binder
threads: 8 # Average threads per process
requested_threads: 4
requested_threads_started: 4
ready_threads: 6
free_async_space: 524288
Key metrics to monitor:
- High BR_DEAD_BINDER count: Services are dying frequently; investigate OOM kills or crashes
- ready_threads near 0: Thread pool exhaustion; the process cannot handle more incoming transactions
- free_async_space near 0: Async transaction buffer full; oneway calls will be dropped
- BC_TRANSACTION >> BR_REPLY: Transactions timing out; server processes are overloaded
5.7.10 Thermal and Power Debugging¶
Android's kernel-level power management can be debugged through several interfaces:
# CPU frequency information
adb shell cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
adb shell cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies
# CPU idle state information
adb shell cat /sys/devices/system/cpu/cpu0/cpuidle/state0/name
adb shell cat /sys/devices/system/cpu/cpu0/cpuidle/state0/time
# Thermal zone information
adb shell cat /sys/class/thermal/thermal_zone0/type
adb shell cat /sys/class/thermal/thermal_zone0/temp
# Wakelock information
adb shell cat /sys/power/wake_lock
adb shell cat /d/wakeup_sources
5.7.11 Memory Debugging¶
Several kernel features assist with memory debugging:
-
KFENCE (Kernel Electric Fence): Required on ARM64 and x86 by the conditional config. KFENCE detects use-after-free and out-of-bounds access for slab objects using a pool of guard pages.
-
PSI monitoring: Beyond lmkd, PSI can be read directly:
-
meminfo and vmstat: Standard kernel memory reporting:
-
UID sys stats: Per-application I/O accounting:
5.8 Try It: Examine the Emulator Kernel¶
This section provides hands-on exercises for exploring the Android emulator's kernel. These exercises assume you have an AOSP source tree synced and an emulator image built (or the ability to use prebuilt images).
Exercise 1: Inspect the Prebuilt Kernel¶
Start by examining the kernel prebuilts in the AOSP tree:
# List the available prebuilt kernel versions
ls kernel/prebuilts/
# Output: 6.1 6.6 6.12 common-modules mainline
# Check the kernel version string
cat kernel/prebuilts/6.6/arm64/kernel_version.mk
# Output: BOARD_KERNEL_VERSION := 6.6.100-android15-8-gf988247102d3-ab14039625-4k
# Count the GKI modules
ls kernel/prebuilts/6.6/arm64/*.ko | wc -l
# Output: 96
# List some notable modules
ls kernel/prebuilts/6.6/arm64/*.ko | head -20
What to look for:
- The kernel version string encodes the LTS version, Android release, git commit, build ID, and page size
- GKI modules cover networking (bluetooth, USB, WiFi), filesystems, crypto, and device drivers
- The vmlinux file contains full debug symbols for kernel debugging
Exercise 2: Examine the KMI Symbol List¶
The KMI symbol list defines the contract between the GKI kernel and vendor modules:
# Count total KMI symbols
wc -l kernel/prebuilts/6.6/arm64/abi_symbollist
# Output: 35710
# Look at the structure
head -30 kernel/prebuilts/6.6/arm64/abi_symbollist
# Search for binder-related symbols
grep -i binder kernel/prebuilts/6.6/arm64/abi_symbollist
# Search for Android-specific symbols
grep android kernel/prebuilts/6.6/arm64/abi_symbollist
# Check the raw symbol list (just names, no sections)
head -10 kernel/prebuilts/6.6/arm64/abi_symbollist.raw
# Output: ANDROID_GKI_memcg_stat_item
# ANDROID_GKI_node_stat_item
# ANDROID_GKI_struct_dwc3
# ...
What to look for:
ANDROID_GKI_*symbols are Android-specific extensions to kernel structuresandroid_rvh_*symbols are restricted vendor hook registration functions- The symbol list is organized into
[abi_symbol_list]sections - Commonly used symbols like
module_layoutappear at the top
Exercise 3: Explore the Virtual Device Modules¶
The emulator uses a combination of GKI modules and device-specific modules:
# List goldfish-specific modules
ls prebuilts/qemu-kernel/arm64/6.12/goldfish_modules/
# List GKI modules used by the emulator
ls prebuilts/qemu-kernel/arm64/6.12/gki_modules/ | head -20
# Count total emulator modules
ls prebuilts/qemu-kernel/arm64/6.12/goldfish_modules/ | wc -l
ls prebuilts/qemu-kernel/arm64/6.12/gki_modules/ | wc -l
# Check the module blocklist
cat device/generic/goldfish/board/kernel/kernel_modules.blocklist
What to look for:
- Goldfish modules (
goldfish_*.ko) are specific to the emulator's virtual hardware - Virtio modules (
virtio_*.ko) implement paravirtualized devices (network, GPU, input, sound) - The
system_heap.komodule provides DMA-BUF allocation - Blocklisted modules (
vkms.ko,snd-aloop.ko) would conflict with virtio equivalents
Exercise 4: Read the Kernel Configuration¶
Examine the Android base configuration to understand what the kernel requires:
# Read the Android 16 base config for kernel 6.12
cat kernel/configs/b/android-6.12/android-base.config
# Count mandatory config options
grep -c "=y" kernel/configs/b/android-6.12/android-base.config
# (Count of options that must be enabled)
# Count explicitly disabled options
grep -c "is not set" kernel/configs/b/android-6.12/android-base.config
# Check architecture-specific requirements
cat kernel/configs/b/android-6.12/android-base-conditional.xml
# Compare across releases
diff kernel/configs/v/android-6.6/android-base.config \
kernel/configs/b/android-6.12/android-base.config
What to look for:
- The base config is alphabetically sorted for maintainability
- Security options (SELinux, seccomp, encryption) are mandatory
- Networking options (netfilter, iptables) are extensive because Android's firewall depends on them
- The conditional XML adds architecture-specific security features (CFI, SCS, KFENCE)
Exercise 5: Examine the Kernel Lifecycle Data¶
# View kernel branch lifecycles
cat kernel/configs/kernel-lifetimes.xml
# Check approved OGKI builds
cat kernel/configs/approved-ogki-builds.xml | head -20
# Count approved builds per branch
grep -c "<build" kernel/configs/approved-ogki-builds.xml
What to look for:
- Each branch has a defined EOL years in the future (4-6 years of support)
- LTS releases within a branch have shorter individual lifetimes (12-15 months)
- The android16-6.12 branch is the newest, with releases starting in 2025
- The approved-ogki-builds.xml file has far more android15-6.6 entries than android16-6.12, reflecting the maturity difference
Exercise 6: Extract the Running Emulator's Kernel Config¶
If you have a running emulator, you can extract the kernel's compiled-in configuration:
# Start the emulator
emulator -avd <your_avd> &
# Wait for boot
adb wait-for-device
# Extract the kernel config (CONFIG_IKCONFIG_PROC=y is required)
adb shell "zcat /proc/config.gz" > emulator_kernel_config.txt
# Check the kernel version
adb shell cat /proc/version
# List loaded modules
adb shell lsmod
# Check memory info
adb shell cat /proc/meminfo
# Check PSI status
adb shell cat /proc/pressure/memory
adb shell cat /proc/pressure/cpu
adb shell cat /proc/pressure/io
The CONFIG_IKCONFIG=y and CONFIG_IKCONFIG_PROC=y options in the base config
ensure that the kernel's configuration is always accessible at runtime through
/proc/config.gz. This is invaluable for debugging configuration-related issues.
Exercise 7: Explore Binder on the Emulator¶
# Check binder devices
adb shell ls -la /dev/binderfs/
# Check binder stats
adb shell cat /sys/kernel/debug/binder/stats 2>/dev/null || \
adb shell cat /dev/binderfs/binder-control/../stats 2>/dev/null
# Watch binder transactions in real time (root required)
adb root
adb shell "echo 1 > /sys/kernel/tracing/events/binder/binder_transaction/enable"
adb shell cat /sys/kernel/tracing/trace_pipe
# (Open an app while watching to see transactions flow)
Exercise 8: Trace Kernel Activity with atrace¶
# List available trace categories
adb shell atrace --list_categories
# Capture a 5-second trace including scheduler and binder events
adb shell atrace -t 5 sched binder -o /data/local/tmp/trace.txt
# Pull and examine
adb pull /data/local/tmp/trace.txt
Exercise 9: Examine Module Loading at Boot¶
The emulator's init script shows how modules are loaded during boot:
Key lines from init.ranchu.rc:
on early-init
start vendor.dlkm_loader
exec u:r:modprobe:s0 -- /system/bin/modprobe -a -d /system/lib/modules zram.ko
Source: device/generic/goldfish/init/init.ranchu.rc, lines 37-38
This shows:
- The
vendor.dlkm_loaderservice loads vendor modules fromvendor_dlkm zram.kois loaded fromsystem_dlkmvia modprobe during early init- The modprobe command runs in the
modprobeSELinux domain (u:r:modprobe:s0)
The init script also configures zram for swap:
Source: device/generic/goldfish/init/init.ranchu.rc, lines 41-42
Exercise 10: Examine the Emulator's Filesystem Layout¶
The emulator's fstab reveals the partition layout:
Key entries:
system /system erofs ro wait,logical,first_stage_mount
system /system ext4 ro wait,logical,first_stage_mount
vendor /vendor erofs ro wait,logical,first_stage_mount
system_dlkm /system_dlkm erofs ro wait,logical,first_stage_mount
/dev/block/vdc /data ext4 ... wait,check,quota,fileencryption=...
/dev/block/zram0 none swap defaults zramsize=75%
Source: device/generic/goldfish/board/fstab/arm
Notable observations:
- System, vendor, and system_dlkm are mounted read-only (either erofs or ext4)
- Data partition uses file-based encryption (
fileencryption=aes-256-xts:...) - Data partition has fs-verity enabled (
fsverity) - zram swap uses 75% of RAM as compressed swap space
- Dynamic partitions use Android's
logicalpartition system (in the super partition)
5.9 Summary¶
The Android kernel is a carefully managed extension of the Linux kernel, with additions that support Android's unique requirements for IPC (Binder), memory management (lmkd + PSI), storage (FUSE passthrough, incremental FS), security (dm-verity, file-based encryption, SELinux), and power management (wakelocks).
The GKI architecture represents a fundamental shift in how Android kernels are managed. By splitting the kernel into a Google-built core image and vendor-supplied modules with a stable interface (KMI), GKI enables:
- Independent kernel security updates
- Reduced fragmentation across the ecosystem
- Longer kernel support lifetimes (4-6 years per branch)
- Verified, approved kernel builds for production devices
The kernel configuration system ensures that all Android devices meet a minimum
set of requirements, verified at build time, test time (VTS), and boot time
(VINTF compatibility matrix). The lifecycle management system
(kernel-lifetimes.xml) provides transparency about which kernels are supported
and for how long.
For developers working with AOSP, understanding the kernel layer is essential for:
- Debugging performance issues that involve scheduler, memory, or I/O behavior
- Understanding the security model (which is enforced at the kernel level)
- Working with hardware-specific features that require kernel module or device tree changes
- Maintaining and updating kernels for devices in the field
The exercises in section 4.8 provide a starting point for hands-on kernel exploration using the Android emulator, which includes a fully functional GKI kernel with the same architecture as production devices.
Key Takeaways¶
-
The Android kernel is upstream Linux plus targeted extensions. The delta is intentionally minimized, and many former Android-only features have been upstreamed.
-
GKI is the future. Starting with Android 12 / kernel 5.10, all new devices must use the GKI architecture with a stable KMI. This enables independent kernel updates and reduces ecosystem fragmentation.
-
Configuration is managed through fragments, not monolithic defconfigs. The
kernel/configs/repository maintains per-release, per-kernel-version configuration requirements that are validated at build, test, and boot time. -
Security is enforced at every layer. From dm-verity (partition integrity) to file-based encryption (data confidentiality) to SELinux (mandatory access control) to seccomp (system call filtering) to CFI and SCS (code integrity), the kernel provides defense in depth.
-
Debugging tools are rich and well-integrated. The combination of ftrace, Perfetto, eBPF, debuggerd, and pstore provides comprehensive kernel-level observability.
-
The emulator is a fully functional GKI target. The goldfish virtual device uses the same GKI architecture as production devices, making it an excellent platform for kernel development and testing.
Cross-References to Other Chapters¶
- Chapter 2 (Build System): Kleaf, the Bazel-based kernel build system
- Chapter 3 (Boot Process): How the kernel is loaded and init processes begin
- Chapter 5 (Hardware Abstraction): HALs that depend on kernel drivers
- Chapter 10 (Security): SELinux policy, seccomp filters, verified boot chain
- Chapter 14 (Performance): Perfetto tracing, CPU scheduling, memory tuning
Key File Reference¶
| File / Directory | Purpose |
|---|---|
kernel/configs/ |
Kernel configuration fragments and lifecycle data |
kernel/configs/b/android-6.12/android-base.config |
Android 16 mandatory kernel config |
kernel/configs/b/android-6.12/android-base-conditional.xml |
Architecture-specific requirements |
kernel/configs/kernel-lifetimes.xml |
Branch support lifecycle |
kernel/configs/approved-ogki-builds.xml |
Approved OEM GKI builds |
kernel/prebuilts/6.6/arm64/ |
GKI 6.6 prebuilt kernel and modules |
kernel/prebuilts/6.6/arm64/abi_symbollist |
KMI symbol list (35,710 symbols) |
kernel/prebuilts/common-modules/virtual-device/ |
Virtual device kernel modules |
prebuilts/qemu-kernel/arm64/6.12/ |
Emulator kernel prebuilts |
device/generic/goldfish/ |
Emulator device configuration |
device/generic/goldfish/board/kernel/arm64.mk |
Emulator arm64 kernel config |
device/generic/goldfish/board/BoardConfigCommon.mk |
Emulator board configuration |
device/generic/goldfish/init/init.ranchu.rc |
Emulator init script |
system/memory/lmkd/ |
Low Memory Killer Daemon |
system/memory/lmkd/lmkd.cpp |
lmkd main logic |
system/memory/lmkd/libpsi/psi.cpp |
PSI monitor interface |
system/incremental_delivery/incfs/ |
Incremental FS userspace library |
system/core/debuggerd/ |
Crash dump handler |
frameworks/native/libs/binder/IPCThreadState.cpp |
Binder userspace IPC |
external/perfetto/src/traced/probes/ftrace/ |
Perfetto kernel trace integration |
build/make/target/board/BoardConfigGsiCommon.mk |
GSI board configuration |