Memory Footprint: CoW in Practice

Memory Footprint: CoW in Practice
Topic 4 → Subtopic 3  |  Kernel-level view of memory cost after fork()
Topic 4
Subtopic 3
VSZ vs RSS
Explained
3
Code Examples

How Much Memory Does a Forked Process Actually Use?

After fork(), a child process may have a large virtual address space (VSZ) because it appears to have all the parent’s memory. But its actual physical memory usage (RSS) is very small initially, because CoW pages haven’t been copied yet. Understanding VSZ vs RSS is key to diagnosing memory usage in multi-process programs.

Keywords:

VSZ (virtual size) RSS (resident set size) overcommit OOM killer /proc/PID/status VmSize VmRSS shared pages

📈 VSZ vs RSS After fork()
VSZ — Virtual Size
Total virtual address space mapped
Includes CoW pages not yet copied
Includes unmapped gaps
Immediately = parent’s VSZ
Does NOT reflect real RAM usage
Seen in: ps -o vsz, VmSize
RSS — Resident Set Size
Pages actually in physical RAM
Starts very small right after fork
Grows as CoW pages are written
True physical memory cost
Includes shared pages (overcounts)
Seen in: ps -o rss, VmRSS
Event Parent VSZ Parent RSS Child VSZ Child RSS
Before fork() 100 MB 80 MB
After fork(), no writes 100 MB ~80 MB 100 MB ~few KB
Child writes 40 MB 100 MB ~80 MB 100 MB ~40 MB

💻 Code Example 1: Measuring VSZ and RSS Before and After fork()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

void print_vmstats(const char *label)
{
    FILE *f = fopen("/proc/self/status", "r");
    if (!f) return;
    char line[128];
    long vmsize = 0, vmrss = 0;
    while (fgets(line, sizeof(line), f)) {
        if (strncmp(line, "VmSize:", 7) == 0) sscanf(line+7, "%ld", &vmsize);
        if (strncmp(line, "VmRSS:",  6) == 0) sscanf(line+6, "%ld", &vmrss);
    }
    fclose(f);
    printf("%-42s VSZ=%6ld KB  RSS=%6ld KB\n", label, vmsize, vmrss);
}

int main(void)
{
    /* Allocate and touch 50 MB */
    char *buf = malloc(50 * 1024 * 1024);
    memset(buf, 'A', 50 * 1024 * 1024);

    print_vmstats("[Parent before fork]");

    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        print_vmstats("[Child  after fork, no write]");

        /* Write to 25 MB: triggers CoW for those pages */
        memset(buf, 'B', 25 * 1024 * 1024);

        print_vmstats("[Child  after writing 25 MB]");
        free(buf);
        _exit(0);
    }

    wait(NULL);
    print_vmstats("[Parent after child exits]");
    free(buf);
    return 0;
}
/* Compile: gcc -o vmstats vmstats.c */
Expected pattern:
Child right after fork: large VSZ (same as parent), tiny RSS (almost no CoW yet)
Child after writing 25 MB: VSZ same, RSS grows by ~25 MB (CoW pages now private)

💻 Code Example 2: Linux Memory Overcommit

Because CoW makes fork() cheap, Linux allows overcommit — allocating more virtual memory than physical RAM + swap. This normally works fine:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    /* Check the overcommit setting */
    FILE *f = fopen("/proc/sys/vm/overcommit_memory", "r");
    if (f) {
        int setting;
        fscanf(f, "%d", &setting);
        fclose(f);
        printf("overcommit_memory = %d\n", setting);
        printf("  0 = heuristic (default): allow reasonable overcommit\n");
        printf("  1 = always allow: malloc always succeeds\n");
        printf("  2 = strict: never overcommit beyond limit\n\n");
    }

    /* With default (0), this large allocation succeeds */
    /* even if physical RAM is less than 4 GB           */
    size_t size = (size_t)4 * 1024 * 1024 * 1024;  /* 4 GB */
    char *p = malloc(size);

    if (p) {
        printf("malloc(4 GB) succeeded (VSZ only, not physical RAM)\n");
        printf("No physical pages allocated yet (CoW/lazy)\n");

        /* Only NOW does physical memory get used */
        printf("Writing 1 MB to force actual allocation...\n");
        memset(p, 0, 1024 * 1024);
        printf("1 MB written. 1 MB of RAM used.\n");

        free(p);
    } else {
        printf("malloc(4 GB) failed (overcommit disabled or no VM)\n");
    }
    return 0;
}
Real implication for fork(): Even if a process has 1 GB of virtual memory, fork() is fast because no physical pages are immediately copied. Physical pages are only allocated as the child actually writes to them.

💻 Code Example 3: Minimizing Footprint Before fork()

Practical advice: free large, temporary allocations before forking to reduce the child’s VSZ and potential CoW cost:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

void print_vsz(const char *msg)
{
    FILE *f = fopen("/proc/self/status", "r");
    char line[128]; long v = 0;
    while (f && fgets(line, sizeof(line), f))
        if (strncmp(line, "VmSize:", 7) == 0) sscanf(line+7, "%ld", &v);
    if (f) fclose(f);
    printf("%-38s VmSize = %ld KB\n", msg, v);
}

int main(void)
{
    /* Load a large config/dataset into memory */
    char *big_config = malloc(100 * 1024 * 1024);  /* 100 MB */
    memset(big_config, 'C', 100 * 1024 * 1024);

    print_vsz("[After loading 100MB config]");

    /* Extract only what you need before fork */
    int important_value = big_config[0];  /* e.g. first byte */

    /* Free the large allocation BEFORE fork */
    free(big_config);
    big_config = NULL;

    print_vsz("[After free, before fork]");

    /* Now fork: child has much smaller footprint */
    pid_t pid = fork();
    if (pid == -1) { perror("fork"); exit(1); }

    if (pid == 0) {
        print_vsz("[Child after fork (lean)]");
        /* Child does its work with smaller footprint */
        printf("[Child] important_value = %d\n", important_value);
        _exit(0);
    }

    wait(NULL);
    return 0;
}

🅾 Interview Questions
Q1: What is the difference between VSZ and RSS?

VSZ (virtual size) is the total virtual address space mapped by a process, including pages not yet in physical RAM. RSS (resident set size) is the physical RAM actually used. After fork(), a child’s VSZ equals the parent’s, but its RSS is small because CoW pages haven’t been copied to physical memory yet.

Q2: What is Linux memory overcommit and how does it relate to fork()?

Linux allows allocating more virtual memory than physical RAM (overcommit) because CoW means virtual memory doesn’t immediately cost physical RAM. After fork(), both parent and child have large VSZ but share physical pages. Pages are only physically allocated when written. The OOM killer handles cases where actual usage exceeds RAM.

Q3: How can you reduce the memory footprint of a forked child?

Free large, no-longer-needed allocations (config data, caches, buffers) in the parent before calling fork(). This reduces the child’s VSZ and the potential CoW cost. If the child will call exec(), this is less important (exec discards all memory). For long-running children, freeing before fork is good practice.

Series Navigation
Topic 4 → Subtopic 3 of 3  |  Next: Topic 5 → vfork()

← Previous Next: vfork() Introduction → Index

Leave a Reply

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