Loading learning content...
In the earliest days of computing, the operating system faced a fundamental challenge that would shape decades of research: how do you allow multiple programs to reside in main memory simultaneously?
Before multi-programming, computers operated in a simple batch mode—load one program, execute it to completion, load the next. This approach left the CPU idle during I/O operations, wasting precious computational resources. The solution was obvious: keep multiple programs in memory so the CPU could switch between them. But this solution immediately raised a critical question that would define memory management forever:
How do you divide a single, finite physical memory among multiple processes that have no knowledge of each other's existence?
Fixed partitioning with equal-size partitions represents humanity's first systematic answer to this question. While primitive by modern standards, understanding this scheme is essential—it establishes the vocabulary, trade-offs, and design patterns that permeate all subsequent memory management innovations.
By the end of this page, you will:
• Understand the fundamental concept of equal-size fixed partitioning • Trace the historical context and design rationale behind this approach • Analyze the memory allocation and process loading mechanics • Implement partition management data structures • Evaluate the scheme's behavior under various workloads • Recognize the inherent trade-offs that motivate more advanced techniques
Equal-size fixed partitioning is a memory management scheme where the operating system divides the total available physical memory into a fixed number of partitions, all of identical size. This division occurs at system boot time (or during OS initialization) and remains constant throughout the system's operation—no partition can be created, destroyed, resized, or merged during runtime.
Let's formalize the properties that define this scheme:
1. Static Division The memory is divided once, typically at system startup. If the system has 64 MB of user memory and we create 8 partitions, each partition is exactly 8 MB. This division persists until the system is reconfigured and rebooted.
2. Uniform Size Every partition has identical dimensions. If partition P₁ is 8 MB, then P₂, P₃, ..., Pₙ are all exactly 8 MB. There are no exceptions, no special cases.
3. Exclusive Allocation Each partition can hold at most one process at any given time. A partition is either:
4. Fixed Boundaries Partition boundaries are immutable. A process allocated to a partition cannot "spill over" into adjacent partitions, regardless of its actual memory requirements.
The appeal of equal-size partitions lies in its radical simplicity. The OS doesn't need complex algorithms to decide partition sizes, doesn't need to track variable-length memory regions, and doesn't need to coalesce adjacent free areas. Every allocation decision reduces to: "Is there a free partition?" This simplicity translated directly to faster implementation, fewer bugs, and predictable behavior in an era when computing resources were precious and software development was expensive.
Given:
The fundamental relationship is:
S = M ÷ n
Or equivalently:
M = n × S
This relationship constrains system design. A system administrator configuring the partition scheme must choose either n or S, and the other is determined. For example:
The degree of multi-programming (maximum concurrent processes) equals n—you can never have more processes in memory than you have partitions, regardless of how small those processes might be.
| Number of Partitions (n) | Partition Size (S) | Max Processes | Best For |
|---|---|---|---|
| 4 | 16 MB | 4 | Few large processes |
| 8 | 8 MB | 8 | Balanced workload |
| 16 | 4 MB | 16 | Many small processes |
| 32 | 2 MB | 32 | High concurrency, small tasks |
| 64 | 1 MB | 64 | Maximum concurrency |
To appreciate fixed partitioning, we must understand the technological context of the late 1950s and 1960s when these schemes emerged. The constraints of that era profoundly shaped the design decisions that may seem arbitrary or suboptimal from a modern perspective.
1. Limited Hardware Support Early computers lacked the Memory Management Units (MMUs) that modern systems take for granted. There was no hardware-assisted address translation, protection mechanisms, or page table support. Any memory management had to be implemented almost entirely in software, with minimal hardware assistance.
2. Scarce Memory Main memory was extraordinarily expensive. A system might have only 32 KB to 256 KB of total memory. Every byte mattered, and the overhead of memory management data structures was a genuine concern.
3. Simple Protection Needs The primary protection mechanism was typically a pair of base and limit registers:
With equal-size partitions, the limit register could be hardwired to a single value, simplifying the hardware.
4. Predictable Workloads Batch processing systems of that era ran standardized jobs with relatively predictable memory requirements. Variability existed, but not at the scale seen in modern interactive systems.
5. Minimal Runtime Overhead With CPU cycles precious, any memory management scheme needed minimal runtime overhead. Equal-size partitions require almost no computation—partition lookup is O(1), and there's no fragmentation management.
IBM's System/360 (1964), one of the most influential computer architectures in history, used variations of fixed partitioning in its early operating systems (DOS/360, OS/MFT). Understanding equal-size partitions provides insight into the design philosophy that shaped commercial computing for decades.
Equal-size fixed partitioning embodies a particular philosophy that trades flexibility for simplicity:
One-Time Configuration Cost The system administrator pays the "cost" of analyzing workload characteristics once, at configuration time. They study the typical processes that will run, estimate memory requirements, and choose an appropriate partition size. This upfront investment eliminates runtime decision-making.
Uniform Treatment By treating all partitions identically, the system avoids a class of bugs and edge cases. There's no "special" partition that behaves differently, no complex priority logic, no size-dependent algorithms.
Predictable Performance Context switching between processes requires loading new base/limit register values. With equal-size partitions, this operation has constant, predictable timing. System administrators could reason about worst-case performance.
Failure Isolation A misbehaving process can only corrupt its own partition. With hardware-enforced boundaries (base/limit registers), processes cannot access memory outside their assigned region, providing primitive but effective isolation.
Understanding the physical memory layout is crucial for grasping how equal-size partitioning integrates with the broader system architecture.
Physical memory in a fixed-partition system is typically organized as follows:
+---------------------------+ 0x00000000 (Address 0)
| Interrupt Vectors |
| (Reserved by Hardware) |
+---------------------------+
| |
| Operating System |
| (Kernel Code + Data) |
| |
+---------------------------+ OS_END
| |
| Partition 0 | Size = S
| [Process A or FREE] |
| |
+---------------------------+ OS_END + S
| |
| Partition 1 | Size = S
| [Process B or FREE] |
| |
+---------------------------+ OS_END + 2S
| |
| Partition 2 | Size = S
| [Process C or FREE] |
| |
+---------------------------+ OS_END + 3S
| ... |
+---------------------------+ OS_END + (n-1)S
| |
| Partition n-1 | Size = S
| [Process N or FREE] |
| |
+---------------------------+ MEMORY_TOP
The starting address of partition i is:
Partition_Start(i) = OS_END + (i × S)
Where OS_END is the first address after the operating system's reserved memory region. This formula enables O(1) address calculation for any partition—no searching, no pointer chasing.
The hardware protection mechanism works as follows:
When a process is dispatched (given the CPU):
physical_address = base + logical_addresslogical_address < limitFor equal-size partitions:
Processes generate logical addresses (also called virtual addresses in this context) relative to address 0. The hardware automatically translates these to physical addresses:
Logical Address: 0x00001000 (4 KB into process memory)
Base Register: 0x00100000 (Partition 3 starts here)
Physical Address: 0x00101000 (Base + Logical)
This translation is transparent to the process—it believes it has memory starting at address 0, regardless of which partition it actually occupies.
| Partition # | Base Address | Limit | Physical Range | Current State |
|---|---|---|---|---|
| 0 | 0x00800000 (8 MB) | 8 MB | 8 MB - 16 MB | Process A (PID 101) |
| 1 | 0x01000000 (16 MB) | 8 MB | 16 MB - 24 MB | FREE |
| 2 | 0x01800000 (24 MB) | 8 MB | 24 MB - 32 MB | Process B (PID 203) |
| 3 | 0x02000000 (32 MB) | 8 MB | 32 MB - 40 MB | Process C (PID 157) |
Implementing equal-size fixed partitioning requires minimal but carefully designed data structures. The simplicity is a primary advantage of this scheme.
The OS maintains a Partition Table that tracks the state of each partition:
123456789101112131415161718192021222324252627282930313233
// System configuration constants (set at boot time)#define NUM_PARTITIONS 8#define PARTITION_SIZE (8 * 1024 * 1024) // 8 MB each#define USER_MEM_START 0x00800000 // After OS kernel // Partition statestypedef enum { PARTITION_FREE, // Available for allocation PARTITION_OCCUPIED // Contains an active process} PartitionState; // Partition descriptortypedef struct { PartitionState state; // Current state int process_id; // PID of occupying process (-1 if free) uint32_t base_address; // Starting physical address uint32_t size; // Partition size (always PARTITION_SIZE) uint64_t allocation_time; // Timestamp for debugging/statistics} PartitionDescriptor; // The global partition tablePartitionDescriptor partition_table[NUM_PARTITIONS]; // Initialize partition table at boot timevoid init_partition_table(void) { for (int i = 0; i < NUM_PARTITIONS; i++) { partition_table[i].state = PARTITION_FREE; partition_table[i].process_id = -1; partition_table[i].base_address = USER_MEM_START + (i * PARTITION_SIZE); partition_table[i].size = PARTITION_SIZE; partition_table[i].allocation_time = 0; }}The allocation algorithm for equal-size partitions is remarkably simple. When a process requests memory, the OS simply finds any free partition:
123456789101112131415161718192021222324252627282930313233343536
// Allocate a partition to a process// Returns partition index on success, -1 on failureint allocate_partition(int process_id, uint32_t required_size) { // Critical check: Process must fit in a partition if (required_size > PARTITION_SIZE) { // Process too large for any partition log_error("Process %d requires %u bytes, max partition size is %u", process_id, required_size, PARTITION_SIZE); return -1; // Allocation failure } // Search for any free partition (linear scan) for (int i = 0; i < NUM_PARTITIONS; i++) { if (partition_table[i].state == PARTITION_FREE) { // Found a free partition - allocate it partition_table[i].state = PARTITION_OCCUPIED; partition_table[i].process_id = process_id; partition_table[i].allocation_time = get_current_time(); // Update process control block with memory info set_process_memory(process_id, partition_table[i].base_address, PARTITION_SIZE); log_info("Allocated partition %d to process %d (base: 0x%08X)", i, process_id, partition_table[i].base_address); return i; // Success - return partition index } } // No free partition available log_warning("No free partition for process %d - system at capacity", process_id); return -1; // Allocation failure - must wait or swap}Allocation: O(n) in worst case, where n is the number of partitions. We scan until finding a free partition.
Deallocation: O(1) if we know the partition index; O(n) if we search by process ID.
Memory Access: O(1) address translation via hardware base register.
In practice, with a small number of partitions (typically 4-16), this linear scan is negligible.
When a process terminates, its partition must be released:
1234567891011121314151617181920212223242526272829
// Deallocate a partition when a process terminatesvoid deallocate_partition(int process_id) { for (int i = 0; i < NUM_PARTITIONS; i++) { if (partition_table[i].process_id == process_id) { // Found the process's partition // Security: Zero out memory to prevent data leakage // (Critical in multi-user systems) memset((void*)partition_table[i].base_address, 0, PARTITION_SIZE); // Mark partition as free partition_table[i].state = PARTITION_FREE; partition_table[i].process_id = -1; partition_table[i].allocation_time = 0; log_info("Deallocated partition %d (formerly process %d)", i, process_id); // Wake up any processes waiting for memory signal_memory_available(); return; } } // Process not found - programming error or double-free log_error("Attempted to deallocate partition for unknown process %d", process_id);}Understanding how a process is loaded into a partition and begins execution reveals the practical mechanics of this memory management scheme.
When a user submits a job (program) for execution, the following sequence occurs:
Programs are typically compiled with addresses starting at 0. When loaded into a partition starting at a different address, there are two approaches:
1. Static Relocation (Load-Time) The loader adjusts all memory references in the program before execution begins. This is a one-time cost at load time.
2. Dynamic Relocation (Run-Time) The base register performs address adjustment on every memory access. All logical addresses are added to the base register to produce physical addresses.
Equal-size partitioning typically uses dynamic relocation (base-register translation), which offers a crucial advantage: a process can be swapped out to disk and later reloaded into a different partition without any address adjustment. The base register simply gets loaded with the new partition's starting address.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
// Complete process loading sequenceint load_process(const char* executable_path, int priority) { // Step 1: Read executable header to determine size ExecutableHeader header; if (read_executable_header(executable_path, &header) < 0) { return -1; // Invalid executable } uint32_t process_size = header.code_size + header.data_size + header.bss_size + header.stack_size; // Step 2: Size check if (process_size > PARTITION_SIZE) { log_error("Process requires %u bytes, max is %u", process_size, PARTITION_SIZE); return -1; // Too large } // Step 3: Create process control block int pid = create_pcb(executable_path, priority); // Step 4: Allocate partition int partition = allocate_partition(pid, process_size); if (partition < 0) { // No free partition - add to waiting queue add_to_memory_wait_queue(pid, process_size); log_info("Process %d waiting for memory", pid); return pid; // Process created but not loaded } uint32_t base = partition_table[partition].base_address; // Step 5: Load executable sections into partition load_code_section(executable_path, base); load_data_section(executable_path, base + header.code_size); zero_bss_section(base + header.code_size + header.data_size, header.bss_size); // Stack grows from top of partition uint32_t stack_top = base + PARTITION_SIZE; // Step 6: Initialize PCB with memory layout PCB* pcb = get_pcb(pid); pcb->base_address = base; pcb->limit = PARTITION_SIZE; pcb->program_counter = base; // Entry point pcb->stack_pointer = stack_top; pcb->state = PROCESS_READY; // Step 7: Add to ready queue add_to_ready_queue(pid); log_info("Process %d loaded into partition %d at 0x%08X", pid, partition, base); return pid;}The effectiveness of equal-size partitioning depends critically on the workload characteristics. Let's analyze how the system behaves under different scenarios.
Conditions: All processes require approximately the same memory (close to partition size)
Example: A system with 8 MB partitions running processes that each need 6-8 MB.
Analysis:
This is the ideal case for equal-size partitioning—it was designed precisely for such workloads.
Conditions: Process sizes vary significantly
Example: 8 MB partitions with processes requiring 1 MB to 20 MB.
Conditions: More processes want to run than there are partitions
Analysis: With n partitions, at most n processes can be memory-resident. Additional processes must:
Unlike variable-size schemes, no amount of memory reorganization can help. If all 8 partitions are occupied by processes using 1 MB each (7 MB wasted each), a 9th 1 MB process cannot run—even though 56 MB of memory is effectively unused.
This rigid limitation motivates the development of more sophisticated memory management techniques.
| Process Distribution | Actual Memory Used | Partition Memory Allocated | Utilization % | Assessment |
|---|---|---|---|---|
| 8 × 8 MB processes | 64 MB | 64 MB | 100% | Optimal |
| 8 × 6 MB processes | 48 MB | 64 MB | 75% | Good |
| 8 × 4 MB processes | 32 MB | 64 MB | 50% | Poor |
| 8 × 1 MB processes | 8 MB | 64 MB | 12.5% | Wasteful |
| 4 × 8 MB + blocked | 32 MB | 32 MB | 50% | Blocked by limit |
Equal-size partitioning forces a painful choice:
Large partitions: Good for big processes but waste memory on small ones, limit multi-programming degree.
Small partitions: Good for small processes but cannot run large ones, even with abundant free memory.
There is no partition size that works well for all workloads—this limitation is inherent to the equal-size approach.
Despite its limitations, equal-size partitioning offers genuine advantages that made it practical for its era and relevant for understanding modern systems.
1. Embedded Systems with Fixed Tasks A control system running exactly 4 monitoring tasks of similar size can use 4 equal partitions efficiently. The fixed workload matches the fixed allocation.
2. Batch Processing with Standardized Jobs Historical mainframes ran standardized COBOL batch jobs with predictable memory requirements. System administrators could size partitions to match typical job profiles.
3. Educational Systems The simplicity makes equal-size partitioning ideal for teaching memory management fundamentals. Students can implement a complete working system.
4. Very Small Partition Counts With only 2-4 partitions (common in early systems), the overhead of more sophisticated schemes wasn't justified. Equal-size worked "well enough."
5. Real-Time Systems with Known Bounds Systems where maximum memory per task is strictly bounded can use equal-size partitions sized to that bound, gaining predictability.
While not used directly in modern general-purpose OS, the equal-size philosophy appears in:
• Fixed-size slab allocators for kernel objects • Page-based allocation (fixed 4KB pages) • Memory pools in embedded systems • Container resource limits (fixed memory quotas)
The principle—simplify by fixing sizes—remains powerful.
We have conducted a thorough examination of equal-size fixed partitioning, the simplest approach to multi-programming memory management.
The equal-size constraint creates a fundamental tension: partition size that works for one workload fails for another. The next page examines unequal-size partitioning, which relaxes the uniformity constraint to better accommodate diverse process sizes. This seemingly small change introduces significant new complexity in allocation strategy.
You now understand the fundamental principles, implementation, and trade-offs of equal-size fixed partitioning. This knowledge forms the foundation for analyzing all subsequent memory management schemes—each one addresses specific limitations we've identified here.