Skip to content
LinuxDeep Dive Published Updated 5 min readViews unavailable

Linux madvise Reclaim Semantics: MADV_FREE Is Not MADV_DONTNEED

Distinguish lazy anonymous-page reclamation from immediate discard semantics, and build a disposable test that checks the documented boundary.

madvise(2) is often called a performance hint, but some advice values change what a later read may return. This matters when an allocator releases a region for reuse, when an application caches decoded data, or when a benchmark tries to explain a falling resident set size. MADV_DONTNEED and MADV_FREE are both reclaim-related, yet their contracts are not interchangeable. The examples here apply to Linux mappings and the Linux-specific madvise interface; MADV_FREE appeared in Linux 4.5. They are not a portable POSIX recipe, and the behavior of a process on a particular machine should be measured rather than inferred from one RSS sample. Linux madvise(2)

Decide whether old bytes must disappear now

After a successful MADV_DONTNEED, subsequent access to anonymous private pages yields zero-fill-on-demand. For shared mappings and file-backed mappings, later access instead repopulates from the up-to-date underlying object. The manual says the process’s RSS is reduced immediately, although freeing shared pages can be deferred. The advice cannot be applied to locked pages or special VM_PFNMAP pages. Thus MADV_DONTNEED is a semantic discard, not merely a request to move data to a cheaper tier. A caller must no longer rely on prior anonymous contents. Linux madvise(2)

MADV_FREE makes a weaker promise in a different direction. It applies only to private anonymous pages. The kernel may free them later under pressure; until it does, an unwritten page can still expose its old contents. If the caller writes a page before reclamation, that write cancels the free operation for that page. If reclamation occurs first, later references see zero-fill-on-demand. A caller cannot tell from a successful return alone whether a future read will see old bytes or zeroes. Therefore use MADV_FREE only after the application has already declared those bytes dead. Do not use it as a data-sanitization operation. Linux madvise(2)

These distinctions make a useful review question: is the application discarding content that must immediately stop being observed, or merely offering dead anonymous pages for opportunistic reclamation? For the former, MADV_DONTNEED has the documented zero-fill behavior on anonymous private memory. For the latter, MADV_FREE permits the kernel to avoid immediate work. Neither is a replacement for an application-level ownership protocol. A thread that still reads the range after another thread offers it for reuse remains a program bug, regardless of which advice call was used. Linux madvise(2)

Rehearse with a disposable private mapping

This small C program allocates one private anonymous page, writes a marker, offers it to the kernel, and reads it back. It does not touch files or privileged controls. Build it on a Linux system with the required headers and libc, not on a non-Linux host. The MADV_FREE branch deliberately prints an observation, not an expected result: either the old byte or zero can be consistent with the contract. The MADV_DONTNEED branch should see zero after successful advice on this mapping. Linux madvise(2)

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

int main(int argc, char **argv) {
    long n = sysconf(_SC_PAGESIZE);
    if (n <= 0) return 1;
    unsigned char *p = mmap(NULL, (size_t)n, PROT_READ | PROT_WRITE,
                            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (p == MAP_FAILED) { perror("mmap"); return 1; }
    p[0] = 0x5a;
    int advice = argc > 1 && strcmp(argv[1], "free") == 0
               ? MADV_FREE : MADV_DONTNEED;
    if (madvise(p, (size_t)n, advice) != 0) {
        perror("madvise"); munmap(p, (size_t)n); return 1;
    }
    printf("byte after advice: 0x%02x\n", p[0]);
    if (munmap(p, (size_t)n) != 0) { perror("munmap"); return 1; }
    return 0;
}

Compile the snippet as a local scratch file and run both modes on an expendable Linux VM:

cc -Wall -Wextra -O2 madvise_demo.c -o madvise_demo
./madvise_demo
./madvise_demo free

Do not add artificial memory pressure to a shared system just to make MADV_FREE return zero. A zero in one run does not imply synchronous reclamation, and 0x5a in another is not a kernel defect. The call result, mapping type, and kernel version are the facts to record. The manual also notes a historical exception: before Linux 4.12, pages on a swapless system could be freed immediately by MADV_FREE. That is another reason to state the runtime boundary when interpreting an old trace. Linux madvise(2)

Validate the requested range and handle partial results

madvise() operates on whole pages: the starting address must be page-aligned, while the byte length is rounded up to a page boundary. An address returned by mmap() is aligned for this example, but production code that advises a slice inside a larger allocation must calculate the page range deliberately. Do not round a pointer down and accidentally include a neighboring object. Also keep the requested length within the mapping the caller actually owns. Linux madvise(2)

Treat the return value as a separate contract from the memory effect. A successful call returns zero; an error returns -1 and sets errno. Linux specifically documents that, if a requested range includes unmapped portions, it applies the advice to mapped portions and returns ENOMEM. Consequently a nonzero result does not prove that nothing changed, and blindly retrying the same whole range can repeat a partial operation. Record the address, length, advice, return value, and errno, then reconcile the range with the process’s mapping lifecycle before deciding what to retry. EINVAL can identify a misaligned address or a mapping/advice combination the kernel does not permit; for example, MADV_FREE is limited to private anonymous pages, while MADV_DONTNEED rejects locked and special VM_PFNMAP pages. Linux madvise(2)

Keep measurement separate from meaning

RSS can help observe pressure, but it does not define the advice semantics. The DONTNEED contract permits an immediate RSS decrease while a shared page’s actual release is delayed. The FREE contract permits a successful call while content remains readable until pressure. Neither single-process RSS nor a single post-call read establishes how quickly a production workload will reclaim memory. Test against the workload’s own read/write lifetime, and treat a later zero as a legitimate result only when the application has already abandoned the old data. Linux madvise(2)

Related:

Sources:

Comments