Memory Segments After fork()

Memory Segments After fork()
Topic 2 → Subtopic 3  |  Stack, data, heap & text segments after process creation
Topic 2
Subtopic 3
4
Segments
3
Code Examples

Independent Copies of Memory

After fork(), the parent and child appear to have identical memory. But they are independent copies. A change made by the child does not affect the parent’s memory, and vice versa. This section shows exactly which segments are involved, with code to prove it.

Keywords:

text segment data segment BSS segment heap segment stack segment global variables local variables malloc() independent copies

📄 Memory Layout Before and After fork()

Before fork()
Stack
local vars, return addr
↑ grows up
Heap
malloc() memory
BSS
uninit globals = 0
Data
init global/static vars
Text
program code (R/O)
Parent Process

fork() →

Parent (after fork)
Stack (own copy)
Heap (own copy)
BSS (own copy)
Data (own copy)
Text 🔂 SHARED

Child (after fork)
Stack (own copy)
Heap (own copy)
BSS (own copy)
Data (own copy)
Text 🔂 SHARED

“Own copy” = copy-on-write until first write. Text segment = truly shared, never copied.

📚 What Lives in Each Segment
Segment Contents C Example After fork()
Text Compiled machine code main(){...} Shared (read-only)
Data (init) Initialized globals & statics int x = 5; Independent copy
BSS (uninit) Uninit globals & statics (=0) static int y; Independent copy
Heap Dynamic memory malloc(100) Independent copy
Stack Local vars, function frames int n = 3; Independent copy

💻 Code Example 1: Classic t_fork.c (from TLPI Chapter 24)

Directly from the textbook: child modifies global (data segment) and local (stack) variables:

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

/* Allocated in data segment */
static int idata = 111;

int main(void)
{
    /* Allocated in stack segment */
    int istack = 222;
    pid_t childPid;

    switch (childPid = fork()) {

    case -1:
        perror("fork"); exit(EXIT_FAILURE);

    case 0:
        /* CHILD: multiply both by 3 */
        idata  *= 3;   /* 111 * 3 = 333 */
        istack *= 3;   /* 222 * 3 = 666 */
        break;

    default:
        /* PARENT: sleep to let child run first */
        sleep(3);
        break;
    }

    /* Both parent and child reach here */
    printf("PID=%ld %s idata=%d istack=%d\n",
           (long)getpid(),
           (childPid == 0) ? "(child) " : "(parent)",
           idata, istack);

    exit(EXIT_SUCCESS);
}
Expected output:
PID=1235 (child) idata=333 istack=666
PID=1234 (parent) idata=111 istack=222
Child’s copies changed; parent’s originals untouched.

💻 Code Example 2: Heap (malloc) Independence After fork()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    /* Allocate heap memory BEFORE fork */
    char *buf = malloc(64);
    if (!buf) { perror("malloc"); exit(1); }
    strcpy(buf, "original data");

    printf("[Before fork] buf = \"%s\" at %p\n", buf, (void*)buf);

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

    if (pid == 0) {
        /* CHILD: modify heap memory */
        strcpy(buf, "child modified");
        printf("[Child  PID=%d] buf = \"%s\" at %p\n",
               getpid(), buf, (void*)buf);
        free(buf);
        _exit(0);
    }

    /* PARENT: sleep, then check its own copy */
    sleep(1);
    printf("[Parent PID=%d] buf = \"%s\" at %p\n",
           getpid(), buf, (void*)buf);

    /* Parent's buf is STILL "original data" */
    free(buf);
    wait(NULL);
    return 0;
}
Key point: Both pointers show the same virtual address, but they map to different physical pages after copy-on-write. The parent sees “original data” while the child sees “child modified”.

💻 Code Example 3: Viewing Process Memory with /proc
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

/* Print memory info from /proc/self/status */
void print_memory_info(const char *label)
{
    char path[64], line[256];
    FILE *f;

    snprintf(path, sizeof(path), "/proc/%d/status", getpid());
    f = fopen(path, "r");
    if (!f) return;

    printf("\n--- Memory for %s (PID=%d) ---\n", label, getpid());
    while (fgets(line, sizeof(line), f)) {
        /* Print VmRSS (Resident Set Size) and VmSize */
        if (strncmp(line, "VmRSS", 5) == 0 ||
            strncmp(line, "VmSize", 6) == 0 ||
            strncmp(line, "VmData", 6) == 0 ||
            strncmp(line, "VmStk", 5) == 0)
            printf("  %s", line);
    }
    fclose(f);
}

/* Note: need #include <string.h> for strncmp */
#include <string.h>

int main(void)
{
    /* Allocate 10 MB before fork */
    char *big = malloc(10 * 1024 * 1024);
    if (big) big[0] = 'A';   /* touch it to force allocation */

    print_memory_info("Parent before fork");

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

    if (pid == 0) {
        print_memory_info("Child after fork");
        /* Modify memory: triggers copy-on-write pages */
        for (int i = 0; i < 10*1024*1024; i++) big[i] = 'B';
        print_memory_info("Child after modifying memory");
        free(big);
        _exit(0);
    }

    wait(NULL);
    print_memory_info("Parent after child exits");
    free(big);
    return 0;
}
Try it: The child’s VmRSS (physical memory) grows after modifying the buffer because copy-on-write kicks in. The parent’s VmRSS stays the same. Compile: gcc -o mem_demo mem_demo.c

🅾 Interview Questions
Q1: If a child modifies a global variable after fork(), does the parent see the change?

No. After fork(), each process has its own independent copy of the data segment (due to copy-on-write). The child’s modification of the global variable creates a private copy for the child. The parent’s copy remains unchanged.

Q2: Do the parent and child share the text (code) segment after fork()?

Yes. The text segment is read-only (program code doesn’t change at runtime). The kernel marks it read-only and both processes share the same physical pages. No copy is ever needed. This saves significant memory.

Q3: What happens to heap memory allocated before fork()?

The child gets a copy-on-write copy of the parent’s heap. The virtual addresses look the same in both processes, but they point to different physical pages once either process modifies the memory. Both processes should independently free their copies (the child should free in the child, parent in the parent).

Q4: What is the BSS segment?

BSS (Block Started by Symbol) holds uninitialized global and static variables. They are automatically zero-initialized by the OS. In the binary file, BSS takes no space — only its size is stored. After fork(), child gets an independent copy just like the data segment.

Q5: Can you use /proc to inspect a process’s memory layout?

Yes. /proc/<pid>/maps shows all memory mappings with addresses and permissions. /proc/<pid>/status shows VmSize (virtual), VmRSS (physical resident), VmData (data), VmStk (stack). /proc/self/ refers to the current process.

Series Navigation
Topic 2 → Subtopic 3 of 4

← Previous Next: Memory Footprint Control → Index

Leave a Reply

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