mlock() vs mlockall() — When to Use Which
In the previous file, we saw mlock() which locks a specific address range. You have to know exactly which buffers you want to lock and call mlock() on each one.
mlockall() takes a different approach — it locks everything in the process’s virtual address space in one call. This is more convenient when:
- You want to guarantee that all process memory is always in RAM
- Your real-time code uses many different buffers and it is impractical to track each one
- You want to lock even future allocations automatically (using MCL_FUTURE)
- You are writing a daemon or service that must have zero page-fault latency
The mlockall() and munlockall() API
#include <sys/mman.h>
int mlockall(int flags);
int munlockall(void);
/* Both return 0 on success, or -1 on error */
mlockall(flags) — Locks some or all of the pages in the calling process’s virtual address space. The flags argument controls which pages are locked.
munlockall() — Removes all memory locks established by the calling process. Also cancels any pending MCL_FUTURE lock so future allocations will no longer be automatically locked.
mlockall() Flags — MCL_CURRENT and MCL_FUTURE
| Flag | What It Locks | When Pages Are Loaded | Typical Use |
| MCL_CURRENT | All pages currently mapped — text, data, stack, heap, all mappings | Immediately — all resident before mlockall() returns | Lock everything right now |
| MCL_FUTURE | All pages mapped after this call — new mmap(), heap growth, stack growth | On allocation — each new page is locked as it is mapped | Ensure future allocations are also locked |
| Both combined | All current + all future pages | Current pages immediately, future pages on allocation | Full real-time process lock |
What MCL_CURRENT Locks — Process Address Space Breakdown
| Process Virtual Address Space (top = high addr) | |
| Stack (grows downward) | ✓ Locked by MCL_CURRENT |
| mmap() regions (shared libs, file maps) | ✓ Locked by MCL_CURRENT |
| Heap (malloc/new — grows upward) | ✓ Locked by MCL_CURRENT |
| BSS (uninitialized global variables) | ✓ Locked by MCL_CURRENT |
| Data segment (initialized globals) | ✓ Locked by MCL_CURRENT |
| Text segment (program code) | ✓ Locked by MCL_CURRENT |
| Future: new mmap(), heap growth, stack growth | ✓ Locked by MCL_FUTURE only |
Example 1: Basic mlockall() — Lock Everything Now
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
/* Lock ALL currently mapped pages into RAM.
* After this returns, no page in this process will cause a page fault. */
if (mlockall(MCL_CURRENT) == -1) {
perror("mlockall MCL_CURRENT");
/* Likely cause: ENOMEM (not enough RAM or RLIMIT_MEMLOCK too low)
* EPERM (need CAP_IPC_LOCK on older kernels) */
return 1;
}
printf("All current pages locked in RAM\n");
/* From here on, no page fault can occur for existing data */
char buf[1024];
memset(buf, 'A', sizeof(buf)); /* No page fault */
printf("buf filled: %c\n", buf[0]);
/* Cleanup */
munlockall();
printf("All locks removed\n");
return 0;
}
Example 2: MCL_FUTURE — Auto-Lock Future Allocations
With MCL_FUTURE, every new page mapped after the call — including malloc(), mmap(), and stack growth — is automatically locked. This is the most comprehensive protection.
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
/* Lock current + all future pages */
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
perror("mlockall");
return 1;
}
printf("All pages locked (current and future)\n");
/* This malloc() will automatically be locked — no extra call needed */
char *heap_buf = malloc(64 * 1024); /* 64 KB — auto-locked */
if (!heap_buf) {
/* malloc CAN fail here if RLIMIT_MEMLOCK is exhausted
* because MCL_FUTURE means every new page must be locked,
* and if the limit is hit, the allocation fails */
perror("malloc failed — RLIMIT_MEMLOCK may be exhausted");
munlockall();
return 1;
}
memset(heap_buf, 0, 64 * 1024);
printf("64KB heap buffer auto-locked (no mlock() call needed)\n");
/* mmap() also auto-locked with MCL_FUTURE */
void *mapped = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mapped == MAP_FAILED) {
perror("mmap");
} else {
printf("mmap region also auto-locked\n");
munmap(mapped, 4096);
}
free(heap_buf);
munlockall(); /* Remove all locks + cancel MCL_FUTURE */
printf("Cleanup done\n");
return 0;
}
When MCL_FUTURE is active, every new page the process maps must be locked. If the system runs out of RAM or the process hits its RLIMIT_MEMLOCK limit, the kernel cannot honor the lock — so the allocation itself fails. This means:
malloc()may return NULLmmap()may return MAP_FAILED- Stack growth may trigger SIGSEGV (signal 11) instead of growing normally
Always set RLIMIT_MEMLOCK to a sufficient value and check return values carefully when using MCL_FUTURE.
Example 3: Real-Time Daemon Startup Pattern
This is the standard way real-time Linux applications (JACK audio, Xenomai, robotics daemons) initialize themselves to avoid all page faults during operation:
/* Standard real-time initialization sequence */
#include <sys/mman.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* Pre-fault the stack by writing to each page */
static void prefault_stack(size_t stack_size)
{
volatile char stack_buf[stack_size];
/* Touch every page of the stack to bring it into RAM */
for (size_t i = 0; i < stack_size; i += 4096)
stack_buf[i] = 0;
}
/* Pre-fault a heap region */
static void prefault_heap(size_t heap_size)
{
char *buf = malloc(heap_size);
if (buf) {
memset(buf, 0, heap_size); /* Touch every page */
free(buf);
}
}
int init_realtime(void)
{
struct rlimit rl;
/* Step 1: Raise RLIMIT_MEMLOCK to unlimited (requires root) */
rl.rlim_cur = RLIM_INFINITY;
rl.rlim_max = RLIM_INFINITY;
if (setrlimit(RLIMIT_MEMLOCK, &rl) == -1) {
perror("setrlimit RLIMIT_MEMLOCK — run as root");
return -1;
}
/* Step 2: Lock all current + future pages */
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
perror("mlockall");
return -1;
}
/* Step 3: Pre-fault the stack (force stack pages into RAM now) */
prefault_stack(8 * 1024 * 1024); /* Pre-fault 8MB of stack */
/* Step 4: Pre-fault expected heap usage */
prefault_heap(64 * 1024 * 1024); /* Pre-fault 64MB of heap */
printf("Real-time init complete — no page faults expected\n");
return 0;
}
int main(void)
{
if (init_realtime() != 0) {
fprintf(stderr, "Real-time init failed\n");
return 1;
}
/* Real-time loop — zero page fault latency */
for (int i = 0; i < 5; i++) {
/* Simulate real-time task */
printf("RT cycle %d — guaranteed no page fault\n", i);
usleep(1000); /* 1ms period */
}
munlockall();
return 0;
}
Example 4: Verify mlockall() Effect via /proc/self/status
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
/* Print VmRSS (resident) and VmLck (locked) from /proc/self/status */
static void print_vm_stats(const char *label)
{
FILE *fp = fopen("/proc/self/status", "r");
if (!fp) return;
char line[256];
char vmrss[64] = "?", vmlck[64] = "?";
while (fgets(line, sizeof(line), fp)) {
if (strncmp(line, "VmRSS:", 6) == 0)
sscanf(line + 6, "%s", vmrss);
if (strncmp(line, "VmLck:", 6) == 0)
sscanf(line + 6, "%s", vmlck);
}
fclose(fp);
printf("[%s] VmRSS: %s kB VmLck: %s kB\n", label, vmrss, vmlck);
}
int main(void)
{
print_vm_stats("Before mlockall");
if (mlockall(MCL_CURRENT) == -1) {
perror("mlockall");
return 1;
}
print_vm_stats("After mlockall");
/* VmLck should now equal (or be close to) VmRSS */
munlockall();
print_vm_stats("After munlockall");
/* VmLck drops back to 0 */
return 0;
}
/* Sample output:
* [Before mlockall] VmRSS: 1280 kB VmLck: 0 kB
* [After mlockall] VmRSS: 1280 kB VmLck: 1280 kB
* [After munlockall] VmRSS: 1280 kB VmLck: 0 kB
*/
munlockall() — Remove All Locks
munlockall() does two things in one call:
| Action | Effect |
| Removes all existing memory locks | All currently locked pages become eligible for eviction |
| Cancels MCL_FUTURE effect | Future allocations will no longer be auto-locked |
CAP_IPC_LOCK privilege. Since Linux 2.6.9 this requirement was removed — any process can call munlockall() to remove its own locks. (Interestingly, munlock() never required privilege, making this an inconsistency that was later fixed.)MCL_CURRENT vs MCL_FUTURE — Side-by-Side Comparison
| Scenario | MCL_CURRENT alone | MCL_FUTURE alone | MCL_CURRENT | MCL_FUTURE |
| Existing stack pages | ✓ Locked | ✗ Not locked | ✓ Locked |
| Existing heap pages | ✓ Locked | ✗ Not locked | ✓ Locked |
| Future malloc() pages | ✗ Not locked | ✓ Auto-locked | ✓ Auto-locked |
| Future mmap() regions | ✗ Not locked | ✓ Auto-locked | ✓ Auto-locked |
| Future stack growth | ✗ Not locked | ✓ Auto-locked | ✓ Auto-locked |
| malloc() may fail? | No extra risk | Yes — if limit hit | Yes — if limit hit |
Example 5: Choosing Between mlock() and mlockall()
/*
* Use mlock() when:
* - You have specific known buffers to protect (audio ring buffer, key buffer)
* - You want minimal RAM footprint (only lock what you need)
* - RLIMIT_MEMLOCK is constrained
*
* Use mlockall() when:
* - You want zero-latency for the ENTIRE process
* - You cannot track all allocations individually
* - You are writing a real-time daemon or time-critical service
*/
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Approach A: Selective locking with mlock() */
void approach_a_selective(void)
{
char *audio_buf, *key_buf;
posix_memalign((void **)&audio_buf, 4096, 64 * 1024);
posix_memalign((void **)&key_buf, 4096, 4096);
memset(audio_buf, 0, 64 * 1024);
memset(key_buf, 0, 4096);
mlock(audio_buf, 64 * 1024); /* Lock only audio buffer */
mlock(key_buf, 4096); /* Lock only key buffer */
printf("Selective: only 65KB locked, rest of process can swap\n");
munlock(audio_buf, 64 * 1024);
munlock(key_buf, 4096);
free(audio_buf);
free(key_buf);
}
/* Approach B: Full lock with mlockall() */
void approach_b_full(void)
{
mlockall(MCL_CURRENT | MCL_FUTURE);
/* Everything from here on is locked — no tracking needed */
char *buf1 = malloc(16 * 1024); /* auto-locked */
char *buf2 = malloc(32 * 1024); /* auto-locked */
memset(buf1, 0, 16 * 1024);
memset(buf2, 0, 32 * 1024);
printf("Full lock: entire process address space locked\n");
free(buf1);
free(buf2);
munlockall();
}
int main(void)
{
printf("--- Approach A: Selective mlock() ---\n");
approach_a_selective();
printf("--- Approach B: Full mlockall() ---\n");
approach_b_full();
return 0;
}
Same Rules Apply as mlock() — Recap
| Rule | Applies to mlockall()? | Detail |
| Not inherited by fork() | Yes | Child process has no locks |
| Not preserved across exec() | Yes | New program image starts with no locks |
| Shared pages stay locked | Yes | Pages shared via MAP_SHARED stay locked while any holder has a lock |
| RLIMIT_MEMLOCK applies | Yes | Total locked memory cannot exceed soft limit (unless CAP_IPC_LOCK) |
| munmap() removes lock | Yes | Unmapping a region also removes its lock |
Interview Questions and Answers
Q1: What is the difference between mlock() and mlockall()?
mlock() locks a specific address range you specify. mlockall() locks the entire process address space — all segments (text, data, BSS, stack, heap, all mappings) in one call. mlockall() is more convenient for real-time processes that want to eliminate all page faults without tracking individual allocations.
Q2: What is MCL_CURRENT and when do you use it?
MCL_CURRENT locks all pages that are currently mapped in the process’s virtual address space. All these pages are guaranteed to be in RAM before mlockall() returns. Use it when you want to immediately eliminate page faults for all existing memory — code, data, stack, heap, and libraries.
Q3: What is MCL_FUTURE and what are its risks?
MCL_FUTURE causes all pages mapped after the mlockall() call to be automatically locked. This includes future malloc(), mmap(), and stack growth. The risk is that if the system runs out of RAM or RLIMIT_MEMLOCK is exceeded, these allocations will FAIL — malloc() returns NULL, mmap() returns MAP_FAILED, and stack growth may cause SIGSEGV.
Q4: What does munlockall() do beyond just unlocking pages?
munlockall() does two things: it removes all existing memory locks from the process, AND it cancels the effect of any previous mlockall(MCL_FUTURE) call. After munlockall(), future allocations will no longer be automatically locked.
Q5: Why is pre-faulting the stack important when using mlockall(MCL_CURRENT)?
mlockall(MCL_CURRENT) locks only the pages currently mapped — including the currently-used portion of the stack. But the stack grows dynamically as functions are called. Pages for deeper function calls are not yet mapped, so they are not locked. Pre-faulting the stack (by accessing stack memory in a dummy function to a large depth) forces the kernel to map those stack pages, so they get included in the MCL_CURRENT lock. Combined with MCL_FUTURE, further stack growth is also automatically locked.
Q6: What privilege is required to call mlockall()? Has this changed across kernel versions?
Historically, mlockall() required CAP_IPC_LOCK (root-equivalent capability). Since Linux 2.6.9, unprivileged processes can call mlockall() but are still subject to RLIMIT_MEMLOCK. CAP_IPC_LOCK bypasses RLIMIT_MEMLOCK and allows locking unlimited memory. Interestingly, before Linux 2.6.9, munlockall() also required CAP_IPC_LOCK (even though munlock() did not) — this inconsistency was corrected in 2.6.9.
Q7: Name a real-world scenario where mlockall(MCL_CURRENT | MCL_FUTURE) is used.
Real-time audio servers like JACK Audio Connection Kit call mlockall(MCL_CURRENT | MCL_FUTURE) at startup along with pre-faulting the stack. This guarantees that the audio callback thread never experiences a page fault — which would cause an audible glitch. Similarly, industrial robot controllers and automotive ECU software running on Linux RT-PREEMPT use this pattern to meet hard real-time timing requirements.
