Readahead
Readahead is the kernel’s speculative population of the page cache with file pages that have not yet been requested, on the bet that an application reading sequentially will soon ask for them. The principle is simple: disk and SSD I/O has high latency but high bandwidth, so it pays to fetch a large contiguous run in one trip rather than fault each page in one at a time. Linux’s readahead is on-demand and adaptive — it detects sequential access at run time, grows its prefetch window as confidence rises, and (crucially) overlaps the next batch’s I/O with the application’s consumption of the current batch by planting a marker page (
PG_readahead) partway through. The headline source ismm/readahead.c, whose openingDOC: Readahead Overviewcomment is the canonical description (permm/readahead.c, v6.12). Readahead only ever populates folios not already present; it never re-reads a cached-but-stale page (that is->read_folio()’s job).
This note covers the on-demand algorithm — sequential detection, window growth and shrink, the synchronous-vs-asynchronous split — and the userspace knobs (madvise, posix_fadvise, the readahead(2) syscall, read_ahead_kb, blockdev --setra). It is the §7 page-cache leaf that sits directly atop The Page Cache and is triggered from the read and fault paths described in Demand Paging.
Mental Model
Think of readahead as a sliding window with a tripwire. The window is a contiguous range of file offsets the kernel has decided to fetch; the tripwire is a single page inside that window, flagged PG_readahead. When the application’s reads finally touch the tripwire page, that access fires the next readahead — asynchronously, while the application keeps consuming pages it already has in cache. The result is a pipeline: by the time the reader drains the current window, the next window’s I/O is already in flight.
flowchart LR subgraph WIN["Current readahead window (in page cache)"] direction LR P0["page<br/>(synchronous part:<br/>needed now)"] P1["..."] MARK["marked page<br/>PG_readahead<br/>(tripwire)"] P2["..."] PN["last page<br/>(async tail)"] P0 --> P1 --> MARK --> P2 --> PN end APP["app read cursor<br/>moving left to right"] -->|"touches marked page"| MARK MARK -->|"fires"| ASYNC["page_cache_async_ra():<br/>grow window, push start<br/>forward, submit next I/O<br/>(non-blocking)"] ASYNC -->|"populates"| NEXTWIN["next window"]
The readahead pipeline. What it shows: a readahead window already sitting in the page cache, split into a leading region (already consumed or about to be) and a trailing async_size region; the first page of the async region carries the PG_readahead flag. The insight: the marker page is the synchronization primitive that decouples fetching from consuming. The application never explicitly asks for readahead; it just reads, and crossing the tripwire transparently triggers the next batch. Steady-state sequential reads thus become fully asynchronous — the reader rarely blocks on I/O because the data is always one window ahead.
The two entry points correspond to the two ways an access can land:
page_cache_sync_ra()— a cache miss: the requested page is not present, so the application is blocked and the readahead must include a synchronous component (fetch what is needed now, plus speculate ahead).page_cache_async_ra()— a cache hit on a marked page: the page is present (so the application is not blocked) but it carriesPG_readahead, signalling that it is time to fetch the next window. This readahead is purely asynchronous (per theDOCoverview,mm/readahead.c).
Mechanical Walk-through
The state: struct file_ra_state
Per-open-file readahead state lives in struct file_ra_state (embedded as file->f_ra). The fields the algorithm pivots on are start (offset of the most recent readahead window), size (its total page count), async_size (how many pages at the tail are the async region — the marker sits at start + size - async_size), ra_pages (the maximum window in pages, seeded from the backing device’s default), and prev_pos (the last byte position of the previous read, used to detect sequential vs random). file_ra_state_init() seeds ra_pages from inode_to_bdi(mapping->host)->ra_pages and sets prev_pos = -1 (file_ra_state_init, mm/readahead.c).
Sequential detection (the synchronous path)
page_cache_sync_ra() runs when a needed page is missing. Its logic (lines ~538–609 of v6.12 readahead.c):
- Forced/degenerate cases first. If readahead is disabled (
ra_pages == 0) or the block cgroup is congested (blk_cgroup_congested()), or the file was markedFMODE_RANDOM(viaposix_fadvise(POSIX_FADV_RANDOM)), it falls back toforce_page_cache_ra()reading essentially just the requested range — “be dumb,” as the comment puts it. - Sequential heuristic. It computes
prev_index = ra->prev_pos >> PAGE_SHIFTand checksindex - prev_index <= 1. If the current read’s start page is the same as or immediately after the previous read’s end page (== 0for unaligned reads,== 1for the trivial sequential case), or this is the start of the file (!index), or the request is larger than the whole window, it treats the access as the beginning of a sequential stream: it setsra->start = index,ra->size = get_init_ra_size(req_count, max_pages), and anasync_size. - History probe. Otherwise it can’t tell from
prev_posalone, so it looks at the page cache itself:page_cache_prev_miss()walks backward fromindex - 1to find the nearest absent page, givingcontig_count— how many contiguous pages precede the current access. Ifcontig_count <= req_count, this is “a standalone, small random read” — it reads exactly the request and does not pollute the readahead state (so one stray read can’t poison the window for a sequential stream). If a long contiguous run is found (especially if the file is cached from offset 0,miss == ULONG_MAX, which doublescontig_countas “a strong indication of a long-run stream”), it sizes the window tomin(contig_count + req_count, max_pages)withasync_size = 1.
Window growth: get_init_ra_size and get_next_ra_size
The initial window is set by get_init_ra_size(size, max), which rounds the request up to the next power of two and then scales: ×4 for small requests (<= max/32), ×2 for medium (<= max/4), or clamps to max for large. The comment gives the concrete shape for a 128 KB (32-page) max: a 1–2 page request → 16 KB, 3–4 pages → 32 KB, 5–8 pages → 64 KB, >8 pages → 128 KB initial (get_init_ra_size, mm/readahead.c).
Each subsequent confirmed-sequential readahead ramps the window up via get_next_ra_size(ra, max):
static unsigned long get_next_ra_size(struct file_ra_state *ra, unsigned long max)
{
unsigned long cur = ra->size;
if (cur < max / 16)
return 4 * cur; /* aggressive quadrupling while far from the cap */
if (cur <= max / 2)
return 2 * cur; /* doubling in the mid-range */
return max; /* clamp at the configured maximum */
}The kernel “ramps up the readahead size aggressively at first, but slows down as it approaches max_readahead” — quadruple while the window is tiny, double in the middle, then saturate. This is a multiplicative-increase confidence build: the longer the stream stays sequential, the bigger the bets, capped at ra_pages (or the device’s optimal io_pages for oversized requests).
The marker page and the async path
When page_cache_ra_unbounded() actually fills the cache, it sets PG_readahead (via folio_set_readahead()) on exactly one folio — the one at index mark, computed so it sits at the start of the async tail (start + size - async_size). The flag is only ever set on freshly-allocated folios, never on pages that turned out to be already cached — this “avoids the readahead-for-nothing fuss, saving pointless page cache lookups,” per the on-demand design comment.
Later, when the read or fault path finds a cached folio carrying PG_readahead, it calls page_cache_async_ra(). That function (lines ~612–671):
- Returns immediately if readahead is off, the folio is under writeback (since
PG_readaheadandPG_reclaimshare a bit), or the cgroup is congested. - Clears the marker (
folio_clear_readahead()). - If the access landed at the expected index (
round_down(ra->start + ra->size - ra->async_size, ...)), it confirms the sequential prediction: it pushes the window forward (ra->start += ra->size), grows it viaget_next_ra_size(), setsasync_size = size(full pipelining), and submits the I/O. - If the marker was hit at an unexpected place (e.g. interleaved streams on one fd), it probes the cache via
page_cache_next_miss()to reconstruct the window, then ramps and submits.
Note the marker fires while the page is already in cache, so the application does not block — the entire next-window fetch is asynchronous. This is the pipelining payoff.
The mmap fault path
For memory-mapped files, the equivalent lives in mm/filemap.c: do_sync_mmap_readahead() and do_async_mmap_readahead(). Here the VMA flags VM_RAND_READ and VM_SEQ_READ (set by madvise) steer behaviour directly. VM_RAND_READ short-circuits readahead entirely; VM_SEQ_READ calls page_cache_sync_ra() with the full window. Otherwise the kernel does read-around (centering a window on the fault: ra->start = max(0, pgoff - ra_pages/2)) and counts misses: if mmap_miss exceeds MMAP_LOTSAMISS (100), it concludes “we miss much more than we hit” and stops bothering — readahead “will only hurt” a random mmap workload (per do_sync_mmap_readahead, mm/filemap.c, v6.12).
The userspace knobs
madvise(2) — advice for memory-mapped regions
madvise() sets VMA flags that the mmap fault path reads (per madvise(2) man page and mm/madvise.c):
MADV_NORMAL— default; clears bothVM_SEQ_READandVM_RAND_READ.MADV_SEQUENTIAL— setsVM_SEQ_READ. “Pages in the given range can be aggressively read ahead, and may be freed soon after they are accessed.” The fault path issues full-window sync readahead and ages the pages out fast.MADV_RANDOM— setsVM_RAND_READ. “Read ahead may be less useful than normally” — in practice the mmap path skips readahead and faults pages in one at a time.MADV_WILLNEED— triggers an immediate, non-blocking readahead of the range (madvise_willneed()→force_page_cache_readahead-style population), pulling pages into cache before they are touched.MADV_DONTNEED— the inverse: frees the range’s resident pages (for anonymous memory this zero-fills on next touch). Not a readahead control per se, but the eviction counterpart.
posix_fadvise(2) — advice for file descriptors
posix_fadvise() adjusts file->f_ra.ra_pages and file->f_mode flags. The exact effects from mm/fadvise.c (v6.12) are unambiguous and worth pinning precisely:
POSIX_FADV_NORMAL— resetsf_ra.ra_pages = bdi->ra_pages(the device default) and clearsFMODE_RANDOM | FMODE_NOREUSE.POSIX_FADV_SEQUENTIAL— setsfile->f_ra.ra_pages = bdi->ra_pages * 2(doubles the max window) and clearsFMODE_RANDOM(mm/fadvise.cline 91).POSIX_FADV_RANDOM— setsFMODE_RANDOM, which makespage_cache_sync_ra()take its “be dumb”do_forced_rabranch and read essentially one page at a time — readahead is effectively disabled.POSIX_FADV_WILLNEED— initiates a non-blocking readahead of the specified region into the page cache (the kernel may trim it under memory pressure).POSIX_FADV_DONTNEED— attempts to drop the cached pages for the region (the standard trick for streaming a huge file once without evicting everyone else’s working set).POSIX_FADV_NOREUSE— setsFMODE_NOREUSE; does not change the window. Since Linux 6.3 it hints that page replacement can deprioritize these pages (posix_fadvise(2)).
readahead(2) — the explicit syscall
readahead(int fd, loff_t offset, size_t count) populates the page cache for a range up front. In v6.12 its implementation (ksys_readahead) validates the fd is readable and backs a regular file or block device, then simply calls vfs_fadvise(..., POSIX_FADV_WILLNEED) — so readahead(2) and posix_fadvise(WILLNEED) are the same mechanism under the hood (per ksys_readahead, mm/readahead.c). It blocks until the read requests have been issued, but not until they complete.
Per-device tuning: read_ahead_kb and blockdev --setra
The maximum window is a backing-device-info (BDI) property. The default is VM_READAHEAD_PAGES, defined as SZ_128K / PAGE_SIZE — i.e. 128 KB (32 pages on a 4 KB-page system) — set in bdi_init() for both ra_pages and io_pages (per include/linux/pagemap.h and mm/backing-dev.c line 1030). Two equivalent ways to change it at runtime:
# Via sysfs — value is in KiB, applies to the BDI behind a device:
cat /sys/class/bdi/8:0/read_ahead_kb # 8:0 = sda; default 128
echo 4096 > /sys/class/bdi/8:0/read_ahead_kb # bump to 4 MiB for big sequential reads
# Via blockdev — value is in 512-byte SECTORS, not KiB:
blockdev --getra /dev/sda # prints sectors (256 == 128 KiB)
blockdev --setra 8192 /dev/sda # 8192 sectors == 4 MiBread_ahead_kb stores bdi->ra_pages = read_ahead_kb >> (PAGE_SHIFT - 10) and shows back K(bdi->ra_pages) (mm/backing-dev.c). Note the unit mismatch: read_ahead_kb is kibibytes, blockdev --setra is 512-byte sectors — a 128 KiB window is 128 via sysfs but 256 via blockdev. They poke the same underlying ra_pages.
Uncertain
Verify: the exact value
read_ahead_kbreports for a given block device on a stock distro (the per-device default can be overridden by udev rules, RAID/LVM stacking, orblockdev --setrain init scripts; some setups ship 128, others larger). Reason:VM_READAHEAD_PAGESis the kernel default but distros and storage stacks layer on top. To resolve: read/sys/class/bdi/<maj>:<min>/read_ahead_kbon the target system rather than assuming 128. uncertain
When Readahead Hurts
Readahead is a bet, and the bet loses on random-access workloads. If an application seeks all over a file (a database doing point lookups, a process touching mmap’d pages at random), every speculative page the kernel fetches alongside the requested one is wasted: it consumes I/O bandwidth, evicts genuinely useful pages from the cache (cache pollution), and inflates memory pressure — all to fetch data that is never read. This is exactly why the kernel works so hard to detect randomness:
- The
mmap_miss > MMAP_LOTSAMISScheck disables read-around for random mmap access (mm/filemap.c). - The “standalone, small random read” branch in
page_cache_sync_ra()reads exactly what was asked and refuses to update the window. FMODE_RANDOM/VM_RAND_READlet userspace declare randomness explicitly, short-circuiting the heuristics.
The failure mode when these don’t fire: a workload with coincidentally adjacent random reads can trick the history-probe into inferring a sequential stream, ballooning the window and thrashing the cache. The fix is to tell the kernel the truth — posix_fadvise(POSIX_FADV_RANDOM) or madvise(MADV_RANDOM) — which is why databases (PostgreSQL, MySQL/InnoDB) and random-I/O engines set these flags or tune read_ahead_kb down for their data volumes. Conversely, large sequential scans (backups, cp of huge files, video streaming) benefit from raising read_ahead_kb to multiple megabytes so the device sees fewer, larger requests.
A subtler hurt: readahead competes for the same page cache as everyone else. On a memory-constrained box, an over-eager window can evict hot pages, and the freshly-read-ahead pages may themselves be reclaimed before they’re used — pure waste. The psi_memstall_enter/leave instrumentation around read_pages() exists precisely so this stall shows up in Pressure Stall Information.
Failure Modes and Diagnosis
- No prefetch on a sequential workload → the access pattern isn’t being recognized as sequential (interleaved streams on one fd, or
prev_posreset by seeks). Check whetherMADV_SEQUENTIAL/POSIX_FADV_SEQUENTIALhelps. - Wasted I/O / cache churn on random reads → readahead heuristics mis-fired. Set
FMODE_RANDOM/VM_RAND_READ, or lowerread_ahead_kb. Observe withiostat(read bandwidth far exceeding what the app consumes) and thereadaheadtracepoints. - Readahead silently off →
ra_pages == 0(someone setread_ahead_kbto 0), orblk_cgroup_congested()is throttling, orFMODE_RANDOMis set. Both sync and async paths bail early. - It never goes fully async → the marker page keeps landing on already-cached pages (so
PG_readaheadis never set), or the window is so small the async tail is empty.
Alternatives and Boundaries
Readahead is speculative file-page population. Distinct mechanisms:
- Explicit prefetch —
readahead(2)/posix_fadvise(WILLNEED)populate on the application’s command rather than by heuristic. Use when you know the access pattern (e.g. about to scan a file). - Demand paging (Demand Paging) — the reactive fill of a page on the fault that needs it; readahead is the proactive sibling that fills neighbours speculatively. Every readahead is “free” only because the demand fault would otherwise have happened anyway.
- Writeback (Dirty Pages and Writeback) — the output side of the page cache; readahead is the input side.
O_DIRECT— bypasses the page cache (and thus readahead) entirely, handing I/O to the application’s own buffers. Databases that manage their own buffer pool use this to avoid double-caching and unwanted readahead.
See Also
- The Page Cache — readahead’s target: every readahead-fetched page lands here, indexed by the address-space XArray.
- Demand Paging — the reactive fault-driven fill; readahead is its proactive speculative counterpart.
- Dirty Pages and Writeback / Dirty Page Balancing and Throttling — the writeback side of the same page cache.
- Pressure Stall Information — where readahead-induced memory stalls surface (
psi_memstall_*aroundread_pages()). - Anonymous vs File-Backed Memory — readahead applies to file-backed pages; anonymous memory has no readahead (it has swap-in instead).
- UP: Linux Memory Management MOC §7 (The Page Cache and Writeback).