Loading content...
The elegance of FIFO lies not just in its concept, but in the simplicity of its realization. Transforming the principle "evict the oldest page" into working code reveals why FIFO has been a foundational teaching tool and why, despite its limitations, it remains valuable in contexts where simplicity and predictability outweigh peak performance.
In this page, we will build FIFO from the ground up. We'll examine the data structures that naturally represent FIFO ordering, the algorithms for page loading and eviction, and complete implementations with detailed walkthroughs. By the end, you'll be able to implement FIFO in any language and understand every aspect of its operation.
By the end of this page, you will be able to implement FIFO page replacement from scratch, understand why queues are the natural data structure for FIFO, trace through complete reference string examples, and analyze the time and space complexity of FIFO operations.
The very name "First-In, First-Out" points directly to the data structure we need: a queue. A queue maintains insertion order and provides:
These operations map perfectly to FIFO page replacement:
| FIFO Operation | Queue Operation | Description |
|---|---|---|
| Load new page | Enqueue | Add to back of queue |
| Select victim | Front/Peek | Identify oldest page |
| Evict victim | Dequeue | Remove from front |
| Check presence | Search | Is a page in the queue? |
Implementation Options:
Several data structures can implement a queue efficiently:
1. Circular Array (Ring Buffer)
+---+---+---+---+---+---+---+---+
| A | B | C | D | E | | | |
+---+---+---+---+---+---+---+---+
↑ ↑
front back
2. Linked List
[Head/Front] → A → B → C → D → E → [Tail/Back]
3. Array with Dynamic Resizing
For kernel-level implementation, circular arrays are preferred due to their cache efficiency and lack of dynamic allocation. For simulation or teaching purposes, linked lists or standard library queues offer cleaner code. The algorithmic complexity is identical; the choice is about context.
Let's formalize the FIFO page replacement algorithm with precise pseudocode. The algorithm handles two cases: page hits (page already in memory) and page misses (page must be loaded).
State Variables:
frames[]: Array representing physical memory framesqueue: FIFO queue tracking page arrival orderpage_to_frame: Mapping from page number to frame index (optional, for O(1) lookup)num_frames: Total number of available framesCore Algorithm:
1234567891011121314151617181920212223242526272829303132333435363738
FUNCTION handle_page_reference(page_number): // Phase 1: Check if page is already in memory (HIT) IF page_number IN frames THEN // Page HIT - no action needed for FIFO log("HIT: Page " + page_number + " already in memory") RETURN "HIT" END IF // Phase 2: Page MISS - must load the page log("MISS: Page " + page_number + " not in memory") page_fault_count += 1 // Phase 2a: Check if there's a free frame IF queue.size() < num_frames THEN // Free frame available - just load free_frame = find_free_frame() frames[free_frame] = page_number queue.enqueue(page_number) log("Loaded page " + page_number + " into frame " + free_frame) ELSE // Phase 2b: No free frame - must evict victim_page = queue.dequeue() // Remove oldest victim_frame = find_frame(victim_page) // Optional: Write back if dirty IF victim_page.is_dirty THEN write_to_disk(victim_page) END IF // Replace victim with new page frames[victim_frame] = page_number queue.enqueue(page_number) // Add new page to back log("Evicted page " + victim_page + ", loaded page " + page_number) END IF RETURN "MISS"END FUNCTIONAlgorithm Analysis:
Let's analyze each phase:
Phase 1: Hit Detection
Phase 2a: Load into Free Frame
Phase 2b: Eviction Required
| Operation | Naive Implementation | Optimized Implementation |
|---|---|---|
| Check if page in memory | O(n) | O(1) with hash set |
| Find free frame | O(n) | O(1) with free list |
| Select victim (dequeue) | O(1) | O(1) |
| Add new page (enqueue) | O(1) | O(1) |
| Find victim's frame | O(n) | O(1) with hash map |
| Overall page miss | O(n) | O(1) |
Let's build a complete, production-quality FIFO page replacement simulator. This implementation includes proper tracking, statistics, and clean interfaces.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
#include <stdio.h>#include <stdlib.h>#include <stdbool.h>#include <string.h> #define MAX_FRAMES 100#define MAX_PAGES 1000 // FIFO Page Replacement Implementationtypedef struct { int frames[MAX_FRAMES]; // Frame contents (-1 = empty) int queue[MAX_FRAMES]; // Circular queue for FIFO order int front; // Queue front index int rear; // Queue rear index int count; // Current number of pages in memory int num_frames; // Total frames available int page_faults; // Page fault counter int page_hits; // Page hit counter} FIFOPager; // Initialize the FIFO pagervoid fifo_init(FIFOPager* pager, int num_frames) { pager->num_frames = num_frames; pager->front = 0; pager->rear = -1; pager->count = 0; pager->page_faults = 0; pager->page_hits = 0; // Initialize all frames as empty for (int i = 0; i < MAX_FRAMES; i++) { pager->frames[i] = -1; pager->queue[i] = -1; }} // Check if page is in memorybool fifo_contains(FIFOPager* pager, int page) { for (int i = 0; i < pager->count; i++) { int idx = (pager->front + i) % pager->num_frames; if (pager->queue[idx] == page) { return true; } } return false;} // Find frame containing a pageint fifo_find_frame(FIFOPager* pager, int page) { for (int i = 0; i < pager->num_frames; i++) { if (pager->frames[i] == page) { return i; } } return -1;} // Process a page referencebool fifo_reference(FIFOPager* pager, int page, int* evicted) { *evicted = -1; // Check for page hit if (fifo_contains(pager, page)) { pager->page_hits++; return false; // No page fault } // Page fault occurred pager->page_faults++; if (pager->count < pager->num_frames) { // Free frame available - no eviction needed pager->rear = (pager->rear + 1) % pager->num_frames; pager->queue[pager->rear] = page; pager->frames[pager->count] = page; pager->count++; } else { // Must evict oldest page *evicted = pager->queue[pager->front]; int victim_frame = fifo_find_frame(pager, *evicted); // Remove oldest from queue pager->front = (pager->front + 1) % pager->num_frames; // Add new page to queue pager->rear = (pager->rear + 1) % pager->num_frames; pager->queue[pager->rear] = page; // Update frame content pager->frames[victim_frame] = page; } return true; // Page fault occurred} // Print current statevoid fifo_print_state(FIFOPager* pager) { printf("Frames: ["); for (int i = 0; i < pager->num_frames; i++) { if (pager->frames[i] == -1) { printf(" - "); } else { printf(" %d ", pager->frames[i]); } } printf("] Queue order (oldest→newest): "); for (int i = 0; i < pager->count; i++) { int idx = (pager->front + i) % pager->num_frames; printf("%d ", pager->queue[idx]); } printf("\n");} // Run simulation with reference stringvoid fifo_simulate(int* reference_string, int length, int num_frames) { FIFOPager pager; fifo_init(&pager, num_frames); printf("\nFIFO Simulation with %d frames:\n", num_frames); printf("Reference string: "); for (int i = 0; i < length; i++) { printf("%d ", reference_string[i]); } printf("\n\n"); for (int i = 0; i < length; i++) { int page = reference_string[i]; int evicted; bool fault = fifo_reference(&pager, page, &evicted); printf("Ref: %d -> ", page); if (!fault) { printf("HIT "); } else if (evicted == -1) { printf("MISS (loaded) "); } else { printf("MISS (evicted %d) ", evicted); } fifo_print_state(&pager); } printf("\n--- Statistics ---\n"); printf("Total references: %d\n", length); printf("Page faults: %d\n", pager.page_faults); printf("Page hits: %d\n", pager.page_hits); printf("Hit ratio: %.2f%%\n", (float)pager.page_hits / length * 100); printf("Fault ratio: %.2f%%\n", (float)pager.page_faults / length * 100);}The circular buffer approach uses modulo arithmetic to wrap around indices. This avoids the need to shift elements and provides true O(1) enqueue and dequeue operations. The 'front' index points to the oldest page (next to be evicted), while 'rear' points to the most recently added page.
Let's trace through FIFO execution step by step with a complete example. This detailed walkthrough shows exactly how FIFO maintains state and makes decisions.
Problem Setup:
This is a classic operating systems textbook example that demonstrates FIFO behavior.
| Step | Page Ref | Frame 0 | Frame 1 | Frame 2 | Queue (oldest→newest) | Result | Evicted |
|---|---|---|---|---|---|---|---|
| 1 | 7 | 7 | 7 | MISS | |||
| 2 | 0 | 7 | 0 | 7→0 | MISS | ||
| 3 | 1 | 7 | 0 | 1 | 7→0→1 | MISS | |
| 4 | 2 | 2 | 0 | 1 | 0→1→2 | MISS | 7 |
| 5 | 0 | 2 | 0 | 1 | 0→1→2 | HIT | |
| 6 | 3 | 2 | 3 | 1 | 1→2→3 | MISS | 0 |
| 7 | 0 | 2 | 3 | 0 | 2→3→0 | MISS | 1 |
| 8 | 4 | 4 | 3 | 0 | 3→0→4 | MISS | 2 |
| 9 | 2 | 4 | 2 | 0 | 0→4→2 | MISS | 3 |
| 10 | 3 | 4 | 2 | 3 | 4→2→3 | MISS | 0 |
| 11 | 0 | 0 | 2 | 3 | 2→3→0 | MISS | 4 |
| 12 | 3 | 0 | 2 | 3 | 2→3→0 | HIT | |
| 13 | 2 | 0 | 2 | 3 | 2→3→0 | HIT | |
| 14 | 1 | 0 | 1 | 3 | 3→0→1 | MISS | 2 |
| 15 | 2 | 0 | 1 | 2 | 0→1→2 | MISS | 3 |
| 16 | 0 | 0 | 1 | 2 | 0→1→2 | HIT | |
| 17 | 1 | 0 | 1 | 2 | 0→1→2 | HIT | |
| 18 | 7 | 7 | 1 | 2 | 1→2→7 | MISS | 0 |
| 19 | 0 | 7 | 0 | 2 | 2→7→0 | MISS | 1 |
| 20 | 1 | 7 | 0 | 1 | 7→0→1 | MISS | 2 |
Analysis of the Trace:
| Metric | Value |
|---|---|
| Total references | 20 |
| Page faults | 15 |
| Page hits | 5 |
| Hit ratio | 25% |
| Fault ratio | 75% |
Observations:
High fault rate: The 75% fault rate is substantial. Compare this to OPT which achieves only 9 faults (45%) on the same reference string.
Evicting useful pages: Notice steps 6-7: Page 0 is evicted at step 6, immediately needed at step 7, forcing another eviction. FIFO doesn't know page 0 is about to be used.
Queue rotation: The queue maintains strict FIFO order. Watch how pages rotate through the queue regardless of how often they're accessed.
Hit clustering: Hits tend to occur when the same pages are referenced closely together (steps 5, 12-13, 16-17).
The trace shows page 0 being evicted at step 6, then immediately needed at step 7. This pattern—evicting a page that's about to be used—is FIFO's fundamental weakness. LRU would recognize page 0 was recently used and protect it. FIFO cannot make this distinction.
While the basic FIFO implementation is simple, several optimizations can improve practical performance:
Optimization 1: O(1) Page Lookup with Hash Sets
Instead of scanning frames to check if a page is present, maintain a hash set of currently loaded pages:
// Add to structure:
HashSet* loaded_pages;
// Contains check becomes:
bool contains = hashset_contains(loaded_pages, page); // O(1)
Optimization 2: Direct Frame Mapping
Maintain a hash map from page number to frame index:
// Add to structure:
HashMap* page_to_frame; // page_number -> frame_index
// Finding victim's frame becomes:
int frame = hashmap_get(page_to_frame, victim_page); // O(1)
Optimization 3: Memory-Aligned Structures
For kernel implementations, align data structures to cache lines:
typedef struct __attribute__((aligned(64))) {
int frames[MAX_FRAMES];
// ... other fields
} FIFOPager;
Optimization 4: Batch Processing
For simulation or analysis, process multiple references before updating statistics:
void fifo_batch_reference(FIFOPager* pager, int* pages, int count) {
for (int i = 0; i < count; i++) {
// Process without intermediate logging
fifo_reference_silent(pager, pages[i]);
}
// Update statistics once
update_statistics(pager);
}
In a real operating system, FIFO doesn't exist in isolation. It integrates with several other subsystems:
1. Memory Management Unit (MMU) Integration
The MMU notifies the OS of page faults via hardware interrupts. The FIFO algorithm runs inside the page fault handler:
Page Fault Interrupt
↓
OS Page Fault Handler
↓
FIFO Victim Selection (if no free frames)
↓
Disk I/O (load new page, optionally write victim)
↓
Update Page Table Entries
↓
Resume Faulted Process
2. Page Table Integration
FIFO must coordinate with page tables:
3. Disk I/O Subsystem
Page replacement involves substantial I/O:
4. Process Scheduling Interaction
Page faults cause context switches:
The FIFO algorithm itself runs in microseconds. The disk I/O it triggers takes milliseconds (1000x+ slower). This means FIFO's O(1) victim selection is effectively "free" compared to the I/O cost. What matters for performance is the QUALITY of victim selection (minimizing future faults), not the SPEED of making the selection.
Implementing FIFO correctly requires careful testing. Here are the test cases every implementation should pass:
Test Category 1: Basic Functionality
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
// Test 1: Sequential loading with no eviction// Reference: 1, 2, 3 with 3 frames// Expected: 3 faults, 0 hits, pages 1,2,3 in memoryvoid test_sequential_load() { int refs[] = {1, 2, 3}; FIFOPager p; fifo_init(&p, 3); for (int i = 0; i < 3; i++) { int evicted; fifo_reference(&p, refs[i], &evicted); assert(evicted == -1); // No eviction yet } assert(p.page_faults == 3); assert(p.page_hits == 0);} // Test 2: Simple eviction// Reference: 1, 2, 3, 4 with 3 frames// Expected: Page 1 evicted when page 4 loadsvoid test_simple_eviction() { int refs[] = {1, 2, 3, 4}; FIFOPager p; fifo_init(&p, 3); int evicted; for (int i = 0; i < 4; i++) { fifo_reference(&p, refs[i], &evicted); } assert(evicted == 1); // Page 1 (oldest) was evicted} // Test 3: Page hit doesn't affect queue// Reference: 1, 2, 3, 2, 4 with 3 frames// Expected: Page 1 evicted (not 2, even though 2 was hit)void test_hit_no_queue_change() { int refs[] = {1, 2, 3, 2, 4}; FIFOPager p; fifo_init(&p, 3); int evicted; for (int i = 0; i < 5; i++) { fifo_reference(&p, refs[i], &evicted); } assert(evicted == 1); // 1 is still oldest despite 2's hit} // Test 4: Verify FIFO order maintained// Reference: 1, 2, 3, 4, 5, 6 with 3 frames// Expected: Evictions in order 1, 2, 3void test_fifo_order() { int refs[] = {1, 2, 3, 4, 5, 6}; int expected_evictions[] = {-1, -1, -1, 1, 2, 3}; FIFOPager p; fifo_init(&p, 3); for (int i = 0; i < 6; i++) { int evicted; fifo_reference(&p, refs[i], &evicted); assert(evicted == expected_evictions[i]); }}We've now covered FIFO implementation comprehensively. Let's consolidate the key points:
What's Next:
With implementation mastered, we turn to FIFO's most surprising property: Belady's Anomaly. In the next page, we'll discover that adding more frames can actually increase page faults—a counterintuitive result that reveals deep truths about the nature of page replacement algorithms.
You can now implement FIFO page replacement from scratch. You understand the data structures, algorithms, complexity, and integration points. Next, we explore Belady's Anomaly—the paradox that makes FIFO a fascinating subject beyond its practical applications.