Subtopic 3
Explained
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.
| 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 |
#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 */
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)
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;
}
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;
}
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.
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.
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.
