What is mincore()?
The Linux kernel maintains physical memory (RAM) for active pages and may move less-used pages to disk (swap). At any moment, some of your mapped pages are resident (in RAM) and some are not resident (swapped out or never loaded yet).
mincore() lets you ask the kernel: “For each page in this virtual address range, tell me whether it is currently in physical memory.” It returns a byte array — one byte per page — where the least significant bit tells you the answer.
This is useful for performance analysis, deciding whether to pre-load data before a time-critical section, and verifying that mlock() worked correctly.
Function Signature
#define _BSD_SOURCE /* or _GNU_SOURCE */
#include <sys/mman.h>
int mincore(void *addr, size_t length, unsigned char *vec);
/* Returns: 0 on success, -1 on error */
| Parameter | Description |
|---|---|
addr |
Start of region — must be page-aligned |
length |
Length in bytes. Number of pages covered = ceil(length / pagesize) |
vec |
Output array. Must have at least ceil(length/pagesize) bytes. One byte per page. |
Understanding the vec[] Output Array
| vec[0] | vec[1] | vec[2] | vec[3] | vec[4] | vec[5] | vec[6] | vec[7] |
|---|---|---|---|---|---|---|---|
| 1 ✅ | 1 ✅ | 1 ✅ | 0 . | 0 . | 0 . | 0 . | 0 . |
| Pages in RAM (bit 0 = 1) | Pages NOT in RAM (bit 0 = 0) | ||||||
The kernel sets bit 0 of each vec[i] byte. Check residency with: (vec[i] & 1). Other bits are reserved and should be ignored.
(length + pagesize - 1) / pagesize bytes for vec. If vec is too small, the kernel writes beyond your allocation — a classic buffer overflow. Always compute the size correctly.Example 1: Basic Page Residency Check
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
long pagesize = sysconf(_SC_PAGESIZE);
int num_pages = 8;
size_t len = num_pages * pagesize;
/* Allocate 8 pages */
char *addr = mmap(NULL, len,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
/* Allocate vec: one byte per page */
unsigned char *vec = malloc(num_pages);
if (!vec) { perror("malloc"); exit(1); }
/* Check residency BEFORE touching any pages */
if (mincore(addr, len, vec) == -1) { perror("mincore"); exit(1); }
printf("Before touching pages:\n%p: ", addr);
for (int i = 0; i < num_pages; i++)
printf("%c", (vec[i] & 1) ? '*' : '.');
printf("\n");
/* Expected: all dots (not resident yet) */
/* Touch pages 0, 2, 4, 6 only */
for (int i = 0; i < num_pages; i += 2)
addr[i * pagesize] = 'X'; /* Touch first byte of every even page */
/* Check residency AFTER touching */
if (mincore(addr, len, vec) == -1) { perror("mincore"); exit(1); }
printf("After touching even pages:\n%p: ", addr);
for (int i = 0; i < num_pages; i++)
printf("%c", (vec[i] & 1) ? '*' : '.');
printf("\n");
/* Expected: *.*.*.*. (even pages resident, odd pages not) */
free(vec);
munmap(addr, len);
return 0;
}
Before touching pages:
0x7f…0000: ……..
After touching even pages:
0x7f…0000: *.*.*.*.
Example 2: Correct vec[] Size Calculation
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
/*
* Safe wrapper for mincore that allocates vec correctly.
* Returns allocated vec (caller must free), or NULL on error.
*/
unsigned char *check_residency(void *addr, size_t length, size_t *num_pages_out)
{
long pagesize = sysconf(_SC_PAGESIZE);
/* Number of pages covering [addr, addr+length) */
size_t num_pages = (length + pagesize - 1) / pagesize;
unsigned char *vec = malloc(num_pages);
if (!vec) {
perror("malloc vec");
return NULL;
}
if (mincore(addr, length, vec) == -1) {
perror("mincore");
free(vec);
return NULL;
}
if (num_pages_out) *num_pages_out = num_pages;
return vec;
}
/* Count how many pages are currently resident */
size_t count_resident_pages(unsigned char *vec, size_t num_pages)
{
size_t count = 0;
for (size_t i = 0; i < num_pages; i++)
if (vec[i] & 1) count++;
return count;
}
int main(void)
{
long pagesize = sysconf(_SC_PAGESIZE);
size_t len = 10 * pagesize; /* 10 pages */
char *buf = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED) { perror("mmap"); exit(1); }
/* Touch only the first 3 pages */
for (int i = 0; i < 3; i++)
buf[i * pagesize] = i;
size_t n;
unsigned char *vec = check_residency(buf, len, &n);
if (!vec) exit(1);
printf("Total pages: %zu\n", n);
printf("Resident pages: %zu\n", count_resident_pages(vec, n));
printf("Not resident: %zu\n", n - count_resident_pages(vec, n));
/* Print map */
printf("Map: ");
for (size_t i = 0; i < n; i++)
printf("%c", (vec[i] & 1) ? '*' : '.');
printf("\n");
free(vec);
munmap(buf, len);
return 0;
}
Example 3: Checking a File Mapping’s Residency
mincore() works on file mappings too, not just anonymous memory. This is very useful for large files — you can check which parts are in the page cache before deciding whether to read-ahead or lock them:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
exit(1);
}
long pagesize = sysconf(_SC_PAGESIZE);
/* Open file */
int fd = open(argv[1], O_RDONLY);
if (fd == -1) { perror("open"); exit(1); }
/* Get file size */
struct stat st;
if (fstat(fd, &st) == -1) { perror("fstat"); exit(1); }
size_t file_size = st.st_size;
if (file_size == 0) {
printf("File is empty\n");
close(fd);
return 0;
}
/* Map the entire file read-only */
void *map = mmap(NULL, file_size, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
/* Check residency */
size_t num_pages = (file_size + pagesize - 1) / pagesize;
unsigned char *vec = malloc(num_pages);
if (!vec) { perror("malloc"); exit(1); }
if (mincore(map, file_size, vec) == -1) { perror("mincore"); exit(1); }
/* Count and display */
size_t resident = 0;
for (size_t i = 0; i < num_pages; i++)
if (vec[i] & 1) resident++;
printf("File: %s\n", argv[1]);
printf("Size: %zu bytes\n", file_size);
printf("Pages: %zu total, %zu resident (%.1f%%)\n",
num_pages, resident,
100.0 * resident / num_pages);
/* Print first 64 page residency chars */
size_t show = (num_pages < 64) ? num_pages : 64;
printf("Map (first %zu pages): ", show);
for (size_t i = 0; i < show; i++)
printf("%c", (vec[i] & 1) ? '*' : '.');
printf("\n");
free(vec);
munmap(map, file_size);
return 0;
}
/*
* Compile: gcc -o fileresid fileresid.c
* Usage: ./fileresid /path/to/largefile
* Run twice: second time more pages may be resident (in page cache)
*/
Example 4: Verify mlock() Success with mincore()
After calling mlock(), you can use mincore() to verify that all pages are indeed resident. This is useful in system tests and startup diagnostics:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
int verify_all_locked(void *addr, size_t len)
{
long pagesize = sysconf(_SC_PAGESIZE);
size_t num_pages = (len + pagesize - 1) / pagesize;
unsigned char *vec = malloc(num_pages);
if (!vec) { perror("malloc"); return -1; }
if (mincore(addr, len, vec) == -1) {
perror("mincore");
free(vec);
return -1;
}
int all_resident = 1;
for (size_t i = 0; i < num_pages; i++) {
if (!(vec[i] & 1)) {
fprintf(stderr, "Page %zu is NOT resident after mlock!\n", i);
all_resident = 0;
}
}
free(vec);
return all_resident;
}
int main(void)
{
long pagesize = sysconf(_SC_PAGESIZE);
size_t len = 8 * pagesize;
char *buf = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (buf == MAP_FAILED) { perror("mmap"); exit(1); }
/* Lock memory */
if (mlock(buf, len) == -1) {
perror("mlock (try running as root)");
exit(1);
}
/* Verify all pages are resident */
if (verify_all_locked(buf, len))
printf("✅ All %zu pages are resident after mlock()\n",
len / pagesize);
else
printf("❌ Some pages are not resident — mlock may have failed\n");
munmap(buf, len);
return 0;
}
Common Errors
| errno | Cause |
|---|---|
EINVAL |
addr is not page-aligned, or length is 0 |
ENOMEM |
Address range contains unmapped pages, or vec buffer is not writable |
EFAULT |
vec points to invalid memory |
mincore() is Linux-specific (also available on BSD systems). It requires #define _BSD_SOURCE (or _GNU_SOURCE) before including <sys/mman.h>. It is not part of POSIX.Interview Questions & Answers
mincore(addr, length, vec) fills the vec array with one byte per page in the range. If bit 0 of vec[i] is 1, page i is currently resident in physical memory (RAM). If bit 0 is 0, the page is either swapped out or has never been accessed (not yet allocated physically by demand paging). The other bits in each byte are reserved and should be ignored.
The size of vec must be at least the number of pages covering [addr, addr+length). This is computed as: (length + pagesize - 1) / pagesize, which is integer ceiling division. For example, if length = 5000 and pagesize = 4096, you need 2 bytes in vec (covering page 0 and page 1). Undersizing vec causes a buffer overflow because the kernel writes one byte per page unconditionally.
Common uses include: (1) Performance profiling — checking what fraction of a large data set is in the page cache before deciding to read-ahead; (2) Real-time verification — confirming that mlock() successfully pinned all required pages before entering the RT loop; (3) Database/search engines — checking whether an index file’s pages are in cache to predict whether a query will be fast or will hit disk; (4) Checkpoint systems — identifying which pages have been modified (a page that is resident and dirty needs to be saved).
The kernel can reclaim pages at any time under memory pressure. If you write to a page, then the system comes under heavy RAM pressure, the kernel may swap that page out before you call mincore(). The result is non-deterministic in a running system. Also, for anonymous mappings with MAP_PRIVATE, pages are not physically allocated until first touched (demand paging), so an uninitialized page is not resident until you access it. This is why mlock() is needed to guarantee residency.
Yes — mincore() works on any mapped region of virtual memory, including the stack, BSS, data segment, and shared libraries. However, the address must be page-aligned. For stack variables, you typically need to round down to the page boundary using addr & ~(pagesize - 1). In practice, developers use it most often on large mmap()-ed regions where granular residency info is valuable.
Topic Summary
mincore(addr, len, vec)fillsvecwith one byte per page.- Bit 0 of
vec[i]= 1 means pageiis in RAM; 0 = not in RAM. addrmust be page-aligned;vecmust holdceil(len/pagesize)bytes.- Not POSIX — requires
_BSD_SOURCEor_GNU_SOURCE. - Use it to verify mlock() results, profile page cache hit rates, or guide pre-loading.
- The snapshot is instantaneous — pages can change residency immediately after the call.
