Zygote
The Linux kernelnerd

The kernel features Android leans on

Seven kernel facilities Android could not work without: Binder, memfd, cgroups and PSI, SELinux, seccomp, DMA-BUF heaps and eBPF — plus the filesystems that make read-only system images cheap.

17 minAPI 21–37 · 17interactivequizverified 2026-10-27

A kernel is a large piece of software, and Android uses a small, specific slice of it. That slice is worth knowing by name, because almost every Android behaviour you can observe — why a process cannot read a file, why a camera buffer is shared without a copy, why an app can start before it has finished downloading — resolves to one of about seven facilities.

This page is the inventory. For each one: what problem it solves, what the interface looks like, and what happens without it.

TL;DR#

  • Binder is the IPC driver. It passes file descriptors, identifies the calling UID, and supports one-way calls. Everything between apps and system_server goes through it.
  • memfd replaced ashmem for shared memory. An anonymous file descriptor that can be sealed, mapped and passed over a socket.
  • cgroup v2 and PSI are how the kernel reports pressure and how Android groups processes. lmkd is built on both.
  • SELinux turns the UID model into mandatory access control: even root is confined to a domain. restricts which system calls a process may make at all.
  • DMA-BUF heaps allocate buffers that can be shared between processes and devices without copying — the basis of the graphics and camera pipelines.
  • eBPF lets Android run small verified programs inside the kernel for network accounting and monitoring. EROFS and IncFS are the read-only and incremental filesystems that make modern system images work.

Mental model#

Think of the kernel as a workshop with a fixed set of machines.

There is a conveyor belt (Binder) that moves work orders between benches and can attach small objects to them. There is a shared whiteboard (memfd) that several benches can look at, with a rule that lets you freeze it so nobody writes again. There is a foreman (lmkd) who walks around reading the pressure gauges (PSI) and decides which bench to shut down when the room gets crowded. There is a guard at the door (SELinux and seccomp) who checks not just who you are but what you are allowed to touch. There is a loading dock (DMA-BUF) where a delivery is placed once and read by several benches. And there is a small scripting engine bolted to the wall (eBPF) that can measure what the machines are doing without stopping them.

Each machine exists because the obvious alternative — copying data, trusting the caller, stopping the machine to inspect it — was too slow or too unsafe.

Where the analogy breaks#

The workshop has one guard. A phone has two independent ones. SELinux checks access to objects: may this domain open this file, connect to this socket, use this device node. seccomp checks whether the operation exists at all: may this process call mount or ptrace, ever. They are layered, not alternatives, and a process can be blocked by either.

The analogy also understates how much of this is policy shipped as data. SELinux rules are compiled into a binary policy file, and the interesting questions ("what can the camera daemon read?") are answered by that file rather than by the code.

The big picture#

Loading diagram…
Text description of this diagram

A treemap of Android's partitions grouped by role. Boot-critical partitions are boot, init_boot, vbmeta and dtbo — everything the bootloader needs before Android code runs. The system group contains system, system_ext, product and the super container that holds logical partitions on devices with dynamic partitions. The vendor group contains vendor, odm, vendor_dlkm and system_dlkm, the last two being where kernel modules live. Data and metadata contains userdata, metadata and cache. The treemap shows how many partitions are in each group, not their byte sizes, which vary enormously between devices.

Two of those partitions matter directly to this page. system_dlkm and vendor_dlkm hold .ko files — the modules that make GKI work — and they are mounted read-only and verified. super is where the dynamic partitions live, created by first-stage init with device-mapper before anything mounts them. Both are examples of a pattern this whole module is about: the kernel's facilities are only useful if the partition layout can deliver the code to use them.

Deep dive#

Binder: IPC that knows who you are#

Most IPC mechanisms give you a byte stream or a message queue. Binder gives you something different: a transaction that carries a target object, a parcel of data, and optionally file descriptors, with the kernel recording the sender's identity.

That identity is the crucial part. When a client calls into a service, the kernel attaches the caller's and PID to the transaction. A system service can then check "is this really the app that owns this package?" without trusting anything the caller said. There is no way to forge it, because the caller never writes it.

Binder also supports one-way transactions, which return immediately and are queued, and reference passing, which lets a service hand a client a handle to another Binder object. Both are used heavily: every registerCallback in the framework gives the service a handle it can call back through.

The device node is traditionally /dev/binder. On newer systems, binderfs can be mounted to create private sets of Binder devices — one binder-control node per mount, from which new devices are requested with an ioctl. That is how containers and virtualised Android instances get their own Binder namespace instead of sharing the host's.1

memfd: the shared whiteboard#

Shared memory is how Android avoids copying large buffers between processes. The original mechanism was ashmem, an Android-specific driver. It was replaced by memfd_create, a standard system call that returns a file descriptor backed by anonymous memory.

The descriptor is the point. Because it is a file descriptor, it can be:

  • mapped with mmap in the creating process,
  • passed over a Unix socket to another process, which maps the same pages,
  • sealed so that no further writes are possible, which is how a producer hands a buffer to a consumer immutably,
  • resized with ftruncate before it is shared.

memfd_create has been in Linux since 3.17. Android's move to it was gradual, because ashmem had a couple of behaviours memfd lacked — notably the ability to make a region read-only for future writes while keeping existing writable mappings alive. Memfd grew seals to cover that, and the ashmem driver was eventually deleted from the kernel.2

The practical consequence for you: a shared-memory bug in an app is now usually a file-descriptor bug. If a descriptor leaks, the memory leaks; if it is passed to the wrong process, that process can read the buffer.

cgroup v2 and PSI: pressure, measured#

Android groups processes into cgroups, and cgroup v2 is a single hierarchy with per-controller files.3 The four controllers that matter:

ControllerWhat it controlsWhere Android uses it
memoryLimits, protection, accountingPer-app memory accounting, lmkd's view of the process tree
cpuCPU bandwidth and weightingBackground vs foreground scheduling weight
ioI/O throttling and weightingKeeping background work from starving the UI
pidsMaximum task countPreventing fork bombs from one app

PSI adds something cgroups do not: a time-based measure of contention. Instead of "this group is using 800 MB", PSI says "tasks in this group spent 12% of the last minute waiting for memory". That is a far better signal for a killer, because it measures the symptom the user feels rather than a proxy for it.4

combines the two. It watches PSI thresholds, maps them to an urgency level, and kills the least important eligible process — where importance comes from the framework's score. Its thresholds are tunable through ro.lmk.* properties, which is why you will see names like ro.lmk.psi_partial_stall_ms in getprop output on a device.5

SELinux: the guard that checks the object#

The UID model says a process can access what its user owns. That is too coarse for Android, where every app has its own UID but the system must still stop a compromised app from reading another app's files even if a bug gave it the wrong UID.

adds a second check. Every process runs in a domain; every file, socket and device has a type. The policy says which domain may perform which operation on which type. The kernel checks on every access, and a denial is logged as an AVC message.6

Three consequences are worth internalising:

Root is not a bypass. A process running as UID 0 is still confined to its domain. That is why adb root on a production device does not give you the access you expect: the shell domain's policy is narrow.

Policy is compiled, not interpreted. The rules live in a binary policy file loaded at boot, built from .te files in system/sepolicy. A denial at runtime means the policy did not allow the operation, not that a check failed.

Labels are everywhere. Files have labels, sockets have labels, properties have labels, and even Binder services have labels. restorecon exists because a file created at runtime inherits a default label and may need to be relabelled to its intended type.

seccomp-bpf: the guard that checks the verb#

SELinux cannot stop a process from calling a system call that exists in the kernel; it can only stop the call from succeeding on a protected object. seccomp goes further: it filters system calls before they execute.

A process installs a small classic-BPF program that inspects struct seccomp_data — syscall number, arguments, architecture — and returns an action. The actions, in increasing severity:

ActionEffect
SECCOMP_RET_ALLOWRun the syscall
SECCOMP_RET_LOGRun it, but log it
SECCOMP_RET_ERRNOFail with an errno, without running it
SECCOMP_RET_TRAPSend SIGSYS to the thread
SECCOMP_RET_KILL_THREADKill the calling thread
SECCOMP_RET_KILL_PROCESSKill the whole process

The program may not dereference pointers. It sees only the raw register values, which is deliberate: seccomp is not a syscall interposer and cannot be tricked by a user-space race on an argument.7

There is one precondition that trips people up. To install a filter, the process must either hold CAP_SYS_ADMIN or set PR_SET_NO_NEW_PRIVS. The second is how unprivileged sandboxes do it: no-new-privs promises that nothing this process executes will gain privileges, which is exactly the property that makes it safe to let an unprivileged process constrain itself.

Android uses seccomp widely: the app process, media codecs, and many native daemons run under filters that allow only the syscalls they need. The practical consequence is that an app calling an unusual syscall through JNI can be killed with SIGSYS, and the tombstone will say so.

DMA-BUF heaps: one buffer, many readers#

A camera produces a frame. The GPU may need to process it, the display may need to scan it out, and an encoder may need to compress it. Copying the frame between three subsystems would be ruinous, so Android uses a buffer that is allocated once and shared by reference.

DMA-BUF is the kernel object. DMA-BUF heaps are where the memory comes from. Each heap is a character device:

/dev/dma_heap/system
/dev/dma_heap/system_uncached

A client opens the heap, allocates with an ioctl, and receives a file descriptor for the buffer. That descriptor can be passed to another process over a socket, and each holder maps it or hands it to a driver.

The heap-per-device design is the security improvement over the old ION allocator. With /dev/ion, anything that could open the device could allocate from any heap. With heaps, SELinux can allow a process to use the cached system heap and deny it the uncached one, or allow the camera to allocate but not the display.8

In Android 12, GKI 2.0 replaced ION with DMA-BUF heaps, and the android12-5.10 branch disabled CONFIG_ION. The userspace side is libdmabufheap, whose BufferAllocator takes a heap name rather than an ION heap ID.

eBPF: small programs inside the kernel#

is a virtual machine inside the kernel. A user-space program compiles a small bytecode program, the kernel verifies it for safety, and it runs attached to a probe or event.

Android uses it for things that would otherwise require a kernel patch:

  • Socket filtering and statistics in , which is how per-UID network accounting and firewalling work without a separate kernel module.
  • time_in_state, which records how long an app spent at each CPU frequency — the basis for power attribution.
  • gpu_mem, which tracks GPU memory per process and system-wide.
  • Histograms for statsd, collecting memory-management and power latencies at low overhead.9

The appeal is that the policy can change without a kernel update. A new firewall rule is a new eBPF program, not a new kernel image. That is the same modularity argument as GKI, applied at a much smaller scale.

The filesystems: EROFS and IncFS#

Two filesystems deserve mention because they changed what a system image can be.

EROFS is a read-only filesystem designed for compression and read performance. Its key trick is in-place decompression: compressed data sits at the end of a block and is expanded into the same page, so reading does not require allocating extra pages. Images are roughly a quarter smaller on average with compression, and reads are fast because there is no write path to maintain. On devices launching with Android 13 it became the default for read-only partitions in the GMS configuration, and as of Android 13 it is fully supported with Virtual A/B.10

IncFS (Incremental FS) is the filesystem behind incremental app installs. It presents a file whose contents are not yet all present; reads block until the requested page arrives. That is what lets a large game start before it has finished downloading. It arrived in Android 11, and from Android 12 it is built into the kernel rather than loaded as a module.11

How it evolved#

Architecture changes16 releases
APIVersionWhat changed
215.0 Lollipop
  • ART becomes the only runtime: ahead-of-time compilation at install
  • 64-bit ARM and x86 support
  • RenderThread and OpenGL ES 3.1
  • Project Volta and JobScheduler
  • SELinux fully enforcing
225.1 Lollipop
  • Device protection ties verified boot to Google accounts
236.0 Marshmallow
  • Runtime permission model changes how every app asks for access
  • Doze batches background work
  • Fingerprint HAL
  • Adoptable storage
247.0 Nougat
  • Project Treble groundwork
  • A/B seamless updates
  • Hybrid JIT + AOT compilation in ART
  • MediaServer split into sandboxed processes
268.0 Oreo
  • Project Treble: the vendor interface separates vendor and system code
  • Background execution limits
  • Broadcast and service restrictions
  • HIDL for HALs
278.1 Oreo
  • Android Go: a lighter configuration of the same platform
289 Pie
  • App Standby Buckets
  • Mainline groundwork
  • Non-SDK interface restrictions (greylists) begin
  • BiometricPrompt
2910
  • Project Mainline: APEX and updatable system modules
  • Scoped storage
  • Bubbles
  • Project Mainline starts shipping through Play
3011
  • GKI: the Generic Kernel Image splits the kernel into core + vendor modules
  • App hibernation
  • Package visibility filtering
  • Incremental app installs
3112
  • Bluetooth permissions split into the nearby-devices family
  • Exact alarm permissions
  • Foreground service launch restrictions
3212L
  • Window size classes and activity embedding
3313 Tiramisu
  • Granular media permissions replace READ_EXTERNAL_STORAGE
  • The notification permission becomes a runtime permission
  • Predictive back gesture
3414 Upside Down Cake
  • Foreground service types are mandatory
  • Selected photos access via READ_MEDIA_VISUAL_USER_SELECTED
3515 Vanilla Ice Cream
  • 16 KB page size support for native libraries
  • Edge-to-edge is enforced when targeting 35
3616 Baklava
  • The release moves to Q2 (June) with a Q4 feature drop
  • Two SDK releases per year starting with 36
  • Desktop windowing arrives in developer options then stable
3717 Cinnamon Bun
  • Strict per-device app memory limits are enforced by the system
  • Generational garbage collection is added to ART's Concurrent Mark-Compact collector
  • A lock-free MessageQueue for apps targeting API 37
  • Compose-first: new platform and Jetpack APIs target Compose only; View libraries enter maintenance mode
  • Hybrid APK signing (v3.2) combines classical and ML-DSA post-quantum signatures
  • Native library dynamic code loading must be read-only

The kernel-facing changes cluster in a few releases. Android 9 added App Standby Buckets and the groundwork for Mainline. Android 10 introduced dynamic partitions and scoped storage. Android 11 brought GKI, incremental installs and DMA-BUF heaps beginning to replace ION. Android 12 made GKI 2.0 mandatory on 5.10 and switched ION off. Android 13 added the system_dlkm partition and made EROFS the default read-only filesystem for new GMS devices.

Notice what is not in that list: new Android-specific kernel drivers. Since GKI, the platform's kernel work goes into the common kernel and the KMI, not into a vendor fork.

Under the hood#

Curiosity corner#

  • The system heap is cached and system_uncached is not, and the difference is a real performance trade-off: cached memory is faster to re-read but can be reclaimed under pressure.
  • A seccomp violation kills the process, not the syscall. There is no exception to catch in Kotlin, because the process is gone before the exception could exist.
  • EROFS was created by a Huawei engineer and upstreamed into Linux before Android adopted it — another case of an Android need being met by a general kernel feature.
  • Binder's identity guarantee is why Android can have a permission model at all. Any IPC mechanism can pass a message; very few can tell the receiver, with certainty, who sent it.

Try it#

Inspect the kernel surfaces Android uses

adb shell cat /proc/meminfo | head -3 adb shell cat /proc/pressure/memory adb shell ls /dev/dma_heap/ adb shell cat /proc/misc | grep binder adb shell mount | grep -E "erofs|ext4" | head

Example output
MemTotal:        7643212 kB
MemAvailable:    2103444 kB
some avg10=0.42 avg60=0.31 avg300=0.18 total=4823911
system  cached
system-uncached  cacheable
binder 507904 0
hwbinder 507904 0
vndbinder 507904 0
erofs
ext4

/proc/misc is the fastest way to see which Binder devices exist; each one is a separate domain with its own SELinux label. /dev/dma_heap/ shows which heaps the device exposes — a device with only system and system-uncached is not exposing a hardware-specific heap, which is normal for many phones.

Build it#

The N04 lab builds a minimal HAL service and shows the other side of the Binder boundary: a vendor process publishing an interface that a framework client can call. It is the most concrete way to see how the kernel's IPC facility becomes a platform contract.

Check yourself#

Check yourself

1 / 12

Ashmem, Android's original shared-memory driver, was replaced by which standard Linux mechanism?

0 answered · 0 correct

Sources#

Sources

Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.

  1. docsbinderfs — Linux kernel documentationdocs.kernel.orgaccessed 2026-10-27↩
  2. docsmemfd_create(2) — Linux man pagedocs.kernel.orgaccessed 2026-10-27↩
  3. docsControl Group v2docs.kernel.orgaccessed 2026-10-27↩
  4. docsPressure Stall Information (PSI)docs.kernel.orgaccessed 2026-10-27↩
  5. docsLow-memory killer daemon (lmkd)source.android.comaccessed 2026-10-27↩
  6. docsSELinux conceptssource.android.comaccessed 2026-10-27↩
  7. docsSeccomp BPFdocs.kernel.orgaccessed 2026-10-27↩
  8. docsDMA-BUF heapssource.android.comaccessed 2026-10-27↩
  9. docseBPF on Androidsource.android.comaccessed 2026-10-27↩
  10. docsEROFS filesystemsource.android.comaccessed 2026-10-27↩
  11. docsIncremental FSsource.android.comaccessed 2026-10-27↩