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