How does Reading Process Memory Maps work in Linux-Free Embedded Linux Course

PREV_LEC  |  NEXT_LEC
Free Embedded Linux Course · Chapter 11

Reading Process Memory Maps

Lecture 4 of the Managing Memory chapter — decoding /proc/PID/maps to see exactly what a running process’s address space is made of.

Topics Covered
/proc/PID/maps VMA permissions heap and stack limits ulimit free embedded linux course free linux kernel development course

What You Will Learn

This lecture is part of EmbeddedPathashala’s free embedded linux course and closes out the hands-on portion of the Managing Memory chapter in our free linux kernel development course. You will learn how to read every column of /proc/PID/maps, what the [heap] and [stack] regions actually represent, how ulimit governs their maximum size, and you’ll build a small original program that watches a brand-new memory mapping appear in its own map in real time.

Prerequisites

This lecture assumes you’ve completed User Space Memory Layout earlier in this chapter, and that you’re comfortable with the idea of a virtual memory area (VMA) as a contiguous range of a process’s address space.

What Is /proc/PID/maps

Every running process on Linux exposes its own virtual memory area list through the proc filesystem, at /proc/PID/maps (or /proc/self/maps for the calling process itself). Each line describes one contiguous VMA — a mapped range of virtual addresses along with the permissions and backing object for that range. This file is one of the most useful debugging tools available on a live embedded target, because it requires no special build flags or symbols to inspect.

$ cat /proc/self/maps
55a1e2f9a000-55a1e2f9b000 r-xp 00000000 08:01 1181231  /usr/bin/cat
55a1e319b000-55a1e319c000 r--p 00000000 08:01 1181231  /usr/bin/cat
55a1e319c000-55a1e319d000 rw-p 00001000 08:01 1181231  /usr/bin/cat
55a1e41c2000-55a1e41e3000 rw-p 00000000 00:00 0        [heap]
7f2c1a000000-7f2c1a022000 r-xp 00000000 08:01 2103005  /usr/lib/x86_64-linux-gnu/libc.so.6
7ffe9b3a1000-7ffe9b3c2000 rw-p 00000000 00:00 0        [stack]
7ffe9b3f0000-7ffe9b3f4000 r--p 00000000 00:00 0        [vvar]
7ffe9b3f4000-7ffe9b3f6000 r-xp 00000000 00:00 0        [vdso]

Reading Each Column

ColumnMeaning
address rangeStart and end virtual address of this VMA
permissionsread, write, execute, and shared or private (copy-on-write)
offsetOffset into the backing file where this mapping starts, zero for anonymous memory
deviceMajor:minor of the block device holding the backing file
inodeInode number of the backing file, zero for anonymous memory
pathnameBacking file path, or a bracketed pseudo-name such as [heap], [stack], [vdso]

A single executable’s mappings are often split into two or three separate VMAs even though they come from the same file — one read-execute VMA for the code segment and one or two read-write VMAs for initialized and read-only data. This is the kernel enforcing the principle of least privilege at the page-table level: code pages should never be writable, and data pages should never be executable.

Typical Process Address Space, Low to High
program text (r-x) program data (rw-) [heap] grows up shared libraries [stack] grows down kernel space

Where the Heap and Stack Live

The [heap] entry is where malloc(3) satisfies most allocation requests — it grows upward from just after the process’s data segment as the allocator needs more space, using the brk()/sbrk() system calls under the hood for smaller requests. Very large allocations are instead served directly by mmap() as their own separate anonymous VMA, which is why a program that makes one huge malloc() call sometimes shows an extra anonymous mapping far away from [heap] rather than a bigger heap segment.

The [stack] entry holds the calling thread’s stack and grows downward automatically as function calls push new frames — the kernel extends this VMA on demand as long as the access stays within the configured limit.

Heap and Stack Limits via ulimit

Both regions are bounded by per-process resource limits, inspectable and (within limits) adjustable with the shell’s ulimit builtin:

RegionControlling LimitTypical Default
heapulimit -d (data segment size)unlimited on most distributions
stackulimit -s (stack size)8 MiB on most distributions, often much smaller on embedded targets
$ ulimit -s
8192
$ ulimit -Ss 512      # lower this shell's stack limit to 512 KiB

An allocation attempt that would exceed either limit is rejected with SIGSEGV rather than silently succeeding — this is a common source of hard-to-diagnose crashes on embedded targets, where the default stack limit is frequently tuned much smaller than a desktop distribution’s default to conserve RAM.

Hands-On: Watching a Mapping Appear Live

The original demo below, ep_mapwatch.c, prints its own /proc/self/maps, creates a fresh anonymous mapping, then prints the map again so you can see the new line appear.

// ep_mapwatch.c — original demo, EmbeddedPathashala
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

static void dump_maps(const char *label)
{
    FILE *f;
    char line[256];

    printf("----- %s -----\n", label);
    f = fopen("/proc/self/maps", "r");
    if (!f) {
        perror("fopen");
        return;
    }
    while (fgets(line, sizeof(line), f))
        fputs(line, stdout);
    fclose(f);
}

int main(void)
{
    void *region;

    dump_maps("before mmap()");

    region = mmap(NULL, 4096 * 4, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (region == MAP_FAILED) {
        perror("mmap");
        return 1;
    }
    printf("\nnew mapping placed at %p\n\n", region);

    dump_maps("after mmap()");

    munmap(region, 4096 * 4);
    return 0;
}
$ gcc -Wall -o ep_mapwatch ep_mapwatch.c
$ ./ep_mapwatch | tail -8
new mapping placed at 0x7f9a1c3ba000

----- after mmap() -----
...
7f9a1c3ba000-7f9a1c3bb000 rw-p 00000000 00:00 0
7f9a1c3bb000-7f9a1c3bd000 rw-p 00000000 00:00 0
...

The freshly mapped 16 KiB region shows up as a new anonymous rw-p VMA with device 00:00 and inode 0 — exactly the signature of memory with no backing file, sitting at the address the kernel chose for it.

Why Embedded Developers Should Care

On a target device with no debugger attached and limited storage for core dumps, /proc/PID/maps (paired with /proc/PID/smaps for per-region memory accounting) is often the fastest way to confirm whether a shared library actually got mapped, whether a suspicious anonymous region is growing between samples — a classic sign of a memory leak — or whether a crash address falls inside a valid mapping at all.

Common Mistakes and Troubleshooting

  • Assuming every VMA with a file path has that entire file resident in RAM — only the pages actually touched are backed by real memory, per the previous lecture on lazy allocation.
  • Forgetting that [heap] only reflects brk()-based allocations — large malloc() requests served via mmap() show up as separate anonymous VMAs instead.
  • Hard-coding an assumed stack limit in embedded code instead of checking ulimit -s at startup, which breaks the moment the target’s default configuration changes.

Best Practices

  • Use /proc/PID/smaps rather than plain maps when you need actual resident memory (RSS) per region, not just the VMA boundaries.
  • Sample /proc/PID/maps periodically during a soak test to spot anonymous regions that keep growing and never shrink — a strong leak indicator.
  • On memory-constrained targets, set an explicit, deliberately small stack limit for threads that don’t need a large one, rather than inheriting a generous default.

Interview Questions

  • What do the device and inode columns in /proc/PID/maps tell you about a mapping?
  • Why can a single executable file appear as more than one VMA in the same process?
  • What happens when a stack allocation would exceed the limit set by ulimit -s?

Summary and Key Takeaways

/proc/PID/maps gives you a direct, no-tooling-required view into exactly how a process’s virtual address space is laid out — code, data, heap, shared libraries, and stack, each as one or more VMAs with their own permissions and backing object. The heap grows upward and the stack grows downward, both bounded by ulimit limits that a violating access turns into a SIGSEGV rather than silent success. The ep_mapwatch demo showed this concretely: a fresh mmap() call is immediately visible as a new line in the map, with the telltale 00:00 device and zero inode marking it as anonymous. That closes out the hands-on core of this free embedded linux course chapter on managing memory.

FAQ

What’s the difference between /proc/PID/maps and /proc/PID/smaps?

maps lists each VMA’s address range, permissions, and backing file; smaps adds detailed per-region statistics like actual resident memory (RSS) and proportional share, which maps alone does not provide.

Why does device 00:00 and inode 0 appear for some entries?

Those are anonymous mappings with no backing file — heap, stack, and memory obtained via mmap() with MAP_ANONYMOUS all show this signature.

Can a process read another process’s /proc/PID/maps?

Only if it has permission to do so, generally the same user or root — Linux restricts access to another process’s memory map for security reasons.

Why does one shared library show up as multiple map lines?

The kernel maps the executable and read-only segments separately from the writable data segment so it can enforce different permissions on each, which is why one file often produces two or three VMAs.

What happens if the stack grows past its ulimit -s limit?

The kernel refuses to extend the stack VMA further and the process receives a SIGSEGV, which by default terminates it.

Is a growing anonymous VMA always a memory leak?

Not always, but a steadily growing anonymous region that never shrinks across repeated samples during a soak test is one of the strongest practical signs of a leak.

Is this course free?

Yes — this lecture is part of EmbeddedPathashala’s free embedded linux course covering the full path from toolchains and bootloaders through kernel internals and device drivers.

{ “@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [ {“@type”: “Question”, “name”: “What’s the difference between /proc/PID/maps and /proc/PID/smaps?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “maps lists each VMA’s address range, permissions, and backing file; smaps adds detailed per-region statistics like actual resident memory and proportional share.”}}, {“@type”: “Question”, “name”: “Why does device 00:00 and inode 0 appear for some entries?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Those are anonymous mappings with no backing file — heap, stack, and mmap MAP_ANONYMOUS regions all show this signature.”}}, {“@type”: “Question”, “name”: “Can a process read another process’s /proc/PID/maps?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Only if it has permission to do so, generally the same user or root — Linux restricts access for security reasons.”}}, {“@type”: “Question”, “name”: “Why does one shared library show up as multiple map lines?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “The kernel maps executable and read-only segments separately from the writable data segment to enforce different permissions on each.”}}, {“@type”: “Question”, “name”: “What happens if the stack grows past its ulimit -s limit?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “The kernel refuses to extend the stack VMA further and the process receives a SIGSEGV, which by default terminates it.”}}, {“@type”: “Question”, “name”: “Is a growing anonymous VMA always a memory leak?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Not always, but a steadily growing anonymous region that never shrinks across repeated samples during a soak test is a strong practical sign of a leak.”}}, {“@type”: “Question”, “name”: “Is this course free?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes — this lecture is part of EmbeddedPathashala’s free embedded linux course covering the full path from toolchains and bootloaders through kernel internals and device drivers.”}} ] }

Continue Your Free Embedded Linux Course

Next up: the slab allocator and vmalloc in more depth, and how kernel drivers request memory safely.

PREV_LEC  |  NEXT_LEC

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *