Session 6.1 – Secondary Storage Structure
Understanding magnetic disks, SSD architecture, and storage performance optimization in modern operating systems
Learning Objectives
By the end of this session, you will be able to:
- Understand the physical structure and operation of magnetic disk drives
- Analyze SSD architecture and compare with traditional hard drives
- Calculate disk scheduling algorithms and their performance implications
- Evaluate storage performance metrics and optimization techniques
- Design efficient storage hierarchies for different application requirements
Magnetic Disk Structure
Physical Components
Magnetic disk drives consist of several key physical components that work together to provide persistent storage. Understanding these components is essential for optimizing storage performance and predicting system behavior.
Disk Components:
- Platter: Circular magnetic disk surface
- Track: Concentric circles on platter surface
- Sector: Fixed-size storage units on tracks
- Cylinder: Set of tracks at same arm position
- Head: Read/write mechanism
- Arm: Mechanical actuator for head movement
Disk Geometry and Addressing
Modern disk drives use Cylinder-Head-Sector (CHS) addressing or Logical Block Addressing (LBA) to locate specific data blocks. Understanding addressing schemes is crucial for disk scheduling optimization.
Example: CHS to LBA Conversion
Given a disk with:
- Cylinders: 1024
- Heads: 16
- Sectors per track: 63
CHS to LBA Conversion Formula:
LBA = (C × Heads + H) × Sectors_per_track + (S - 1)
Where:
C = Cylinder number
H = Head number
S = Sector number
Example Calculation:
Given: CHS (100, 5, 30)
Cylinders = 1024, Heads = 16, Sectors_per_track = 63
Step 1: Calculate cylinder and head component
(C × Heads + H) = (100 × 16 + 5) = 1605
Step 2: Multiply by sectors per track
1605 × 63 = 101,115
Step 3: Add sector offset
101,115 + (30 - 1) = 101,115 + 29 = 101,144
Result: LBA = 101,144
Disk Performance Metrics
Several timing components contribute to overall disk access time. Understanding these metrics enables effective performance analysis and optimization.
Disk Access Time Components
Total Access Time Formula:
Access_Time = Seek_Time + Rotational_Delay + Transfer_Time
Component Breakdown:
┌───────────────────┬────────────────────────────────────────┐
│ Component │ Description │
├───────────────────┼────────────────────────────────────────┤
│ │ Time to position read/write head over │
│ Seek Time │ the correct track. Depends on: │
│ │ • Current head position │
│ │ • Target track location │
├───────────────────┼────────────────────────────────────────┤
│ │ Time for desired sector to rotate under │
│ Rotational │ the head. Average = Revolution_time / 2 │
│ Delay │ • Depends on rotational speed (RPM) │
│ │ • Worst case = Full revolution │
├───────────────────┼────────────────────────────────────────┤
│ │ Time to read/write data from/to disk. │
│ Transfer Time │ = Data_size / Transfer_rate │
│ │ • Usually smallest component │
│ │ • Sequential access is faster │
└───────────────────┴────────────────────────────────────────┘
Performance Optimization Focus:
• Seek Time: Use scheduling algorithms (SSTF, SCAN)
• Rotational Delay: NCQ, read-ahead, write coalescing
• Transfer Time: Larger block sizes, parallel I/O
Example: Access Time Calculation
For a disk with the following specifications:
- Average seek time: 8ms
- Rotational speed: 7200 RPM
- Transfer rate: 100 MB/s
- Block size: 4KB
Disk Access Time Calculation:
Given Specifications:
• Average seek time: 8ms
• Rotational speed: 7200 RPM
• Transfer rate: 100 MB/s
• Block size: 4KB
Step 1: Calculate Rotational Delay
Revolution_time = 60 seconds ÷ 7200 RPM = 8.33ms
Average_rotational_delay = 8.33ms ÷ 2 = 4.17ms
Step 2: Calculate Transfer Time
Transfer_time = 4KB ÷ 100MB/s
= 4,096 bytes ÷ (100 × 10⁶ bytes/s)
= 0.04ms
Step 3: Calculate Total Access Time
Total_time = Seek_time + Rotational_delay + Transfer_time
= 8ms + 4.17ms + 0.04ms
= 12.21ms
Time Breakdown:
┌─────────────────┬─────────┬──────────┐
│ Component │ Time │ Percent │
├─────────────────┼─────────┼──────────┤
│ Seek Time │ 8.00ms │ 65.5% │
│ Rotational Delay│ 4.17ms │ 34.2% │
│ Transfer Time │ 0.04ms │ 0.3% │
│ TOTAL │ 12.21ms │ 100.0% │
└─────────────────┴─────────┴──────────┘
Disk Scheduling Algorithms
Disk scheduling algorithms minimize seek time by optimizing the order in which disk I/O requests are serviced. Different algorithms offer varying performance characteristics depending on workload patterns.
First-Come, First-Served (FCFS)
FCFS Algorithm
Algorithm FCFS_Disk_Scheduling:
Input: request_queue[], initial_head_position
Output: service_sequence[], total_seek_time
BEGIN
total_seek_time = 0
current_position = initial_head_position
service_sequence = []
FOR EACH request in request_queue DO
seek_distance = |current_position - request|
total_seek_time = total_seek_time + seek_distance
service_sequence.append(request)
current_position = request
END FOR
RETURN service_sequence, total_seek_time
END
Time Complexity: O(n)
Simple but may result in high seek times for scattered requests.
Shortest Seek Time First (SSTF)
SSTF Algorithm
Algorithm SSTF_Disk_Scheduling:
Input: request_queue[], initial_head_position
Output: service_sequence[], total_seek_time
BEGIN
remaining_requests = copy(request_queue)
total_seek_time = 0
current_position = initial_head_position
service_sequence = []
WHILE remaining_requests is not empty DO
closest_request = NULL
min_distance = INFINITY
FOR EACH request in remaining_requests DO
distance = |current_position - request|
IF distance < min_distance THEN
min_distance = distance
closest_request = request
END IF
END FOR
total_seek_time = total_seek_time + min_distance
service_sequence.append(closest_request)
remaining_requests.remove(closest_request)
current_position = closest_request
END WHILE
RETURN service_sequence, total_seek_time
END
Time Complexity: O(n²)
Better average performance but can cause starvation of distant requests.
SCAN (Elevator) Algorithm
SCAN (Elevator) Algorithm
Algorithm SCAN_Disk_Scheduling:
Input: request_queue[], initial_head_position, disk_size, direction
Output: service_sequence[], total_seek_time
BEGIN
left_requests = []
right_requests = []
// Partition requests based on initial head position
FOR EACH request in request_queue DO
IF request < initial_head_position THEN
left_requests.append(request)
ELSE
right_requests.append(request)
END IF
END FOR
// Sort requests for optimal scanning
sort(left_requests, DESCENDING) // From head toward 0
sort(right_requests, ASCENDING) // From head toward end
total_seek_time = 0
current_position = initial_head_position
service_sequence = []
IF direction == "RIGHT" THEN
// Service right side first
FOR EACH request in right_requests DO
total_seek_time += |current_position - request|
service_sequence.append(request)
current_position = request
END FOR
// Move to disk end if needed
IF current_position != disk_size-1 THEN
total_seek_time += (disk_size-1) - current_position
current_position = disk_size-1
END IF
// Service left side
FOR EACH request in left_requests DO
total_seek_time += |current_position - request|
service_sequence.append(request)
current_position = request
END FOR
END IF
RETURN service_sequence, total_seek_time
END
Time Complexity: O(n log n)
Provides uniform wait times and prevents starvation.
C-SCAN (Circular SCAN) Algorithm
C-SCAN (Circular SCAN) Algorithm
Algorithm C_SCAN_Disk_Scheduling:
Input: request_queue[], initial_head_position, disk_size
Output: service_sequence[], total_seek_time
BEGIN
left_requests = []
right_requests = []
// Partition requests based on initial head position
FOR EACH request in request_queue DO
IF request < initial_head_position THEN
left_requests.append(request)
ELSE
right_requests.append(request)
END IF
END FOR
// Sort for circular scanning (both ascending)
sort(left_requests, ASCENDING) // From 0 upward
sort(right_requests, ASCENDING) // From head upward
total_seek_time = 0
current_position = initial_head_position
service_sequence = []
// Service right side first (toward end)
FOR EACH request in right_requests DO
total_seek_time += |current_position - request|
service_sequence.append(request)
current_position = request
END FOR
// Circular jump: go to end, then to beginning
IF right_requests is not empty THEN
total_seek_time += (disk_size-1) - current_position // To end
total_seek_time += disk_size - 1 // To beginning
current_position = 0
END IF
// Service left side from beginning
FOR EACH request in left_requests DO
total_seek_time += |current_position - request|
service_sequence.append(request)
current_position = request
END FOR
RETURN service_sequence, total_seek_time
END
Time Complexity: O(n log n)
More uniform wait times than SCAN, treats all requests more fairly.
Interactive Disk Scheduling Demo
Test Scenario: Requests [98, 183, 37, 122, 14, 124, 65, 67] with head at position 53
Request Queue Visualization:
0 25 50 75 100 125 150 175 200
|----|----|----|----|----|----|----|----|----|
↑
HEAD
(53)
Pending Requests:
14 37 65 67 98 122 124 183
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
[39] [16] [12] [14] [45] [69] [71] [130] ← Distance from head
Solid State Drive Architecture
Solid State Drives (SSDs) use NAND flash memory instead of magnetic storage, providing significantly different performance characteristics and operational considerations compared to traditional hard disk drives.
NAND Flash Memory Structure
NAND Flash Memory Hierarchy
SSD Organization Structure:
Channel Level (Highest)
┌────────────────────────────────────────────────────────────────┐
│ CHANNEL (Parallel data pathways - typically 4-16 channels) │
│ • Independent data buses for parallel operation │
│ • Each channel can operate simultaneously │
│ • Bandwidth scales with number of channels │
└────────────────────────────────────────────────────────────────┘
│
↓
┌────────────────────────────────────────────────────────────────┐
│ DIE (Independent flash memory units - 1-8 per channel) │
│ • Each die has its own control logic │
│ • Can execute commands independently │
│ • Typical capacity: 32GB - 256GB per die │
└────────────────────────────────────────────────────────────────┘
│
↓
┌────────────────────────────────────────────────────────────────┐
│ PLANE (Collection of blocks sharing resources - 1-4 per die) │
│ • Shares page buffers and bit lines │
│ │ • Can perform some operations in parallel │
│ • Typical size: 8GB - 64GB per plane │
└────────────────────────────────────────────────────────────────┘
│
↓
┌────────────────────────────────────────────────────────────────┐
│ BLOCK (Smallest erase unit - 128-512 pages per block) │
│ • Erasing must be done at block level │
│ • Typical size: 512KB - 4MB per block │
│ • Limited erase cycles: 1K-10K (depends on NAND type) │
└────────────────────────────────────────────────────────────────┘
│
↓
┌────────────────────────────────────────────────────────────────┐
│ PAGE (Smallest read/write unit - 4KB to 16KB per page) │
│ • Can only write to empty (erased) pages │
│ • Contains user data + spare area (ECC, metadata) │
│ • Read/Program operations at page granularity │
└────────────────────────────────────────────────────────────────┘
│
↓
┌────────────────────────────────────────────────────────────────┐
│ CELL (Basic storage unit - stores 1-4 bits per cell) │
│ • SLC: 1 bit/cell (fastest, most durable) │
│ • MLC: 2 bits/cell (balanced performance/cost) │
│ • TLC: 3 bits/cell (higher density, slower) │
│ • QLC: 4 bits/cell (highest density, lowest endurance) │
└────────────────────────────────────────────────────────────────┘
(Lowest Level)
SSD Operations and Limitations
| Operation | Characteristics | Performance Impact | Limitations |
|---|---|---|---|
| Read | Page-level operation | Fast (25-100μs) | Read disturb after many reads |
| Write | Page-level, requires empty page | Medium (200-1500μs) | Write amplification |
| Erase | Block-level operation | Slow (1.5-3ms) | Limited P/E cycles (1K-10K) |
Flash Translation Layer (FTL)
The Flash Translation Layer manages the mapping between logical block addresses (LBA) and physical flash memory addresses, handling wear leveling, garbage collection, and bad block management.
Flash Translation Layer (FTL)
Algorithm FTL_Write_Operation:
Input: logical_block_address (LBA), data
Output: success/failure status
Data Structures:
mapping_table[LBA] → Physical_Block_Address (PBA)
reverse_mapping[PBA] → LBA
free_block_list[]
erase_count[block_id]
BEGIN
// Check if LBA already has a mapping
IF LBA exists in mapping_table THEN
old_PBA = mapping_table[LBA]
mark_page_invalid(old_PBA)
update_valid_page_count(get_block(old_PBA))
END IF
// Allocate new physical page
new_PBA = allocate_free_page()
IF new_PBA == NULL THEN
// No free pages, trigger garbage collection
victim_block = select_victim_block()
new_PBA = garbage_collect(victim_block)
IF new_PBA == NULL THEN
RETURN FAILURE // Disk full
END IF
END IF
// Update mapping tables
mapping_table[LBA] = new_PBA
reverse_mapping[new_PBA] = LBA
// Write data to flash memory
status = flash_program(new_PBA, data)
RETURN status
END
Wear Leveling Strategies
Wear Leveling Algorithms
Algorithm Dynamic_Wear_Leveling:
Input: write_request
Output: selected_physical_block
BEGIN
available_blocks = get_free_blocks()
IF available_blocks is empty THEN
RETURN NULL
END IF
min_erase_count = INFINITY
selected_block = NULL
FOR EACH block in available_blocks DO
IF erase_count[block] < min_erase_count THEN
min_erase_count = erase_count[block]
selected_block = block
END IF
END FOR
RETURN selected_block
END
Algorithm Static_Wear_Leveling:
BEGIN
hot_block = find_max_erase_count_block()
cold_block = find_min_erase_count_block_with_static_data()
erase_diff = erase_count[hot_block] - erase_count[cold_block]
IF erase_diff > WEAR_LEVELING_THRESHOLD THEN
// Swap data between blocks
temp_data = read_block(cold_block)
erase_block(cold_block)
write_block(cold_block, read_block(hot_block))
erase_block(hot_block)
write_block(hot_block, temp_data)
// Update erase counts
erase_count[cold_block]++
erase_count[hot_block]++
END IF
END
Storage Performance Optimization
I/O Queue Management
Modern storage systems implement sophisticated queue management techniques to maximize throughput and minimize latency. Understanding queue depths and parallelism is crucial for performance optimization.
Native Command Queuing (NCQ) Benefits
NCQ Performance Analysis: Without NCQ (Sequential Processing): ┌─────────────┬────────────┬───────────┬─────────────┐ │ Request │ Seek Time │Transfer │ Total Time │ ├─────────────┼────────────┼───────────┼─────────────┤ │ Request_1 │ 8ms │ 0.04ms │ 8.04ms │ │ (Track 100) │ │ │ │ ├─────────────┼────────────┼───────────┼─────────────┤ │ Request_2 │ 6ms │ 0.04ms │ 6.04ms │ │ (Track 200) │ │ │ │ ├─────────────┼────────────┼───────────┼─────────────┤ │ Request_3 │ 10ms │ 0.04ms │ 10.04ms │ │ (Track 50) │ │ │ │ ├─────────────┼────────────┼───────────┼─────────────┤ │ TOTAL │ 24ms │ 0.12ms │ 24.12ms │ └─────────────┴────────────┴───────────┴─────────────┘ With NCQ (Optimized Ordering): Reordered Sequence: 50 → 100 → 200 ┌─────────────┬────────────┬───────────┬─────────────┐ │ Request │ Seek Time │Transfer │ Total Time │ ├─────────────┼────────────┼───────────┼─────────────┤ │ Request_3 │ 10ms │ 0.04ms │ 10.04ms │ │ (Track 50) │ (from 53) │ │ │ ├─────────────┼────────────┼───────────┼─────────────┤ │ Request_1 │ 3ms │ 0.04ms │ 3.04ms │ │ (Track 100) │ (from 50) │ │ │ ├─────────────┼────────────┼───────────┼─────────────┤ │ Request_2 │ 6ms │ 0.04ms │ 6.04ms │ │ (Track 200) │ (from 100) │ │ │ ├─────────────┼────────────┼───────────┼─────────────┤ │ TOTAL │ 19ms │ 0.12ms │ 19.12ms │ └─────────────┴────────────┴───────────┴─────────────┘ Performance Improvement: 21% reduction in seek time
Storage Hierarchy Optimization
| Storage Tier | Capacity | Latency | Bandwidth | Cost/GB | Use Case |
|---|---|---|---|---|---|
| L1 Cache | 32-128KB | 1-3 cycles | 1TB/s | $1000+ | CPU operations |
| RAM | 8-128GB | 50-100ns | 25-100GB/s | $3-8 | Active data |
| NVMe SSD | 256GB-8TB | 10-100μs | 3-7GB/s | $0.10-0.30 | OS, applications |
| SATA SSD | 128GB-4TB | 50-200μs | 500MB/s | $0.08-0.20 | Frequent access |
| HDD | 500GB-20TB | 5-15ms | 100-250MB/s | $0.02-0.05 | Bulk storage |
Advanced Optimization Techniques
Advanced Storage Optimization Techniques
1. Read-ahead/Prefetching Algorithm:
Algorithm Adaptive_Readahead:
BEGIN
window_size = INITIAL_WINDOW
hit_rate = 0.0
access_pattern = []
FOR EACH read_request DO
// Detect sequential pattern
IF is_sequential(access_pattern) THEN
window_size = min(MAX_WINDOW, window_size * 2)
prefetch_blocks(current_block + 1, window_size)
ELSE
window_size = max(MIN_WINDOW, window_size / 2)
END IF
update_access_pattern(current_block)
END FOR
END
2. Write Coalescing:
• Combine small writes into larger sequential writes
• Reduce number of I/O operations
• Improve bandwidth utilization
• Buffer writes temporarily in memory
3. I/O Scheduling Optimization:
• NCQ (Native Command Queuing): 32 outstanding commands
• TCQ (Tagged Command Queuing): Legacy alternative
• Elevator algorithms: SCAN, C-SCAN for fairness
• Deadline scheduling: Prevent I/O starvation
4. Multi-level Buffer Management:
• L1: CPU Cache (1-3 cycles)
• L2: Main Memory (50-100ns)
• L3: SSD Cache (10-100μs)
• L4: HDD Storage (5-15ms)
5. Parallel I/O Techniques:
• RAID striping across multiple disks
• Multiple I/O channels utilization
• Asynchronous I/O operations
• Multi-threading for concurrent access
Summary
This session covered the fundamental concepts of secondary storage structure including:
- Magnetic Disk Structure: Physical components, geometry, addressing schemes, and performance metrics
- Disk Scheduling Algorithms: FCFS, SSTF, SCAN, and C-SCAN with their trade-offs and use cases
- SSD Architecture: NAND flash memory structure, Flash Translation Layer, and wear leveling techniques
- Performance Optimization: I/O queue management, storage hierarchies, and advanced optimization strategies
Key Takeaways
Understanding storage hardware characteristics is crucial for system performance optimization. Different storage technologies require different optimization approaches, and modern systems use hierarchical storage to balance performance, capacity, and cost.