logo

Memory

What is a Page?

In Linux, a Page is the smallest unit of memory that the CPU and the Operating System manage.

Instead of managing memory byte-by-byte (which would be incredibly slow and complex), Linux breaks all available RAM into fixed-size "chunks." These chunks are the Pages.

The Numbers: How big is a Page?

On almost all modern Linux systems (x86_64), the standard page size is 4 KiB (4096 bytes).

If you have 8GB of RAM, your Linux kernel isn't looking at "8 billion bytes"; it’s looking at roughly 2 million pages.

You can check your system's page size by running:

getconf PAGESIZE

Virtual Pages vs. Physical Frames

To understand pages, you have to understand that Linux uses Virtual Memory. There are two sides to every page:

  1. Virtual Page: What the process sees. Your app thinks it has a continuous block of memory from address 0 to MAX.
  2. Page Frame: What is actually in the Physical RAM chips.

The Page Table: This is the "Map." The kernel maintains a table that maps a process's Virtual Page to a physical Page Frame.

  • Cool fact: Two different processes can have the same Virtual Page address, but the Page Table points them to two completely different physical frames in RAM.

Why do we use Pages? (The Benefits)

A. Memory Protection

Because memory is divided into pages, the kernel can set permissions on each page.

  • Code Pages: Marked as "Read + Execute" (you can't write to them, preventing self-modifying code or certain hacks).
  • Data Pages: Marked as "Read + Write" (but not Execute).
  • Shared Pages: Multiple processes can point to the same physical Page Frame (this is how OverlayFS and Shared Libraries save RAM).

B. Paging and "Swapping"

When you run out of RAM, Linux doesn't have to move an entire program to the disk. It can pick specific, rarely used pages and move them to the "Swap" space on your hard drive. The process doesn't even know it happened—until it tries to access that page, which triggers a Page Fault.

C. Demand Paging (Efficiency)

When you start a 1GB program, Linux doesn't actually load 1GB into RAM. It creates a map of virtual pages but leaves them empty.

  1. The app tries to read a line of code.
  2. The CPU realizes that "Page" isn't in RAM (Page Fault).
  3. The Kernel pauses the app, grabs that one 4KB page from the disk, puts it in RAM, and resumes the app. This is why programs start fast even if they are huge.

4. Dirty Pages

When a process modifies the data in a page, that page is marked as "Dirty" in the Page Table.

  • Linux won't delete a dirty page from RAM to make room for others until it has "flushed" (written) those changes back to the disk.

Huge Pages (For High Performance)

4 KiB is a good size for most things, but for massive databases or high-performance apps, managing millions of 4 KiB pages creates a lot of overhead for the CPU.

Linux supports Huge Pages (usually 2MB or 1GB in size).

  • Pros: Fewer entries in the Page Table, meaning the CPU can find memory addresses much faster.
  • Cons: If you only need 10KB of data but use a 2MB Huge Page, you are wasting a lot of RAM (Internal Fragmentation).

How it relates to Containers and gVisor

  • cgroups: When you set a memory limit on a container (e.g., 512MB), the kernel is actually counting how many pages that cgroup has been assigned. If it asks for one more page than allowed, the kernel triggers the OOM Killer (Out of Memory).
  • gVisor: The Sentry (the guest kernel) has to manage its own internal Page Tables for the application, and then the Host Linux kernel manages the Page Tables for the Sentry. This "double-mapping" is part of what provides the security, but also the performance overhead.

Virtual vs Physical

Every address that can be printed out in C is virtual, any address you can see as a programmer of a user-level program is a virtual address. The printed out address is an illusion of how things are laid out in memory. Only OS knows the physical memory.

Virtual Address Space

Virtual address is an abstraction of 3 parts:

  • code: the program, static.
  • stack: function calls, local variables, managed by compiler, e.g. int x;.
  • heap: dynamic memory, created by malloc(), managed by user program, e.g. int *x = (int *) malloc(sizeof(int)).
  • under the hood they are spread in physical address by OS.

Order of the 3 parts: code comes first in the address space, then the heap, and the stack is all the way at the other end of this large virtual space.

So it looks like this:

[ code | heap ... stack ]

To prove it:

#include <stdio.h>
#include <stdlib.h>
int main() {
    printf("code(main) : %p\n", (void *) main);
    printf("heap(1) : %p\n", (void *) malloc(1));
    printf("heap(2) : %p\n", (void *) malloc(2));
    int x1 = 1;
    printf("stack(x1) : %p\n", (void *) &x1);
    int x2 = 2;
    printf("stack(x2) : %p\n", (void *) &x2);
    return 0;
}
code(main) : 0x400570
heap(1) : 0xcf6020
heap(2) : 0xcf6040
stack(x1) : 0x7fffdae25218
stack(x2) : 0x7fffdae25214

Again all of these addresses are virtual, and will be translated by the OS and hardware in order to fetch values from their true physical locations.

Can one page host multiple files?

The answer depends on whether you are talking about file data (the contents) or file metadata (the inode/info), and which filesystem you are using.

The Standard Rule: No (The Page Cache)

In the standard Linux Page Cache (the mechanism that caches file contents in RAM), the answer is generally No.

  • The kernel manages memory in Pages (typically 4KB).
  • The Page Cache uses a structure called address_space which belongs to exactly one inode.
  • A physical page in the Page Cache is mapped to a specific offset within a specific file.

The result: Even if you have a file that is only 10 bytes long, it will occupy an entire 4KB page in the Page Cache. The remaining 4,086 bytes are padded with zeros and wasted. This is known as internal fragmentation.

The Exception: "Inline Data" (Small Files)

Some modern filesystems (like ext4, Btrfs, and XFS) have an optimization called Inline Data or Tail Packing.

If a file is extremely small (usually less than a few hundred bytes), the filesystem doesn't allocate a separate data block for it. Instead, it stuffs the file's data directly into the Inode structure itself.

  • Inodes are small (typically 256 or 512 bytes).
  • The kernel stores Inodes in "Slabs" in memory.
  • Since a single 4KB page can hold 8 or 16 Inodes, and those Inodes contain the actual file data, one physical page of memory is technically hosting the data of multiple different files.

Metadata and the Buffer Cache: Yes

While the Page Cache handles file content, the Buffer Cache handles disk metadata (bitmaps, directory tables, etc.).

Filesystems often store multiple Inodes or multiple directory entries in a single Disk Block. When the kernel reads that block into memory:

  • The block is cached in a page.
  • That single page will contain the metadata (or directory entries) for dozens or hundreds of different files.

Memory-Mapped Files (mmap)

When you use mmap() to map a file into memory, you are subject to the 4KB alignment rule.

  • You cannot map "File A" to the first 2KB of a page and "File B" to the second 2KB of the same page using mmap.
  • The CPU's Memory Management Unit (MMU) works in pages. Each page table entry points to one physical page. There is no architectural way to split a single hardware page between two different file mappings for two different processes.

Shared Libraries: A Different Kind of Sharing

It is worth noting that one page can be shared by multiple processes, but it is still the same file.

  • For example, the page containing the code for printf() in libc.so is loaded into RAM once.
  • 100 different running programs all point to that same physical page.
  • But that page only contains data from libc.so, not from any other file.

What is Slab Allocation

To understand Slab Allocation, you first have to understand the problem it solves: The Page Allocator is too "clunky" for the kernel's daily needs.

The Problem: Pages are too big

The Linux kernel's primary memory manager (the Buddy System) works in Pages (usually 4KB).

However, the kernel constantly needs to create and destroy tiny objects:

  • A struct inode (approx. 600 bytes)
  • A struct dentry (approx. 200 bytes)
  • A struct task_struct (the process descriptor)

If the kernel asked for a full 4KB page every time it needed a 200-byte structure, it would waste over 90% of its memory (Internal Fragmentation). Furthermore, initializing these structures (setting up locks, list heads, etc.) takes time.

The Solution: Slab Allocation

The Slab Allocator acts as a wholesaler. It buys large pages from the kernel and carves them into small, equal-sized "slots" for specific objects.

The Hierarchy:

  1. Cache: For every type of object (e.g., "Inode Cache"), the kernel maintains a kmem_cache.
  2. Slab: Each Cache contains multiple Slabs. A Slab is one or more contiguous physical pages.
  3. Object: Each Slab is divided into equal-sized chunks called Objects. These are the actual structures (like an Inode) that the kernel uses.

How it Works (The "Pizza" Analogy)

Think of the Buddy System (Page Allocator) as a bakery that only sells Full Pizzas (4KB Pages). The Slab Allocator is a restaurant:

  1. It buys 10 pizzas (Slabs) from the bakery.
  2. It pre-slices every pizza into 8 slices (Objects).
  3. When a customer (the kernel) wants a slice (an Inode), the restaurant just hands one over immediately.
  4. When the customer is done, they give the slice back. The restaurant doesn't throw it away; it just puts it back in the box for the next customer.

Key Benefits of Slabs

A. No Internal Fragmentation

Because the allocator knows exactly how big the object is, it packs them tightly into the pages. A 400-byte object will be placed in a 400-byte slot, with almost no wasted space.

B. Object Recycling (Performance)

This is the most "clever" part. In the kernel, initializing a complex structure (setting up its spinlocks, wait queues, and pointers) is expensive.

  • When an object is "freed," the Slab Allocator does not wipe the memory or return it to the system.
  • It keeps the object in its "initialized" state.
  • The next time the kernel needs that type of object, it gets one that is already "warmed up," significantly speeding up performance.

C. Hardware Cache Efficiency

By keeping objects of the same type close together in memory, the CPU's L1/L2 caches are much more likely to have the data ready (Cache Locality).

Slab States

A Slab can be in one of three states:

  1. Full: All objects in the slab are marked as used.
  2. Partial: Some objects are used, some are free. (The allocator prefers to use these first).
  3. Empty: All objects are free. If the system is low on memory, the allocator might give these pages back to the Buddy System.

Seeing it in action

You can see every slab cache currently running on your Linux machine by looking at /proc/slabinfo.

sudo slabtop

(This command gives you a real-time view of which kernel objects are consuming the most memory.)

You will likely see:

  • ext4_inode_cache: Slabs specifically for ext4 Inodes.
  • dentry: Slabs for Directory Entries.
  • kmalloc-128, kmalloc-512: Generic slabs for various sizes.

Is the memory page size always the same as the disk block size?

The short answer is no, but in the vast majority of Linux systems, they are both set to 4KB by default to make the system run faster.

While they are often the same, they are controlled by two completely different things:

  1. Memory Page Size: Controlled by the CPU Hardware (Architecture).
  2. Disk Block Size: Controlled by the Filesystem Format (Software).

The Memory Page (Hardware Level)

The page size is the smallest unit of memory that the CPU's Memory Management Unit (MMU) can handle.

  • Common size: 4KB (on x86_64).
  • Can it change? Yes. Modern CPUs support Huge Pages (2MB or 1GB). Some architectures like ARM or PowerPC can be configured for 16KB or 64KB pages.
  • Why it matters: Every time the kernel maps virtual memory to physical RAM, it does so in "page" increments.

The Disk Block (Filesystem Level)

The block size (also called "logical block size") is the smallest unit of space the filesystem can allocate on a disk.

  • Common size: 4KB (for ext4, XFS, etc.).
  • Can it change? Yes. When you format a disk (e.g., mkfs.ext4 -b 2048), you can choose 1KB, 2KB, or 4KB.
  • Physical Sector Size: Note that the actual hard drive has its own "sector size" (traditionally 512 bytes, now often 4KB "Advanced Format"). The filesystem block must be a multiple of the physical sector size.

What happens when they are different?

Case A: Block Size < Page Size (Common on older systems)

  • Example: 1KB Disk Block, 4KB Memory Page.
  • Result: One memory page in the Page Cache will hold 4 disk blocks.
  • How Linux handles it: The kernel uses a structure called a Buffer Head. It attaches four buffer heads to the page, each tracking the status (dirty/clean) of one of the four 1KB blocks. This works fine but adds a bit of management overhead.

Case B: Block Size > Page Size (The "Problem" Case)

  • Example: 16KB Disk Block, 4KB Memory Page.
  • Result: A single disk block is larger than a single unit of memory management.
  • The Struggle: Historically, Linux had a hard time with this. The Page Cache is indexed by pages. If a block is 16KB, the kernel has to guarantee it can find 4 contiguous physical pages in RAM to hold that one block. If memory is fragmented, this fails.
  • Modern Status: Recent Linux kernels (and filesystems like XFS) have introduced "Large Block Size" support to handle this better, but it is still more complex than the 4KB/4KB match.

Why do we usually keep them the same (4KB)?

When the Page Size and Block Size are both 4KB, the kernel achieves "Page-Block Alignment."

  1. Zero Wasted Effort: One disk read fills exactly one page in the Page Cache. There is no need for "Buffer Heads" to split a page into smaller pieces.
  2. Direct Mapping: The kernel can map a file directly into a process's memory (mmap) with perfect 1
    alignment.
  3. Efficiency: Disk I/O and Memory management "speak the same language." When the kernel decides to "evict" a dirty page from RAM, it corresponds exactly to writing one block back to the disk.

What is a Page Fault?

A Page Fault occurs when a program tries to access a part of its memory (a "page") that is not currently mapped into the physical RAM (CPU's memory).

When this happens, the CPU hardware (the MMU) freezes the program and sends a signal to the Linux Kernel. The kernel looks at the situation and decides what to do. There are three types:

A. Minor Page Fault (Normal)

The data is actually already in RAM (perhaps because another program is using it), but this specific program doesn't have a "link" to it yet. The kernel just updates the program’s map.

  • Result: The program resumes immediately. No one notices.

B. Major Page Fault (Normal, but slow)

The data is not in RAM at all. It is on the disk (either in the Swap file or in a file on disk like a database or a shared library).

  • Result: The kernel pauses the program, reads the data from the disk into RAM, updates the map, and then resumes the program. This is why a computer feels "laggy" when you have too many apps open—it's doing constant Major Page Faults.

C. Invalid Page Fault (The Error)

The program is trying to access a memory address that it is simply not allowed to access (e.g., address 0, or kernel memory).

  • Result: This leads to a Segmentation Fault.

Comparing to Segmentation Fault

While they sound similar, they are very different in terms of "intent." A Page Fault is usually a normal, routine part of how a computer works, while a Segmentation Fault is almost always a sign of a bug or a crash.

Think of it this way:

  • Page Fault: A "Please wait" message from the hardware to the kernel.
  • Segmentation Fault: An "Illegal operation" message from the kernel to the application.

What is a Segmentation Fault

A Segmentation Fault is often the end-result of an "Invalid Page Fault."

  1. The program tries to access an illegal address (e.g., 0x0000).
  2. The CPU sees that this address isn't in RAM and triggers a Page Fault.
  3. The Kernel looks at its "Master Map" and realizes the program is not allowed to have memory at 0x0000.
  4. Instead of loading data from disk, the kernel says: "This is a violation!"
  5. The kernel sends a SIGSEGV (Segmentation Fault) signal to the program, which usually causes it to crash and print Segmentation fault (core dumped).