Memory protection unit
A Memory Protection Unit (MPU) is a hardware mechanism integrated into microprocessors, particularly in embedded and real-time systems, that enforces access permissions on defined memory regions to prevent unauthorized reads, writes, or instruction executions, thereby isolating software tasks and enhancing overall system reliability without requiring virtual address translation.[1][2]
Unlike the more comprehensive Memory Management Unit (MMU), which combines memory protection with virtual-to-physical address mapping for general-purpose computing, an MPU operates on physical addresses and uses a simpler, region-based configuration to define protection boundaries, offering deterministic performance and lower overhead ideal for resource-constrained environments like microcontrollers.[1] In architectures such as ARM Cortex-M and Cortex-R series, the MPU is an optional feature within the Protected Memory System Architecture (PMSA), programmable via dedicated registers to specify attributes like read-only, no-execute, or privilege-level restrictions for up to 16 or more regions, triggering exceptions on violations to allow software intervention.[1] Similarly, in Intel Nios II processors, the MPU monitors all memory accesses in user or supervisor modes, generating faults post-access to protect against errant code while supporting multitasking operating systems without full virtualization.[2]
This protection model is crucial for preventing faults in safety-critical applications, such as automotive or industrial control systems, where it mitigates risks from software bugs or malicious code by enforcing spatial and temporal isolation between tasks.[1][2] Key benefits include reduced latency from avoiding translation lookups, simplified implementation for real-time operating systems (RTOS), and hardware-enforced compliance with security standards, though it requires careful configuration by system software to avoid overly restrictive or ineffective setups.[1]
Fundamentals
Definition and Purpose
A Memory Protection Unit (MPU) is a hardware mechanism integrated into processors, particularly microcontrollers, that divides the physical memory address space into configurable regions, each assigned specific access permissions to control reads, writes, and executions.[3][4] This allows the MPU to act as a gatekeeper, intercepting memory access requests from the processor core and enforcing rules based on the current execution context, such as privilege level.[5] Unlike more complex systems, the MPU operates directly on physical addresses in environments without virtual memory translation.[6]
The primary purposes of an MPU include enforcing isolation between different segments of memory, such as code, data, and peripherals, to prevent one component from interfering with others.[3] It mitigates software faults, like buffer overflows or pointer errors, that could corrupt critical areas, thereby improving system reliability in resource-constrained embedded applications.[7] Additionally, the MPU enhances security by blocking unauthorized access in single-address-space systems, reducing risks from malicious code injection or privilege escalation without the overhead of full virtual memory support.[6][8]
The MPU emerged as a lightweight alternative to full Memory Management Units (MMUs) with the rise of microcontroller-based embedded systems in the early 2000s, notably through architectures like ARM Cortex-M, to address reliability and security needs in applications without the complexity of general-purpose computing.[9] For instance, in a typical microcontroller setup, an MPU can configure the stack region as read-write accessible only to the current task while marking flash memory as read-only, thereby preventing accidental overwrites during runtime operations.[3][10]
Key Components
The Memory Protection Unit (MPU) consists of several core hardware registers that collectively define and enforce memory access controls. A control register enables or disables the MPU functionality and configures default behaviors for unmapped memory regions, such as allowing privileged access when the MPU is inactive. For example, in ARM Cortex-M processors, this is denoted as MPU_CTRL.[3] In addition, a type or capability register indicates the number of supported regions and other features; this number varies by implementation and processor architecture, typically 8 in many ARM-based microcontrollers and up to 64 (32 instruction + 32 data regions) in others like Intel Nios II.[11][12]
Base address registers specify the starting address of each protected memory area, aligning with the system's address space to isolate code, data, or peripherals.[3] Complementing these, size registers define the length of each region, often expressed as powers of two, ranging from 32 bytes up to the full address space (e.g., 4 GB in 32-bit systems), to facilitate efficient boundary checks. In ARM, these are integrated into the region attribute and size register (MPU_RASR).[11]
Access permission registers encode fine-grained controls through bit fields, including read/write permissions, execute-never flags to prohibit instruction fetches, no-access options, and distinctions between privileged and unprivileged modes to enforce isolation between software components.[3] These permissions ensure that only authorized accesses occur, preventing unintended modifications or executions in designated memory areas.
In MPU designs, regions are typically configured to be non-overlapping to simplify setup, but overlaps are permitted in many implementations, with conflict resolution often based on priority schemes, such as higher-numbered regions taking precedence (e.g., region 15 over region 0 in ARM with up to 16 regions).[13] This priority mechanism allows flexible layering of protections, such as overlaying stricter rules on subsets of broader regions, without requiring exhaustive non-overlap enforcement.[11]
Principles of Operation
Memory Region Definition
In a Memory Protection Unit (MPU), memory regions are configured by the programmer or operating system through writes to dedicated MPU registers, where each region is defined by its base address, size, and enablement status. The base address specifies the starting location of the protected area in the memory map, while the size is typically encoded using bit fields representing powers of two (2^N), supporting granularities from 32 bytes up to 4 gigabytes depending on the implementation. This setup process occurs during system boot, task initialization, or runtime reconfiguration to establish isolated memory segments for code, data, or peripherals.[14]
A key constraint in region definition is address alignment: the base address must be a multiple of the region's size to enable straightforward hardware comparisons during access checks. For example, a 1 KB (1024-byte) region requires its base address to align on a 1 KB boundary, such as 0x1000 or 0x2000, preventing misaligned configurations that could complicate protection logic. This requirement ensures efficient operation across various memory architectures.[14][15]
The number of configurable regions is hardware-limited, commonly to 8 in standard implementations like those in ARM Cortex-M processors, though some variants support up to 16. This finite capacity demands strategic partitioning of the memory map, prioritizing protection for essential areas while potentially relying on overlaps or defaults for less critical spaces.[14][15]
Once defined, regions require explicit enablement, but the MPU itself must first be activated globally via a control register to enforce protections system-wide; post-reset, the MPU is typically disabled to allow initial configuration without faults. Individual regions can then be toggled on or off independently, facilitating temporary adjustments, such as during debugging or mode transitions, without disrupting other protected areas.[14]
To achieve finer granularity within a single region slot, subregions divide larger areas into up to 8 equal segments, each with independent enablement for permissions or disablement. For instance, a 256 KB region can be subdivided into eight 32 KB subregions (each 2^(N-3) bytes for size exponent N=18), allowing selective protection of subsections like a vulnerable data buffer while keeping the overall allocation efficient. This feature is available for regions of 256 bytes or larger, optimizing resource use in constrained environments.[14][15]
Access Permission Enforcement
The Memory Protection Unit (MPU) enforces access permissions by performing hardware checks on every memory access attempt, including load operations, store operations, and instruction fetches. Upon an access, the processor compares the target address against the defined memory regions, which are typically up to eight programmable regions plus a default background region. If the address falls outside all defined regions, it defaults to the background region attributes; otherwise, it matches the enabled region with the highest number among those that include the address. Once matched, the MPU verifies the permission bits—such as read, write, and execute flags—against the type of operation and the current processor mode. For instance, a store operation in unprivileged mode requires the region to explicitly allow writes from that mode; mismatches result in immediate denial.[16]
If an access violates permissions, the MPU generates a precise synchronous fault, typically triggering a MemManage exception in ARM Cortex-M processors. This exception halts the offending operation and vectors to a dedicated handler, where software can respond by terminating the task, logging the violation, or recovering resources in an operating system context. In bare-metal environments without an OS, the handler might simply reset the system or ignore the fault, though the latter risks undefined behavior. The fault is precise, meaning the exact instruction causing it is identifiable via the stacked program counter and fault status registers.[16][17]
Privilege levels play a central role in enforcement, with Cortex-M architectures supporting two modes: privileged (also called handler mode) and unprivileged (thread mode). In privileged mode, the processor has broader access, often defaulting to the background region's permissions for unmatched addresses, allowing full system control for operating system kernels or firmware. Unprivileged mode restricts access to only explicitly permitted regions, preventing user applications from modifying critical kernel data or peripherals. MPU configuration registers themselves require privileged access to modify, ensuring that unprivileged code cannot alter protections. This separation isolates applications from the OS, enhancing security in multi-tasking setups.[16]
For overlapping regions, the MPU resolves conflicts by prioritizing higher-numbered regions (e.g., region 7 over region 0), applying the strictest permission rules from the matching region to prevent unintended access escalations. No-access regions can be configured for sensitive I/O peripherals by setting all permission bits to deny reads, writes, and executes, trapping any attempts to prevent software corruption of hardware registers. This ordered checking ensures deterministic enforcement even with partially overlapping configurations.[16]
The enforcement process introduces minimal runtime overhead, as the MPU checks are integrated into the processor pipeline without stalling accesses in compliant cases; faults, when generated, add exception entry latency but are comparable to other bus errors. This contrasts with more complex mechanisms like the MMU, where translation lookups impose greater per-access costs.[18]
Differences from Memory Management Unit
A Memory Protection Unit (MPU) fundamentally differs from a Memory Management Unit (MMU) in its approach to memory handling, as the MPU operates exclusively on physical addresses without performing any address translation.[1] In contrast, an MMU employs virtual-to-physical address mapping through page tables, enabling advanced features such as demand paging and memory swapping to support dynamic memory allocation and efficient resource sharing in multitasking environments.[19] This lack of translation in the MPU simplifies its design, focusing solely on access control over predefined physical memory regions rather than providing a full virtual memory abstraction.[20]
The architectural overhead of an MPU is significantly lower than that of an MMU, as it does not require complex components like a Translation Lookaside Buffer (TLB) for caching translations, resulting in reduced power consumption and hardware complexity.[4] This makes the MPU particularly suitable for resource-constrained microcontrollers, where simplicity and predictability are prioritized over the extensive capabilities of an MMU, which is better suited for high-performance desktops and servers running sophisticated multitasking operating systems.[21] The absence of TLB-related overhead in the MPU also contributes to lower latency in memory access checks, enhancing determinism in time-critical applications.[4]
In terms of granularity, an MPU defines protection using a limited number of contiguous memory regions—typically up to 16—allowing straightforward partitioning of physical memory into blocks with uniform attributes.[22] Conversely, an MMU utilizes smaller, fixed-size pages, commonly 4 KB, which support dynamic allocation and finer-grained management through hierarchical page tables.[19] These regions in an MPU are programmed statically and lack the paging mechanism of an MMU, limiting flexibility but ensuring efficient enforcement without the need for table walks.[1]
A key limitation of the MPU is its inability to provide per-process address space isolation, as all tasks share the same physical address space without virtualization, potentially exposing one process's memory to others if not carefully managed.[4] In comparison, an MMU enables complete virtualization and isolation through distinct virtual address spaces for each process, along with support for privilege rings to enforce security boundaries.[21] The MPU was introduced as an optional feature in the ARMv7-M architecture for Cortex-M processors, bridging the gap between systems with no hardware protection and those requiring a full MMU.[15]
These distinctions lead to divergent use cases: MPUs excel in real-time embedded systems demanding low overhead and predictable behavior, while MMUs are essential for general-purpose computing environments that leverage memory protection rings and virtual memory for robust multitasking and resource efficiency.[1][4]
Advantages over No Hardware Protection
The Memory Protection Unit (MPU) provides hardware-enforced isolation of memory regions, preventing unauthorized access by malware or software bugs, which significantly reduces risks such as code injection and buffer overflow exploits in unprotected systems.[3] By defining access permissions at the hardware level, the MPU ensures that unprivileged tasks cannot modify critical data or execute code from non-executable regions, offering a robust defense absent in software-only protection mechanisms.[5]
In terms of reliability, the MPU detects memory access violations early through exceptions, such as MemManage faults triggered by attempts to access invalid addresses, enabling prompt fault isolation in embedded firmware.[3] This capability catches common errors like stack overflows or null pointer dereferences before they propagate, facilitating easier debugging and reducing the likelihood of system crashes compared to no hardware protection, where such faults often go undetected until runtime failures occur.[23]
The MPU enhances efficiency by performing access checks in hardware without requiring operating system intervention for every memory operation, contrasting with software polling methods that introduce higher latency and overhead.[5] This low-overhead design supports safe multitasking in real-time operating systems (RTOS) on resource-constrained devices, allowing multiple tasks to share memory while maintaining isolation, a feature impractical without hardware support.[23]
In automotive electronic control units (ECUs), the MPU prevents faults in one software module from propagating to others by confining memory interference to specific partitions, thereby avoiding cascading failures and improving overall system robustness.[24] This fault containment aligns with safety standards like ISO 26262, enabling targeted recovery such as resetting only the affected partition. Additionally, the MPU's integration into microcontroller cores adds minimal silicon area, providing substantial protection benefits at low hardware cost for low-end embedded systems.[5]
Implementations in Hardware
ARM Cortex-M MPU
The Memory Protection Unit (MPU) in ARM Cortex-M processors is an optional hardware feature designed to enhance security and reliability in embedded systems by defining protected memory regions with specific access permissions and attributes. It is implemented in the Cortex-M3, Cortex-M4, and Cortex-M7 cores, which are based on the ARMv7-M architecture, where it supports up to 16 configurable memory regions to allow fine-grained control over memory access. In contrast, the MPU is absent in the Cortex-M0 core to minimize silicon area and cost for ultra-low-power applications.[25][15]
The MPU's configuration is managed through a set of system control registers in the System Control Space (SCS). The MPU_TYPE register provides implementation details, including the DREGION field (bits [15:8]) that specifies the number of supported regions, which can range from 0 (indicating no MPU) to 16 in ARMv7-M implementations. The MPU_CTRL register controls overall operation: its ENABLE bit (bit 0) activates the MPU, the PRIVDEFENA bit (bit 2) determines whether a default background region applies to privileged accesses when no explicit region matches, and the HFNMIENA bit (bit 1) extends MPU enforcement to HardFault, NMI, and FAULTMASK-blocked handlers. For individual regions, the MPU_RNR register selects the active region number (0-15), the MPU_RBAR register sets the base address (ADDR field, bits [31:5], aligned to region size) and optionally specifies the region number on write (REGION field, bits [3:0]), while the MPU_RASR register defines region attributes including size, permissions, and subregions.[25][26][27][28]
Access permissions and attributes are encoded in the MPU_RASR register. The TEX field (bits [21:19]) combines with the S bit (bit 18), C bit (bit 17), and B bit (bit 16) to specify memory types such as Device, Strongly-ordered, or Normal (cacheable), enabling control over caching and sharing behaviors. The AP field (bits [26:24]) enforces access levels: for example, 011 allows full read/write/execute for both privileged and unprivileged modes, 001 restricts to privileged read/write only, and 000 denies all access, triggering a fault on violation. The XN bit (bit 28) implements execute-never protection, preventing instruction fetches from the region (value 1) to mitigate code injection risks, which generates a MemManage fault if attempted. Additionally, the SRD field (bits [15:8]) allows disabling up to eight 32-byte subregions within larger regions (≥256 bytes) for more granular control.[28]
When the MPU is enabled but no defined region matches an access address, a background region provides default permissions if PRIVDEFENA is set: privileged-mode accesses default to read/write with XN=0 (executable), while unprivileged accesses fault; peripheral address spaces may default to XN=1. Violations, such as unauthorized access or execution, trigger a MemManage exception, with fault details reported in the Configurable Fault Status Register (CFSR), specifically the MemManage Fault Status Register (MMFSR) bits like IACCVIOL (instruction access violation) or DACCVIOL (data access violation), and the fault address captured in the Memory Management Fault Address Register (MMFAR) for debugging.[29]
The MPU was introduced with the ARMv7-M architecture in the Cortex-M3 core, first released in 2006, adding support for subregions, more precise permissions, and up to 16 regions to better suit real-time embedded applications.[30]
To enable the MPU and configure a read-only code region, software must follow a precise sequence to avoid faults during setup. First, disable interrupts and ensure privileged mode. Write to MPU_CTRL to set ENABLE=0 (if not already). Select the region via MPU_RNR (e.g., write 0 for region 0). Set MPU_RBAR with the base address (e.g., 0x08000000 for flash) and region number. Configure MPU_RASR: enable the region (ENABLE=1, bit 0), set size (e.g., SIZE=19 for 1MB, bits [5:1]), AP=0b101 for privileged read-only (bits [26:24]), XN=0 (bit 28) if executable code, TEX/C/B for non-cacheable Normal memory (e.g., TEX=0b000, C=0, B=0), and SRD=0x00 for full region enable. Repeat for other regions if needed. Finally, set MPU_CTRL.ENABLE=1 and PRIVDEFENA=1. A simplified C-like example using CMSIS-style access (assuming __DSB() for barrier):
MPU->CTRL = 0; // Disable MPU
__DSB();
__ISB();
MPU->RNR = 0; // Select region 0
MPU->RBAR = (0x08000000UL | 0); // Base address, region 0
MPU->RASR = (1UL << 0) | // Enable
(19UL << 1) | // Size: 1MB (2^20 bytes)
(0b101 << 24) | // Priv RO
(0UL << 28); // XN=0
MPU->CTRL = (1UL << 2) | (1UL << 0); // PRIVDEFENA=1, ENABLE=1
__DSB();
__ISB();
MPU->CTRL = 0; // Disable MPU
__DSB();
__ISB();
MPU->RNR = 0; // Select region 0
MPU->RBAR = (0x08000000UL | 0); // Base address, region 0
MPU->RASR = (1UL << 0) | // Enable
(19UL << 1) | // Size: 1MB (2^20 bytes)
(0b101 << 24) | // Priv RO
(0UL << 28); // XN=0
MPU->CTRL = (1UL << 2) | (1UL << 0); // PRIVDEFENA=1, ENABLE=1
__DSB();
__ISB();
This setup protects the code region from writes while allowing privileged reads and execution.[28][29]
Other Microcontroller Architectures
In RISC-V microcontrollers, the Physical Memory Protection (PMP) mechanism operates in Machine mode (M-mode) to enforce region-based access controls on physical memory, supporting up to 16 protection entries that utilize Naturally Aligned Power-of-Two (NAPOT) encoding for region sizes ranging from 4 bytes to 4 GB.[31] These entries are configured via Control and Status Registers (CSRs), with permissions such as read, write, and execute managed through the MSECCFG register, enabling isolation of sensitive regions while allowing M-mode software to oversee lower privilege modes like User (U-mode) and Supervisor (S-mode).[32] This design prioritizes simplicity for embedded systems without a full Memory Management Unit (MMU), focusing on contiguous physical regions to prevent unauthorized access by less privileged code.[33]
Microchip's AVR XMEGA family incorporates a limited form of memory protection through its I/O Memory Protection feature, which defines up to 8 configurable regions with a 1 KB granularity to safeguard critical peripherals and memory areas against unintended access.[34] Each region supports basic permission bits for read, write, and execute operations, enforced during CPU access to prevent faults in safety-critical applications, though it lacks the flexibility of more advanced MPUs by focusing primarily on I/O and peripheral isolation rather than broad address space partitioning.[35] This implementation suits low-power 8/16-bit environments where full virtualization is unnecessary, emphasizing runtime error detection over complex region overlapping.
The PowerPC e200 core family, used in NXP and STMicroelectronics microcontrollers, features a dedicated Memory Protection Unit (MPU) or MMU with 8 to 24 protection zones or TLB entries depending on the variant (e.g., 8 in e200z1, 24 in e200z4/z7), each defined by Zone Protection Registers (ZPRs) or region descriptors that specify base addresses, sizes, and permissions to isolate peripherals and memory segments.[36][37] These zones enable granular control over supervisor and user access modes, including read/write/execute restrictions and peripheral-specific protections, which help mitigate faults in automotive and industrial embedded systems by containing errors within designated areas. The MPU integrates with the core's exception handling to trigger interrupts on violations, providing a hardware-enforced boundary without relying on software polling.
Renesas RX series microcontrollers implement an MPU supporting up to 8 regions, where the MPU Address (MPUA) registers facilitate precise address matching and permission assignment for code and data segments.[38] This integration allows overlapping regions with configurable read/write/execute and user/supervisor modes, enhancing security in real-time applications by protecting against buffer overflows and unauthorized modifications. In parallel, Texas Instruments' MSP430 family, particularly FRAM-based variants introduced in the 2010s, employs a basic Memory Protection Module (MPM) that divides flash and RAM into up to three segments, each configurable for read-only, write-protected, or executable states to prevent accidental overwrites in low-power IoT devices.[39]
Variations exist in other architectures, such as Microchip's PIC32 series, which relies on simpler segment-based protection mechanisms embedded in its MIPS M4K core configuration, defining fixed memory segments with access controls via exception traps rather than dynamic regions, suitable for cost-sensitive designs prioritizing boot-time integrity over runtime reconfiguration.[40]
Applications and Use Cases
In Embedded Systems
In embedded systems, the Memory Protection Unit (MPU) is frequently deployed to safeguard real-time operating system (RTOS) tasks by allocating specific memory regions to individual threads, thereby preventing one task from corrupting another's data or code. For instance, the FreeRTOS MPU port enables user-defined regions to be assigned to tasks upon creation, with the option for runtime reconfiguration to accommodate dynamic needs.[41] This approach extends to isolating peripheral resources, such as UART buffers, where the MPU restricts access to designated software components, reducing the risk of erroneous or malicious interference with device drivers.[17]
Configuration of the MPU in these resource-constrained environments typically involves static strategies at boot time, where regions are predefined to match a fixed memory map, ensuring predictable protection without ongoing overhead.[42] Alternatively, dynamic configuration permits runtime adjustments, such as resizing stack regions for tasks with variable memory demands, though this requires careful management to avoid performance impacts.[43]
A primary challenge in MPU deployment stems from the hardware's limited region count—often 8 to 16 per core—which necessitates coarse partitioning of memory and peripherals, potentially leaving some areas unprotected or requiring shared regions that dilute isolation.[7] In power-sensitive applications like wireless sensors, this limitation can complicate designs, as finer-grained protection might exceed available regions, prompting developers to prioritize critical areas.
In Internet of Things (IoT) devices, such as the ESP32 with its Xtensa architecture supporting MPU features, the unit enforces protections like read-only access to flash memory, thereby preventing tampering with firmware during operation.[44] For safety-critical embedded systems, the MPU is essential for meeting standards like IEC 61508, where it facilitates SIL 3 certification by enabling independence between safety-related and non-safety software modules through enforced access controls.[45]
To streamline implementation, integrated development environments (IDEs) such as Keil MDK and IAR Embedded Workbench offer built-in support for MPU setup, including default segmentation schemes and configuration tools that generate region definitions based on project linker scripts.[46] These features allow developers to visualize and validate memory maps early in the design process, promoting reliable deployment in bare-metal or lightweight RTOS contexts.
Integration with Operating Systems
Operating systems integrate the Memory Protection Unit (MPU) to enhance security in multitasking environments, particularly in resource-constrained embedded systems. The kernel typically initializes the MPU during system boot, configuring initial memory regions and access permissions to protect critical kernel data and code from unauthorized access. This setup ensures that the system starts with a secure memory layout before user tasks are launched.[17]
The kernel manages MPU regions dynamically through system calls, allowing it to assign, reconfigure, or deallocate regions for specific threads or processes during runtime. For example, when switching contexts, the OS updates MPU registers like RBAR and RASR based on the task's process table to enforce isolation. Access violations trigger a MemManage fault, which the kernel handles by terminating the offending task, releasing its resources, and potentially logging the incident to prevent system compromise. This fault-handling mechanism aligns with the OS's overall error recovery strategy, ensuring stability without requiring full system resets.[17][41]
MPU integration with privilege levels complements the OS scheduler by restricting user-mode execution to designated regions, while kernel-mode operations retain privileged access. Unprivileged tasks, running in user mode, can only invoke kernel services via supervisor calls (SVC), which temporarily elevate privileges without altering MPU configurations, thus preventing direct manipulation of protected memory. This synergy allows the scheduler to manage task priorities and preemptions while the MPU enforces spatial isolation, reducing the risk of faults propagating across tasks.[17][41]
Several real-time operating systems (RTOS) provide built-in support for MPU-mediated protection. Zephyr RTOS leverages the MPU to isolate user-mode threads, treating them as untrusted entities that are confined to their own memory domains, thereby safeguarding the kernel and other threads from erroneous or malicious code. FreeRTOS, starting from version 10, includes an MPU API such as vTaskAllocateMPURegions() for runtime allocation of up to three user-definable regions per task, enabling fine-grained control over memory access in both privileged and unprivileged modes. In NuttX RTOS's protected build mode, the MPU separates kernel and user spaces into distinct blobs, reserving regions like 128 KB of FLASH and 4 KB of SRAM exclusively for the kernel to block user applications from accessing privileged resources.[47][41][48]
Despite these benefits, MPUs have limitations in supporting full multi-user operating systems, as their fixed number of regions—often 8 to 16—cannot accommodate the granular paging required for virtual memory management in complex environments like desktop or server OSes. In such cases, hybrid designs in some system-on-chips combine MPU for coarse-grained protection with an MMU for finer virtualization, balancing overhead and scalability in mixed workloads.[49]