Skip to main content
Bytes & Beyond

Process Fundamentals

Understanding what a process is, program vs process, and process memory layout

Process Fundamentals

A process is a running instance of a program along with all the resources (memory, CPU state, file descriptors, etc.) allocated to it by the operating system. It’s the unit of resource allocation.

Program

  • Set of binary instructions stored on disk
  • Just bytes in a file
  • Examples: /bin/ls, a.out, chrome.exe

Process

  • Running instance of a program
  • Allocated memory, CPU state, file descriptors
  • A single program can have multiple processes running simultaneously

Example: Opening Chrome 5 times creates 5 separate processes, each with its own memory space and resources.

Process Memory Layout

Every process has a virtual memory address space divided into segments:

Process memory divided into segments: Text (code), Data, BSS, Heap, Stack, and Kernel space at the top.

High Address (Kernel Space)
└─────────────────┘
│     Kernel      │  ← Reserved for OS
└─────────────────┘
                       
┌─────────────────┐
│      Stack      │  ← Local variables, function calls (grows down ⬇)
├─────────────────┤
│       ↓         │
│   (free space)  │
│       ↑         │
├─────────────────┤
│      Heap       │  ← Dynamic memory (malloc, new) (grows up ⬆)
├─────────────────┤
│   Data Segment  │  ← Global variables, static data (initialized)
├─────────────────┤
│   BSS Segment   │  ← Uninitialized global variables (size 0)
├─────────────────┤
│   Text Segment  │  ← Program code (read-only, shared)
└─────────────────┘
Low Address (User Space)

Text Segment (Code)

  • Loaded Compiled program instructions from disk into ram.
  • Read-only (prevents accidental modification)
  • The only part shared among running processes of a program

Data Segment

  • Global and static variables that are initialized
  • Example: int x = 10; (global)

BSS Segment

  • Uninitialized global and static variables
  • Takes no space in the executable file
  • Zeroed out by the OS at runtime
  • Example: int x; (global, no initialization)

Heap

  • Dynamically allocated memory at runtime.
  • Grows upward (toward high addresses)
  • Managed by manually from code by malloc(), new, free() etc.

Stack

  • Managed automatically by the CPU using the Stack Pointer register. When a function calls another, it pushes a frame onto the stack. When it returns, it pops that frame off.
  • A stack frame contains local variables, function parameters and return addresses.
  • Grows downward (toward low addresses)
  • Each thread has its own stack

C Example: All Memory Segments


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

// --- DATA SEGMENT (Initialized Globals) ---
int global_init_var = 100; 

// --- BSS SEGMENT (Uninitialized Globals) ---
// Automatically set to 0 by the OS before main() starts.
int global_uninit_var;     

void investigate_stack(int recursion_depth) {
    int stack_var = 42;
    printf("Stack Frame %d Address: %p\n", recursion_depth, (void*)&stack_var);
    
    if (recursion_depth < 3) {
        investigate_stack(recursion_depth + 1);
    }
}

int main() {
    // 1. Text Segment (Code)
    printf("=== TEXT SEGMENT (Low Addresses / Read-Only) ===\n");
    printf("Address of main():    %p\n", (void*)&main);
    printf("Address of function:  %p\n\n", (void*)&investigate_stack);

    // 2. Data & BSS Segments (Static Memory)
    printf("=== DATA & BSS SEGMENTS (Fixed Size) ===\n");
    printf("Data (Init Global):   %p  (Value: %d)\n", (void*)&global_init_var, global_init_var);
    printf("BSS (Uninit Global):  %p  (Value: %d)\n\n", (void*)&global_uninit_var, global_uninit_var);

    // 3. The Heap
    int *heap_var1 = (int*)malloc(sizeof(int));
    int *heap_var2 = (int*)malloc(sizeof(int));

    printf("=== HEAP (Grows Up ⬆) ===\n");
    printf("Heap Var 1 Address:   %p\n", (void*)heap_var1);
    printf("Heap Var 2 Address:   %p\n", (void*)heap_var2);
    printf("Difference:           %ld bytes\n\n", (long)((char*)heap_var2 - (char*)heap_var1));

    // 4. The Stack
    printf("=== STACK (Grows Down ⬇) ===\n");
    investigate_stack(1);

    // 5. Kernel Space (Conceptual)
    printf("\n=== KERNEL SPACE (High Addresses) ===\n");
    printf("Any address significantly higher than the Stack is typically Kernel space.\n");
    printf("Trying to access random high addresses (e.g., 0xffffffffffffffff) causes a Segfault.\n");

    free(heap_var1);
    free(heap_var2);
    return 0;
}

When you run the C example above, the output looks like:

=== TEXT SEGMENT (Low Addresses / Read-Only) ===
Address of main():    0x102dac5b0
Address of function:  0x102dac548

=== DATA & BSS SEGMENTS (Fixed Size) ===
Data (Init Global):   0x102db4000  (Value: 100)
BSS (Uninit Global):  0x102db4004  (Value: 0)

=== HEAP (Grows Up ⬆) ===
Heap Var 1 Address:   0x128605e70
Heap Var 2 Address:   0x128605fe0
Difference:           368 bytes

=== STACK (Grows Down ⬇) ===
Stack Frame 1 Address: 0x16d052d28
Stack Frame 2 Address: 0x16d052cf8
Stack Frame 3 Address: 0x16d052cc8

Analysis of All Memory Segments

From the output, we can analyze the actual memory layout and understand how each segment behaves:

Text Segment (Code)

Address of main():    0x102dac5b0
Address of function:  0x102dac548
  • Text segment occupies the lowest address range
  • Both function addresses are close to each other (difference: 0x102dac5b0 - 0x102dac548 = 0x68 = 104 bytes)
  • All code is loaded into RAM at startup

Data Segment (Initialized Globals)

Data (Init Global):   0x102db4000  (Value: 100)
  • Located just above the Text segment
  • Holds global_init_var = 100 (hardcoded in the executable file)
  • Fixed size (known at compile time)
  • Persists for the entire program lifetime
  • Much higher than Text: 0x102db4000 - 0x102dac5b0 = 0x7a50 = 31312 bytes

BSS Segment (Uninitialized Globals)

BSS (Uninit Global):  0x102db4004  (Value: 0)
  • Located immediately after the Data segment
  • Holds global_uninit_var (automatic initialization to 0)
  • Takes no space in the executable file (only metadata)
  • Very close to Data segment: 0x102db4004 - 0x102db4000 = 4 bytes
  • Zeroed out by the OS before main() starts

Heap (Dynamic Memory)

Heap Var 1 Address:   0x128605e70
Heap Var 2 Address:   0x128605fe0
Difference:           368 bytes
  • Located much higher than Data/BSS segments
  • Grows upward (toward higher addresses) as allocations increase
  • First allocation: 0x128605e70
  • Second allocation: 0x128605fe0 (higher address than first)
  • Addresses are unpredictable (depends on allocator, OS, and other processes)
  • Gap from BSS to Heap: 0x128605e70 - 0x102db4004 = 0x25851e6c ≈ 600 MB (huge!)

Stack (Local Variables & Function Frames)

Stack Frame 1 Address: 0x16d052d28 (main)
Stack Frame 2 Address: 0x16d052cf8 (investigate_stack(1))
Stack Frame 3 Address: 0x16d052cc8 (investigate_stack(2))
  • Located much higher than heap
  • Grows downward (toward lower addresses) as functions call other functions
  • Frame 1 (main): 0x16d052d28 (highest, pushed first)
  • Frame 2 (investigate_stack(1)): 0x16d052cf8 (48 bytes lower: 0x30)
  • Frame 3 (investigate_stack(2)): 0x16d052cc8 (48 bytes lower: 0x30)
  • Each recursive call creates a new frame at a lower address
  • Stack frames are managed automatically by the CPU’s Stack Pointer register

Kernel Space (OS Reserved)

  • Located above the stack
  • Any address beyond the stack is typically kernel space
  • Accessing kernel space from user code causes a Segmentation Fault

Stack Frame Structure

1. The main Stack Frame (Top / Highest Address: 0x16d052d28)

  • Local variables: int *heap_var1, int *heap_var2 (pointers on stack → point to heap)
  • Space for function arguments (if any)
  • Return address to caller

2. The investigate_stack(1) Frame (Address: 0x16d052cf8, -48 bytes)

  • Function argument: recursion_depth = 1
  • Local variable: stack_var = 42
  • Return address: Instruction to return to inside main()

3. The investigate_stack(2) Frame (Address: 0x16d052cc8, -96 bytes from main)

  • Function argument: recursion_depth = 2
  • Local variable: stack_var = 42
  • Return address: Instruction to return to inside investigate_stack(1)

4. The investigate_stack(3) Frame (Lowest Address)

  • Function argument: recursion_depth = 3
  • Same structure, doesn’t recurse further