Loading content...
Static allocation strategies—whether equal, proportional, priority-based, or combinations thereof—share a fundamental limitation: they assume memory needs are known in advance. In reality, memory requirements change continuously.
A database query might spike memory usage temporarily. A compiler might need more memory during certain compilation phases. A web server's memory footprint fluctuates with request load. Dynamic adjustment addresses this reality by continuously monitoring process behavior and adapting allocations in response.
Dynamic adjustment transforms static policies into living systems that respond to actual conditions rather than predicted ones.
By the end of this page, you will understand: monitoring techniques for detecting allocation mismatches, adjustment algorithms that respond to runtime behavior, feedback control systems for stable adaptation, and real-world implementations in Linux and Windows. You'll be able to design and analyze dynamically-adjusting allocation systems.
Static allocation assumes predictable memory behavior. Real workloads violate this assumption constantly:
Phase Behavior: Programs go through distinct phases with different memory requirements. An initialization phase might need 50MB, processing phase 500MB, and cleanup 20MB.
Load Variation: Server applications scale memory with request load. Peak hours require more memory than idle periods.
Data Dependency: Memory needs depend on input data. Processing a small file vs. a large file yields dramatically different requirements.
Temporal Patterns: Database caches grow over time. Garbage collectors temporarily double memory usage. Long-running processes accumulate state.
| Scenario | Static Allocation | Dynamic Adjustment |
|---|---|---|
| Process enters memory-intensive phase | Thrashes within fixed allocation | Detects need, increases allocation |
| Process becomes memory-idle | Holds unused frames | Reclaims frames for other processes |
| New process joins system | Fixed redistribution | Gradual adaptation based on actual usage |
| Memory pressure increases | Fixed proportions maintained | Reduces allocations for low-activity processes |
Dynamic adjustment requires continuous monitoring to detect when allocations should change. Key metrics include:
Page Fault Rate (PFR): The primary indicator of allocation inadequacy. High PFR suggests allocation is below working set.
Working Set Size (WSS): Estimated pages actively in use. Approximated via reference bit sampling.
Memory Pressure Indicators:
Process Activity:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
typedef struct { int pid; // Page fault metrics unsigned long page_faults; // Total faults unsigned long major_faults; // Disk I/O required unsigned long minor_faults; // In-memory resolution double fault_rate; // Faults per second // Working set estimation size_t resident_set; // Current RSS size_t working_set_estimate; // Estimated WSS size_t allocated_frames; // Current allocation // Activity indicators double cpu_percent; // CPU utilization double io_wait_percent; // I/O wait time // Derived metrics double allocation_ratio; // allocated / WSS bool is_thrashing; // High fault rate indicator} ProcessMonitor; // Update monitoring data for a processvoid update_monitor(ProcessMonitor* mon) { static unsigned long last_faults[MAX_PROCESSES]; static time_t last_time[MAX_PROCESSES]; // Calculate fault rate time_t now = time(NULL); unsigned long current_faults = get_process_faults(mon->pid); if (last_time[mon->pid] > 0) { double elapsed = difftime(now, last_time[mon->pid]); if (elapsed > 0) { mon->fault_rate = (current_faults - last_faults[mon->pid]) / elapsed; } } last_faults[mon->pid] = current_faults; last_time[mon->pid] = now; // Update working set estimate via reference bit sampling mon->working_set_estimate = estimate_working_set(mon->pid); mon->allocation_ratio = (double)mon->allocated_frames / mon->working_set_estimate; // Thrashing detection: high fault rate + low allocation ratio mon->is_thrashing = (mon->fault_rate > THRASH_THRESHOLD) && (mon->allocation_ratio < 1.0);} // Estimate working set using reference bit samplingsize_t estimate_working_set(int pid) { size_t referenced_pages = 0; PageTableEntry* pte = get_page_table(pid); size_t page_count = get_page_count(pid); for (size_t i = 0; i < page_count; i++) { if (pte[i].valid && pte[i].referenced) { referenced_pages++; pte[i].referenced = 0; // Clear for next interval } } return referenced_pages;}The Page Fault Frequency (PFF) algorithm is the classic approach to dynamic adjustment. It uses page fault rate as the control signal for allocation changes.
Principle: Maintain page fault rate within an acceptable range by adjusting allocation.
Upper Threshold: Indicates insufficient allocation. Process is page faulting too frequently and cannot make efficient progress.
Lower Threshold: Indicates potential over-allocation. Process has more frames than it actively uses.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
from dataclasses import dataclassfrom typing import Listimport time @dataclassclass PFFProcess: pid: int name: str allocated_frames: int min_frames: int max_frames: int # Monitoring state page_faults: int = 0 last_faults: int = 0 fault_rate: float = 0.0 class PFFController: """Page Fault Frequency dynamic adjustment controller.""" def __init__(self, upper_threshold: float, lower_threshold: float): self.upper = upper_threshold # Faults/sec above which to add frames self.lower = lower_threshold # Faults/sec below which to reclaim self.processes: List[PFFProcess] = [] self.free_pool: int = 0 self.interval: float = 1.0 # Seconds between adjustments def update_fault_rates(self): """Calculate current fault rates for all processes.""" for p in self.processes: current = get_page_fault_count(p.pid) p.fault_rate = (current - p.last_faults) / self.interval p.last_faults = current def adjust_allocations(self): """Perform PFF-based allocation adjustment.""" # Processes needing more frames starving = [p for p in self.processes if p.fault_rate > self.upper and p.allocated_frames < p.max_frames] # Processes with excess frames excess = [p for p in self.processes if p.fault_rate < self.lower and p.allocated_frames > p.min_frames] # First: reclaim from excess processes for p in excess: reclaimable = min( p.allocated_frames - p.min_frames, int(p.allocated_frames * 0.1) # Max 10% per interval ) p.allocated_frames -= reclaimable self.free_pool += reclaimable # Second: allocate to starving processes if starving and self.free_pool > 0: per_process = max(1, self.free_pool // len(starving)) for p in starving: grant = min(per_process, p.max_frames - p.allocated_frames) if grant > 0 and self.free_pool >= grant: p.allocated_frames += grant self.free_pool -= grant def run_control_loop(self): """Main PFF control loop.""" while True: time.sleep(self.interval) self.update_fault_rates() self.adjust_allocations() self.log_status()PFF has known limitations:
• Reactive, not predictive: Only responds after problems occur • Threshold selection: Wrong thresholds cause oscillation or sluggish response • Phase change lag: Takes time to detect and respond to sudden changes • No global optimization: Each process adjusted independently
An alternative to PFF is direct working set tracking. Rather than reacting to fault rates, track which pages are actually in use and adjust allocation to match.
Working Set Model:
The working set W(t, Δ) at time t with window Δ is the set of pages referenced in the interval [t-Δ, t].
Adjustment Policy:
Advantages over PFF:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#define WSS_WINDOW_INTERVALS 10 // Working set window#define WSS_BUFFER_PERCENT 20 // Extra allocation buffer typedef struct { int pid; size_t allocated; size_t wss_estimate; size_t target_allocation; // Reference bit history for WSS estimation unsigned char* ref_bits[WSS_WINDOW_INTERVALS]; int current_interval;} WSSProcess; // Update working set estimate using sliding windowvoid update_wss(WSSProcess* proc) { size_t page_count = get_virtual_page_count(proc->pid); // Capture current reference bits capture_reference_bits(proc->pid, proc->ref_bits[proc->current_interval], page_count); // Advance interval proc->current_interval = (proc->current_interval + 1) % WSS_WINDOW_INTERVALS; // Estimate WSS: pages referenced in any interval proc->wss_estimate = 0; for (size_t page = 0; page < page_count; page++) { bool referenced = false; for (int i = 0; i < WSS_WINDOW_INTERVALS; i++) { if (proc->ref_bits[i][page]) { referenced = true; break; } } if (referenced) proc->wss_estimate++; } // Calculate target with buffer proc->target_allocation = proc->wss_estimate + (proc->wss_estimate * WSS_BUFFER_PERCENT / 100);} // Adjust allocation toward targetvoid adjust_to_wss(WSSProcess* proc, size_t* free_pool) { if (proc->allocated < proc->target_allocation) { // Need more frames size_t need = proc->target_allocation - proc->allocated; size_t grant = (need < *free_pool) ? need : *free_pool; proc->allocated += grant; *free_pool -= grant; } else if (proc->allocated > proc->target_allocation) { // Have excess frames size_t excess = proc->allocated - proc->target_allocation; size_t reclaim = excess / 2; // Gradual reclamation proc->allocated -= reclaim; *free_pool += reclaim; }}Both PFF and WSS-based adjustment are forms of feedback control—systems that measure output, compare to a target, and adjust inputs to minimize error. Understanding control theory principles leads to more stable, responsive allocation systems.
Control System Components:
PID Control for Memory Allocation:
A PID (Proportional-Integral-Derivative) controller provides sophisticated control:
Proportional: Adjustment proportional to current error
Integral: Adjustment based on accumulated error
Derivative: Adjustment based on error rate of change
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
class PIDMemoryController: """PID controller for dynamic frame allocation.""" def __init__(self, kp: float, ki: float, kd: float, target_fault_rate: float): self.kp = kp # Proportional gain self.ki = ki # Integral gain self.kd = kd # Derivative gain self.target = target_fault_rate # State self.integral = 0.0 self.last_error = 0.0 def compute_adjustment(self, current_fault_rate: float, dt: float) -> int: """ Compute frame adjustment based on fault rate error. Returns: Number of frames to add (positive) or remove (negative) """ # Error: difference from target fault rate error = self.target - current_fault_rate # Note: negative error means too many faults (need more frames) # Proportional term p_term = self.kp * error # Integral term (with anti-windup) self.integral += error * dt self.integral = max(-100, min(100, self.integral)) i_term = self.ki * self.integral # Derivative term derivative = (error - self.last_error) / dt if dt > 0 else 0 d_term = self.kd * derivative self.last_error = error # Combined output output = p_term + i_term + d_term # Convert to frame count (negative output = add frames) return int(-output * 10) # Scale factor def reset(self): """Reset controller state.""" self.integral = 0.0 self.last_error = 0.0Control parameter tuning is critical:
• Too aggressive (high gains): Oscillation, instability • Too conservative (low gains): Slow response, thrashing persists • Balanced: Quick response without overshoot
Start with proportional-only control, add integral to eliminate steady-state error, then add derivative if oscillation occurs.
Linux: kswapd and Direct Reclaim
Linux uses multiple mechanisms for dynamic adjustment:
Windows: Working Set Manager
Windows actively manages per-process working sets:
123456789101112131415161718192021222324
#!/bin/bash# Linux Pressure Stall Information (PSI) monitoring # Read memory pressure metricsecho "Memory Pressure Metrics:"cat /proc/pressure/memory # Output format:# some avg10=0.00 avg60=0.00 avg300=0.00 total=0# full avg10=0.00 avg60=0.00 avg300=0.00 total=0 # "some": Time at least one task stalled on memory# "full": Time all tasks stalled on memory# avg10/60/300: 10-second, 60-second, 5-minute averages # Use PSI for dynamic adjustment triggerTHRESHOLD=25.0 # 25% stalled time CURRENT=$(cat /proc/pressure/memory | grep "^some" | awk '{print $2}' | cut -d= -f2) if (( $(echo "$CURRENT > $THRESHOLD" | bc -l) )); then echo "Memory pressure detected: ${CURRENT}%" echo "Triggering allocation adjustment..."fi| OS | Mechanism | Trigger | Response |
|---|---|---|---|
| Linux | kswapd | Free pages < low watermark | Background page reclaim |
| Linux | Direct reclaim | Allocation failure | Synchronous reclaim |
| Linux | OOM killer | No reclaimable memory | Process termination |
| Windows | Working Set Trim | Periodic or pressure | Per-process page-out |
| Windows | Memory Manager | Continuous | Dynamic balance adjustment |
Module Complete
You have now mastered the complete spectrum of frame allocation strategies:
These strategies form the foundation of memory management in all modern operating systems. Understanding them enables you to analyze, design, and optimize memory allocation policies for any computing environment.
You now possess comprehensive knowledge of frame allocation strategies—from simple equal allocation to sophisticated dynamic adjustment. You can analyze real-world allocation policies, design appropriate strategies for specific workloads, and understand how modern operating systems manage memory across competing processes.